Detect Flutter Memory Leaks with leak_tracker

x32x01
  • by x32x01 ||
Detect Flutter Memory Leaks with leak_tracker
A Flutter screen can disappear while its controllers remain in memory. If objects such as AnimationController or FocusNode are not properly disposed of, they may retain resources longer than intended.

These issues are easy to miss during development. Your app might build successfully, pass code reviews, and work normally during short testing sessions. Over time, however, improper resource management can contribute to unnecessary memory usage and performance problems.

The good news? You don't have to manually inspect every StatefulWidget to catch these mistakes.

Flutter provides a testing tool called leak_tracker that helps detect improperly managed disposable objects during automated widget tests.

In this guide, you'll learn how to configure Flutter leak tracking, detect common disposal mistakes, and integrate leak detection into your testing workflow.



🔍 What Is leak_tracker in Flutter?​

leak_tracker is a Dart package used to monitor the lifecycle of instrumented objects that require explicit cleanup.
It helps identify three important situations:
  • Not-disposed objects: Objects that should have been disposed of but were not.
  • Disposed but retained objects: Objects that remain reachable in memory after disposal.
  • Late garbage collection: Objects that remain in memory longer than expected after disposal.
These categories help developers identify potential memory leaks and resource-management problems before they affect users.
Flutter also provides leak_tracker_flutter_testing, which integrates leak tracking with Flutter's widget testing framework.

Important: Leak tracking is not a replacement for memory profiling. It detects supported lifecycle problems rather than every possible source of excessive memory consumption.



⚙️ How to Enable leak_tracker in Flutter​

You can configure leak detection globally so your widget tests automatically check for supported leaks.
The setup involves three steps.

Step 1: Add the testing dependency
Open your project's pubspec.yaml file and add the following package under dev_dependencies:
YAML:
dev_dependencies:
flutter_test:
sdk: flutter
leak_tracker_flutter_testing: any
Using any follows Flutter's documented testing setup, allowing dependency resolution to select a compatible package version.

Run the following command to install the dependencies:
Bash:
flutter pub get

Step 2: Create the global test configuration
Inside your project's test directory, create a file named: flutter_test_config.dart
Add the following configuration:
Dart:
import 'dart';

import 'package/leak_tracker_flutter_testing.dart';

Future<void> testExecutable(FutureOr<void> Function() testMain) async {
LeakTesting.enable();

await testMain();
}
Flutter's testing framework uses testExecutable() to initialize the testing environment before executing tests.
The key instruction is: LeakTesting.enable();
It enables leak tracking globally for the test suite using this configuration.

Step 3: Enable leak tracking in widget tests
To check an individual widget test, use the experimentalLeakTesting parameter.
For example:
Dart:
import 'package/material.dart';
import 'package/flutter_test.dart';
import 'package/leak_tracker_flutter_testing.dart';

void main() {
testWidgets(
'My widget does not leak resources',
(WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: Text('Leak tracking test'),
),
),
);
},
experimentalLeakTesting: LeakTesting.settings,
);
}
Note: Global initialization enables the leak-tracking infrastructure. Passing experimentalLeakTesting explicitly allows a widget test to use the configured leak-detection settings.



🧪 Practical Example: Detecting an Undisposed Controller​

Consider a widget that creates an AnimationController but forgets to dispose of it.
This is a common resource-management mistake in Flutter applications.

Here is an example of incorrect implementation:
Dart:
class AnimatedScreenState extends State<AnimatedScreen>
with SingleTickerProviderStateMixin {

late final AnimationController controller;

@override
void initState() {
super.initState();

<span>controller = AnimationController(<br>  vsync: this,<br>  duration: const Duration(seconds: 1),<br>);</span>
}

@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text('Animated Screen'),
),
);
}
}
Notice the problem?
The widget creates an AnimationController but never releases it.
Flutter's lifecycle requires the controller to be disposed of when its owning state is removed.

The correct implementation
Add the dispose() method:
Dart:
@override
void dispose() {
controller.dispose();
super.dispose();
}
This releases the controller's resources when the widget is permanently removed from the widget tree.
A widget test that mounts and subsequently removes the widget can help verify this behavior.

For example, after mounting the screen, replace it with another widget:
Dart:
await tester.pumpWidget(const SizedBox.shrink());
This triggers the previous widget's disposal lifecycle.
When leak tracking is enabled for the test, supported lifecycle violations can be reported.
Tip: Always make sure your test actually removes the widget being inspected. Simply rendering a screen does not necessarily exercise its disposal behavior.



