Flutter – Detecting memory leaks – Find leaks with Dart DevTools
Apr 12th, 2025
This is the third blog post in the
Flutter – Detecting memory leaks
series. If you just stumbled onto this article and want to start from the beginning, navigate
.
This article will cover the simplest and most suggested way of detecting Flutter memory leaks, which is using Dart DevTools Memory view tool. It is the topic extensively covered by Flutter’s documentation, blog posts and YouTube videos, but this series wouldn’t be complete without it.
Code setup
The best way to learn a new skill and retain the knowledge learned is by trying to do it yourself, not only reading about it. If your app doesn’t leak, and you are just learning about Flutter memory leak detection, I implore that you use my code setup to follow along. Otherwise use your leaking app.
The code below is a simple two-page Flutter application with a button that creates non-collectable timers.
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Memory Leaks demo app',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
useMaterial3: true,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
void _induceMemoryLeak() {
for (int i = 0; i < 100000; i++) {
Timer.periodic(const Duration(seconds: 1), (timer) {
// do nothing
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text("Memory Leaks demo page"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomTextButton(onPressed: _induceMemoryLeak, text: "Add 100.000 timers"),
const SizedBox(height: 30),
CustomTextButton(
onPressed: () => Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const SecondPage())),
text: "Navigate",
),
],
),
),
);
}
}
class SecondPage extends StatelessWidget {
const SecondPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: const Text("Memory Leaks demo page - 2")),
body: Center(
child: CustomTextButton(
onPressed: () => Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const HomePage())),
text: "Navigate back",
),
),
);
}
}
class CustomTextButton extends StatelessWidget {
const CustomTextButton({super.key, required this.text, required this.onPressed});
final String text;
final Function() onPressed;
@override
Widget build(BuildContext context) {
return TextButton(
style: TextButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Colors.teal,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
onPressed: onPressed,
child: Text(text),
);
}
}
A two-page app with leaking timers
Detecting memory leaks with Dart DevTools
First things first. You must NEVER look for memory leaks running the Flutter app in debug mode, as the absolute memory used may be higher while running your application in debug mode. For more accurate results use
profile
mode.
Detection steps:
- switch to the Memory view tab and select the Diff Snapshots tab
- create a heap snapshot
- use the application
- create another heap snapshot
- diff to find the leaking objects
Step 1: Switch to the memory view tab and select the Diff Snapshots tab
When your application is running in profile mode, open Dart DevTools and switch to the
Memory view
tab. This opens all of the memory-related tools Dart DevTools have to offer.
Next click on the
Diff Snapshots
tab. This tool lets you take memory heap snapshots throughout the application’s lifetime and compare the snapshots between them. If you remember from the
in this series, the leaking objects are not garbage collected, and in theory, you should catch them in here.


Dart DevTools - Memory view tab
Dart DevTools - Memory view tab
Step 2: Create a heap snapshot
As soon as your application stabilises, you need to create a memory heap snapshot. This will serve as a base for heap memory comparison.


Dart DevTools - How to create a memory heap snapshot
Dart DevTools - How to create a memory heap snapshot
Step 3: Use the application
As you are not really sure what is causing your memory leaks, this step is all about trying to induce memory leaks. You should use your app in the same way your end-users are/will use it. Something during its operation is causing your memory leaks. If you are following this article using my provided code, now it is time to press the
Add 100.000 timers
button and then the
Navigate
button to navigate to the next page. By returning to the original page, the garbage collector should have collected the timers if they were disposed of properly, but it didn’t.


Leaking application
Leaking application
Step 4: Create another heap snapshot
Now, your application is hopefully leaking, and it’s time to create a new memory heap snapshot that will be compared to the initial snapshot. Before making a snapshot, force the Garbage Collection by pressing the
GC
button inside the Dart DevTools Memory View tab.


Dart DevTools - Forcing garbage collection and making a new heap snapshot
Dart DevTools - Forcing garbage collection and making a new heap snapshot
Step 5: Diff to find the leaking objects
Before diffing the snapshots, make sure to set the class filters to show you all types of objects, only in this way, you are “sure” to see all of the objects.


Dart DevTools - Filtering object types
Dart DevTools - Filtering object types
And finally, you compare your latest heap snapshot with the original one.


Dart DevTools - Diffing the snapshots
Dart DevTools - Diffing the snapshots
Observing the difference between the two snapshots, we can see that, in my case, the leak was caused, indeed, by non-garbage-collected timers. You can see the number of timers created, that none of them was released from the memory and that they newly occupy 7.6 MB of additional memory.
What to do if you’ve found the culprit?
If you were lucky enough to find your culprit/s, it is time to monitor the object/s and try to find the leak’s origin in your app. If you are using my code, you already know the origin, but in real life, the origin of leaks will be more tricky to find. Introducing the
Trace Instances
tab.
After you switch to the Trace Instances tab, you need to set filters for the object classes you want to observe and check the
Trace
checkbox so the actual tracing commences. You then need to use your application like you did on the
Diff Snapshots
tab between your two heap snapshots. When you are fairly certain the leak has been introduced, press the
Refresh
button.


Dart DevTools - Tracing the leaking classes
Dart DevTools - Tracing the leaking classes
When the delta of the class changes, it is time to start the analysis. Click on the class and then on the
Expand All
button to expand the traces. Read carefully through the call tree to find the origin of your leak.


Dart DevTools - Leaking timers origin found
Dart DevTools - Leaking timers origin found
Is your code still leaking?
The above tool is not omnipotent. Your app might still leak, and the tool didn’t help you find your source. This happens for various reasons, like:
- Indirect Retentions – Objects held via closures, callbacks, or framework internals may not show clear retention paths.
- Unclosed Resources – Forgotten StreamSubscriptions, AnimationControllers, or ScrollControllers can leak without apparent traces.
- Static/Global References – Objects stored in static variables or global caches won’t appear as leaks—they’re intentionally retained but might be unintentional in your logic.
- Third-Party plugins – Leaks in plugins or libraries may not be visible in your app’s instance tracking.
- Complex Object Graphs – If an object is retained through multiple layers (for example, a mix of widgets, providers, and streams), Dart DevTools might not highlight the root cause.
Remember, if the above techniques didn’t help, your leak might be relatively subtle – time to put on your detective hat and try the next tool in my utility belt, the Dart’s Leak Tracker. The tool is still in development and not officially released, but I will show you how you can use it in
next article
of the series
(coming soon)
.