My application (through multiple class instances, static classes and get_it dependency injector) contains some nasty memory leaks. I'm on a mission to find and resolve them.
But first, I wanted to establish the ground truth with a small example. With profiling of that example, I noticed that inside Flutter DevTools
-> Memory
-> Heap Snapshot
my class instance is still present even though I navigated away from that screen (that initialised this class) and therefor I assumed that it will be removed with GC from the heap memory.
This is not a case, and the ProductService
class is still present in the heap memory after I navigate back to the main screen.
Here is my complete example app, if somebody would like to reproduce this issue.
main.dart:
import 'package:flutter/material.dart';
import 'package:memory_leak_example/second.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('First Route'),
),
body: Center(
child: ElevatedButton(
child: const Text('Open route'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SecondRoute()),
);
},
),
),
);
}
}
second.dart:
import 'package:flutter/material.dart';
import 'package:memory_leak_example/product_service.dart';
class SecondRoute extends StatefulWidget {
const SecondRoute({super.key});
@override
State<SecondRoute> createState() => _SecondRouteState();
}
class _SecondRouteState extends State<SecondRoute> {
late final ProductService productService = ProductService();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Second Route'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: StreamBuilder<Object>(
stream: productService.timer$,
builder: (context, snapshot) {
return Text('Go back (${snapshot.data})!');
},
),
),
),
);
}
@override
void dispose() {
productService.onDispose();
super.dispose();
}
}
product_service.dart:
import 'dart:async';
import 'bridge.dart';
class ProductService {
ProductService() {
sub = timer$.listen((int tick) {
print(tick);
});
}
final Stream<int> timer$ = Bridge.timer$;
late final StreamSubscription<int> sub;
void onDispose() {
sub.cancel();
}
}
bridge.dart
import 'dart:async';
class Bridge {
Bridge._();
static Stream<int> timer$ = Stream<int>.periodic(const Duration(seconds: 1), (x) => x).asBroadcastStream();
}
Why is ProductService
instance not removed from the heap memory after SecondScreen
is disposed?