📋 What Can leak_tracker Detect?​

Flutter's leak-tracking integration supports instrumented disposable objects.
Common examples include:
  • AnimationController
  • FocusNode
  • Other Flutter objects instrumented for lifecycle tracking.
However, there are important limitations.

1. It doesn't automatically monitor every Dart object.
Custom classes may require additional instrumentation before their lifecycle can be tracked.

2. It doesn't replace manual resource cleanup.
For example, a StreamSubscription still needs to be canceled when it is no longer required.
Dart:
@override
void dispose() {
subscription.cancel();
super.dispose();
}

3. It doesn't detect every memory leak.
Objects retained through static references, caches, closures, or other application structures may require additional investigation using Flutter DevTools.



⚠️ How to Ignore Known Leak Reports​

Sometimes a test reports a leak originating from framework behavior, third-party code, or an object outside your immediate control.
Instead of disabling leak detection across the entire project, you can configure exceptions for an individual test.
The package provides LeakTesting.settings.withIgnored() for customizing ignored leak categories.

For example:
Dart:
experimentalLeakTesting: LeakTesting.settings.withIgnored(
classes: ['KnownProblematicClass'],
),
Replace KnownProblematicClass with the actual class you intend to exclude.
Use this carefully.
Ignoring a class hides matching reports in that test, including potentially legitimate problems. Investigate the reported behavior before introducing an exception.



🚀 Run Flutter Leak Detection in CI​

One of the biggest benefits of automated leak detection is that it can become part of your regular testing workflow.
After configuring your widget tests, run:
Bash:
flutter test
You can also execute the same command in your continuous integration pipeline.

For example, a basic GitHub Actions workflow might look like this:
YAML:
name: Flutter Tests

on:
push:
pull_request:

jobs:
test:
runs-on: ubuntu-latest

steps:
  - uses: actions/checkout@v4

  - uses: subosito/flutter-action@v2
    with:
      channel: stable

  - name: Install dependencies
    run: flutter pub get

  - name: Run widget tests
    run: flutter test
This runs your Flutter tests whenever changes are pushed or a pull request is created.
When leak tracking is configured for the relevant tests, detected lifecycle violations can cause those tests to fail.
That means developers can catch supported resource-management mistakes before merging changes.



🔬 leak_tracker vs. Flutter DevTools​

Both tools help investigate memory-related issues, but they serve different purposes.
leak_tracker
  • Detects supported object lifecycle problems.
  • Integrates with automated widget tests.
  • Helps catch missing disposal operations.
  • Can be included in CI pipelines.
Flutter DevTools
  • Provides detailed memory profiling.
  • Helps inspect heap usage and object allocations.
  • Supports investigating retained objects.
  • Helps analyze memory behavior while an application runs.
Which should you use?
Use both.
Leak tracking is useful for automated regression testing, while DevTools provides deeper visibility when investigating memory consumption or unexpected object retention.
They complement each other rather than replace one another.



✅ Best Practices for Preventing Flutter Memory Leaks​

Automated testing works best when combined with proper lifecycle management.
Keep these practices in mind:
  1. Dispose of controllers when their owning widgets are removed.
  2. Cancel stream subscriptions when they are no longer needed.
  3. Remove listeners that require explicit cleanup.
  4. Avoid retaining widget contexts or state objects unnecessarily.
  5. Test widget creation and removal, not just initial rendering.
  6. Include leak detection in your CI workflow.
  7. Use Flutter DevTools when investigating complex memory problems.



Final Thoughts​

Forgetting dispose() is an easy mistake, especially in Flutter projects with many stateful widgets.
With leak_tracker_flutter_testing, you can automate checks for supported resource lifecycle problems instead of relying entirely on manual code reviews.
Configure leak tracking, enable it in relevant widget tests, and run those tests in CI.
Your tests can help catch disposal mistakes before your users experience the consequences.
 
Similar threads
x32x01
Replies
0
Views
139
x32x01
x32x01
x32x01
Replies
0
Views
161
x32x01
x32x01
x32x01
Replies
0
Views
26
x32x01
x32x01
x32x01
Replies
0
Views
119
x32x01
x32x01
x32x01
Replies
0
Views
194
x32x01
x32x01
Register & Login Faster
Forgot your password?
Forum Statistics
Threads
1,180
Messages
1,187
Members
17
Latest Member
ahmadhamees
Back
Top