Mar 15th, 2025
One early morning in late December 2024, when most of us were mentally already on our New Year’s leave, I came to work a bit later than usual. As I arrived at the office and was putting my 20-layer outfit on a clothes stand, a coworker turned to me and said: “Have you seen the email?”. Me, annoyingly: “What email?” as I have made it my life’s mission to avoid doing work-related stuff at home. He replies: “Joe from REDACTED-COMPANY-NAME messaged us that all their devices are frozen or have crashed”.
The above exchange marks the beginning of my two-month Flutter memory leak and app optimization goose-chase.
You, who are probably going through the same stuff, might find this blog post series a godsend. In this 4-part series, I will outline all that I have learned about memory leaks, their causation, and how to fix them. I will first touch on a little theory about what causes memory leaks and how to avoid them, and then continue with the ways to detect your memory leaks, from simplest to advanced techniques.
With the information you receive, it won’t take you two months to find and solve your memory leaks.
Imagine you are checking out of a hotel room, and the receptionist forgets to mark your room as empty. Even though that room is unused, no one else can use it.
Imagine you allocate an object to a place in memory and forget to dispose of it when you no longer need it. Even though that memory space could be reused, no one else can use it.
Having such an object occupying your memory space is called a memory leak.
In short,… memory leaks cause high memory usage, slow down performance, cause crashes, and, on mobile devices, drain more battery.
Let’s focus on Flutter and its memory management functionalities.
Dart programming language, the backbone of Flutter, uses a `generational garbage collection` (GC) mechanism to manage the system’s memory usage.
It uses a two-tiered approach that takes care of short-life objects and long-life objects, respectively:
Flowchart of Dart's Garbage Collection Process
Every object with no references attached is a target for Dart’s GC. If an object still has references pointing to it, even when it is not used anymore, Dart’s GC will skip it, and the object will remain in memory. Let's label them as non-collectable objects.
So, do you even need to do anything? Does Dart’s garbage collector do all the work instead of you? Does Dart take care of unused memory allocations?
Yes and no.
It is true that unlike `C`, where you need to take care of all your memory allocations by yourself, Dart does most of the heavy lifting for you. However, there are exceptions for which Dart GC isn’t smart enough to take care of them.
As Dart is an object-oriented language, it means that everything is an object deep down, even the primitive types like `int`, `double`, `bool`, `String` and `null`.
At first glance, you will do really well if you take care of all the Dart objects with the `dispose` method on them. The developer must dispose of these objects " manually " when the object’s intended use is complete.
But the above doesn’t cover everything. Not all of non-collectable objects have a dispose method on them.
Here is a list of most commonly leaking objects:
If you are interested in examples and blueprints on how to dispose of the above non-collectable object, head onto
of this series.