flutter_cached_network_image
flutter_cached_network_image copied to clipboard
Memory leak on real device only
🐛 Bug Report
On real device Android & iOS this package have a memory leak.
Our app got some crash in production because of this issue : we have a long list of product inside a paginated infinite list. User can scroll on it and some of them reported crash. After investigation we discover this memory leak.
Expected behavior
Constant RSS usage
Reproduction steps
Use code bellow and check on devtools the memory usage graph.
With CachedNetworkImage : (increase during scroll)
With Image.network: (stable during scroll)
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: false,
),
home: const Home(),
);
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
bool useCachedNetwork = true;
@override
Widget build(BuildContext context) {
return Builder(
builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Memory leak"),
),
floatingActionButton: FloatingActionButton.extended(
label: Text(
"Use ${useCachedNetwork ? "Image.network" : "CachedNetworkImage"}",
),
onPressed: () {
setState(() => useCachedNetwork = !useCachedNetwork);
},
),
body: ListView.builder(
itemBuilder: (BuildContext context, int index) => SizedBox(
height: 80,
child: Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: <Widget>[
AspectRatio(
aspectRatio: 1,
child: useCachedNetwork
? CachedNetworkImage(
imageUrl: "https://picsum.photos/id/$index/1000/1000",
errorListener: (_) {},
progressIndicatorBuilder: (
BuildContext context,
String url,
DownloadProgress progress,
) =>
Center(
child: CircularProgressIndicator(
value: progress.progress,
),
),
errorWidget: (_, __, ___) => const Center(
child: Icon(Icons.error),
),
)
: Image.network(
"https://picsum.photos/id/$index/1000/1000",
errorBuilder: (_, __, ___) => const Center(
child: Icon(Icons.error),
),
),
),
const SizedBox(width: 16),
Expanded(
child: Text(
index.toString(),
),
),
],
),
),
),
),
),
);
},
);
}
}
Configuration
Versions:
flutter: 3.16.8
cached_network_image: 3.3.1
Platform:
- [x] :iphone: iOS
- [X] :robot: Android (Pixel 7 Android 14)
on Linux same issue, actually it crush quicker.
For those facing this issue, the following solution/workaround worked for me. https://github.com/flutter/flutter/issues/102140#issuecomment-2031004058
@EArminjon I found using the errorListener
makes Flutter unable to remove unused instances of the CachedNetworkImage with the garbage collector. If you don't mind having error handling, try removing the errorListener
property and check if that helps with the memory leaks.
Reference to https://github.com/Baseflow/flutter_cached_network_image/issues/951
Any update for this Issue?
I create a simple app and confirmed that errorListener
causing memory leak.
bug_cached_network_image
-
with setting errorListener
-
without setting errorListener
Sadly we also bumped into this very serious issue. If you check ImageCompleterHandler's dispose
method you can see that it calls maybeDispose
on the ImageStreamCompleter
object. This maybeDispose method does not do anything if the completer has any listeners, so basically nothing is disposed from the memory.
Replacing addListener()
by addEphemeralErrorListener()
(https://github.com/flutter/flutter/commit/aeddab428d4d51dc2e7b070969572067a37d3650)?) would solve this issue. The only problem is: this method is only available since 3.16
, but this package has flutter: '>=3.10.0'
requirements in its pubspec.yaml, so I'm not sure how could be this implemented with backward compatibility:
if (errorListener != null) {
imageStreamCompleter.addEphemeralErrorListener((exception, stackTrace) {
errorListener?.call(exception);
});
}
Without the listener all errors are forwarded to FlutterError.onError
which spams them to Crashlytics if its connected, so it is a tricky situation.
I also confirm errorListener
cause memory leak. Is this a bug of this package or a Flutter bug?
@XuanTung95 This is a bug of this package, and I think it is related to this PR https://github.com/Baseflow/flutter_cached_network_image/pull/891