Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory leak on real device only #938

Open
2 tasks done
EArminjon opened this issue Apr 10, 2024 · 5 comments
Open
2 tasks done

Memory leak on real device only #938

EArminjon opened this issue Apr 10, 2024 · 5 comments

Comments

@EArminjon
Copy link

EArminjon commented Apr 10, 2024

🐛 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)
Capture d’écran 2024-04-10 à 17 01 45

With Image.network: (stable during scroll)
Capture d’écran 2024-04-10 à 17 02 46

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:

  • 📱 iOS
  • 🤖 Android (Pixel 7 Android 14)
@marwenbk
Copy link

marwenbk commented Jun 6, 2024

on Linux same issue, actually it crush quicker.

@Ockerjo
Copy link

Ockerjo commented Jun 11, 2024

For those facing this issue, the following solution/workaround worked for me. flutter/flutter#102140 (comment)

@limonadev
Copy link

@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 #951

@andannn
Copy link

andannn commented Aug 2, 2024

Any update for this Issue?
I create a simple app and confirmed that errorListener causing memory leak.
bug_cached_network_image

  • with setting errorListener
    img_v3_02dc_2d21940d-60cd-4128-896d-68c1d32603ix

  • without setting errorListener
    image

@slaci
Copy link

slaci commented Aug 7, 2024

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() (flutter/flutter@aeddab4)?) 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants