- by x32x01 ||
A Flutter screen can disappear while its controllers remain in memory. If objects such as
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
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.
It helps identify three important situations:
Flutter also provides
Important: Leak tracking is not a replacement for memory profiling. It detects supported lifecycle problems rather than every possible source of excessive memory consumption.
The setup involves three steps.
Step 1: Add the testing dependency
Open your project's
Using
Run the following command to install the dependencies:
Step 2: Create the global test configuration
Inside your project's
Add the following configuration:
Flutter's testing framework uses
The key instruction is:
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
For example:
Note: Global initialization enables the leak-tracking infrastructure. Passing
This is a common resource-management mistake in Flutter applications.
Here is an example of incorrect implementation:
Notice the problem?
The widget creates an
Flutter's lifecycle requires the controller to be disposed of when its owning state is removed.
The correct implementation
Add the
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:
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.
Common examples include:
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
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.
Instead of disabling leak detection across the entire project, you can configure exceptions for an individual test.
The package provides
For example:
Replace
Use this carefully.
Ignoring a class hides matching reports in that test, including potentially legitimate problems. Investigate the reported behavior before introducing an exception.
After configuring your widget tests, run:
You can also execute the same command in your continuous integration pipeline.
For example, a basic GitHub Actions workflow might look like this:
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
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.
Keep these practices in mind:
With
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.
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.
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 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.dartAdd 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();
} 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,
);
} experimentalLeakTesting explicitly allows a widget test to use the configured leak-detection settings.🧪 Practical Example: Detecting an Undisposed Controller
Consider a widget that creates anAnimationController 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'),
),
);
}
} 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();
} 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()); 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:
AnimationControllerFocusNode- Other Flutter objects instrumented for lifecycle tracking.
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'],
), 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 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 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.
- Provides detailed memory profiling.
- Helps inspect heap usage and object allocations.
- Supports investigating retained objects.
- Helps analyze memory behavior while an application runs.
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:
- Dispose of controllers when their owning widgets are removed.
- Cancel stream subscriptions when they are no longer needed.
- Remove listeners that require explicit cleanup.
- Avoid retaining widget contexts or state objects unnecessarily.
- Test widget creation and removal, not just initial rendering.
- Include leak detection in your CI workflow.
- Use Flutter DevTools when investigating complex memory problems.
Final Thoughts
Forgettingdispose() 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.