skip to Main Content

My flutter app was running fine on all platforms for months and has randomly started crashing and I cannot find the issue or figure why it started happening out of the blue. The crash closes the app and the app is unable to open again.

I have both Crashlytics and Sentry set up to check crash logs but neither are showing what the issue is. I was only able to pull the following errors by reproducing the crash with a real device connected to VSCode. I have provided two, the error almost always occurs on a specific screen or on the screen before that one. The error occurs on some android devices, but never on Samsung Galaxy S21 or S8 which I have personally tested on. On iPhone 6, The app crashes way before I can get to the screens in question. The error does not happen on the emulator. Once it crashes and stops on iPhone 12, the app will not even launch on the phone if I try opening it.

I have tried updating Flutter and XCode, using CachedNetworkImage instead of just NetworkImage, and I have ensured that I call if(mounted) before any calls to setState to minimise any chance of a memory leak, and that I properly dispose of any StreamSubscriptions by overriding the dispose() method. I don’t even know where to start looking for this issue.

Please help me identify what is going on here. What method can I use to find what is causing this crash??

Running on iPhone 12:

Error 1:
* thread #15, name = 'io.worker.3', stop reason = EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=2098 MB, unused=0x0)
    frame #0: 0x000000010f011538 Flutter`ycc_rgb_convert + 140
Flutter`ycc_rgb_convert:
->  0x10f011538 <+140>: strb   w21, [x5]
    0x10f01153c <+144>: ldr    x21, [x12, x19, lsl #3]
    0x10f011540 <+148>: ldr    x20, [x11, x20, lsl #3]
    0x10f011544 <+152>: add    x20, x20, x21
Target 0: (Runner) stopped.
Lost connection to device.

Error 2: 
[Process] 0x11f000d30 - NetworkProcessProxy::didClose (Network Process 0 crash)
[ServicesDaemonManager] interruptionHandler is called. -[FontServicesDaemonManager connection]_block_invoke
* thread #15, name = 'io.worker.3', stop reason = EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=2098 MB, unused=0x0)
    frame #0: 0x000000011336f7ac Flutter`ycc_rgb_convert + 140
Flutter`ycc_rgb_convert:
->  0x11336f7ac <+140>: strb   w21, [x5]
    0x11336f7b0 <+144>: ldr    x21, [x12, x19, lsl #3]
    0x11336f7b4 <+148>: ldr    x20, [x11, x20, lsl #3]
    0x11336f7b8 <+152>: add    x20, x20, x21
Target 0: (Runner) stopped.
Lost connection to device.

Flutter Version:
Flutter 3.7.0 • channel stable • https://github.com/flutter/flutter.git
Framework • revision b06b8b2710 (2 days ago) • 2023-01-23 16:55:55 -0800
Engine • revision b24591ed32
Tools • Dart 2.19.0 • DevTools 2.20.1

Running flutter doctor...
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.7.0, on macOS 12.6.3 21G419 darwin-x64, locale en-GB)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[✓] Xcode - develop for iOS and macOS (Xcode 14.2)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2021.2)
[✓] VS Code (version 1.74.3)
[✓] Connected device (3 available)
[✓] HTTP Host Availability

• No issues found!

EDIT:

Thanks for your reply. The crash mostly happens in one specific part of the app where I’m loading the images in a screen (iPhone 6) or one or two screens after the images screen is loaded (iPhone12). I upgraded to Flutter 3.7 as an attempt to fix the issue which still persists.

I think it’s an image issue because ChatGPT said: "The function name "ycc_rgb_convert" suggests that it could be related to image processing, as YCC (YUV) is a color space used in image and video compression, but it doesn’t confirm that it is related to loading images." Of course, this is merely a clue and not definitive.

It also does not happen on all devices. It happens on iPhone 6, it takes a bit longer to happen on iPhone 12, and it doesn’t happen at all on either Samsung Galaxy S21 or S8. The error started happening about 2 weeks ago, and I only started using CacheNetworkImage while trying to fix this issue, I didn’t need it before and its not really helped now to be honest.

The crash happens in the places screen (or shortly after this screen is in the stack) where I load a sliver grid of "place items". A place item is a grid tile that has a main image and an avatar image, as well as some text and 2 icon buttons from the package like_button placed in the header and footer of the Grid tile. Clicking on a place item takes you to the place detail screen, where the crash will sometimes happen with iPhone12. If I totally avoid the places screen on iPhone 6, the crash will not happen. But if I go load even a measly 4 place items in the places screen, the crash will happen on iPhone 6.

I paginate by loading 6 place items initially and then an additional 4 as the user scrolls. The main images I’m loading are largest and are no more than 1MB ( they have an average size of maybe 400-500kb) and the avatar images are even smaller, the biggest being maybe 200kb and the smallest being less than 20kb. The images are the same ones they have always been as they belong to place documents.

The places screen has a background image loaded from assets that is 200kb and is also precached (another thing I did to fix this but wasn’t there or necessary before). I also use the same placeholder image for each of the cached network images I’m loading, and this is also 200kb and is precached.

Could you perhaps give pointers on how I can identify the memory leak? Trying to use memory profiler in DevTools is not working because it auto disconnects when the crash happens and I get nothing from the console except the error I gave above (only with iPhone 12, no logs at all from iPhone 6). When I ran it on galaxy s8 in profile mode, the retained size never exceeded 25MB while I took all steps that would cause the crash in iPhone 6 and 12, and it runs smooth as butter. No memory spikes.

When I run the iPhone 6 in profile mode, Before it disconnects, the dart heap stays around 16MB, retained size stays around 20-30MB from starting dev tools until the crash, there are no visible memory spikes but perhaps I’m using the tool incorrectly? Idk…

Sentry.io logs these iPhone 6 crashes and says: "OutOfMemory: The OS most likely terminated your app because it overused RAM." but it gives no further details.

Sentry is also a new addition in trying to diagnose this issue.

Here is the place item code:

import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:like_button/like_button.dart';
import 'package:provider/provider.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cached_network_image/cached_network_image.dart';

import '../data/color_palette.dart';
import '../widgets/pricing_icons.dart';
import '../models/place.dart';
import '../screens/place_detail_screen.dart';
import '../models/user.dart';
import '../services/database.dart';
import './custom_dialogbox.dart';

class PlaceItem extends StatelessWidget {
  final Place venue;
  PlaceItem(this.venue);

  final FirebaseAuth _auth = FirebaseAuth.instance;
  final DatabaseService _db = DatabaseService();

  @override
  Widget build(BuildContext context) {
    Size size = MediaQuery.of(context).size;
    final venue = Provider.of<Place>(context, listen: false);
    return ClipRRect(
      clipBehavior: Clip.antiAlias,
      borderRadius: BorderRadius.circular(20),
      child: GestureDetector(
        onTap: () {
          ScaffoldMessenger.of(context).hideCurrentSnackBar();
          Navigator.of(context).pushNamed(
            PlaceDetailScreen.routeName,
            arguments: venue.venueId,
            // Extract in the screen we'll nav to with ModalRoute
          );
        },
        child: GridTile(
          header: Consumer<Place>(
              builder: (ctx, venue, child) => _gridTileHeader(context)),
          footer: _gridTileFooter(size),
          // Venue Image
          child: CachedNetworkImage(
            imageUrl: venue.venueImageUrl,
          
            fadeInDuration: const Duration(milliseconds: 500),

            placeholder: (context, url) => Image.asset(
              'assets/images/tile_background.png',
              fit: BoxFit.cover,
            ),
            fit: BoxFit.cover,
            errorWidget: (context, url, error) => Container(
              decoration: const BoxDecoration(
                image: DecorationImage(
                  image: AssetImage(
                    'assets/images/tile_background.png',
                  ),
                  fit: BoxFit.cover,
                ),
              ),
              child: const Center(
                child: Icon(Icons.error, color: Colors.grey,),
              ),
            ),
          ),
        ),
      ),
    );
  }

  Widget _gridTileHeader(context) {
    final user = _auth.currentUser;
    final animationDuration = const Duration(milliseconds: 700);
    return StreamBuilder<UserData>(
        stream: DatabaseService(uid: user.uid).userData,
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            UserData userData = snapshot.data;
            bool isBookmarked = userData.bookmarks.contains(venue.venueId);
            bool isFaved = userData.favorites.contains(venue.venueId);

            return GridTileBar(
              backgroundColor: Colors.black.withOpacity(0.4),
              // Animated bookmark button
              leading: LikeButton(
                // key: _globalKey,
                isLiked: isBookmarked,
                circleColor: const CircleColor(
                  start: firstColor,
                  end: sixthColor,
                ),
                bubblesColor: const BubblesColor(
                  dotPrimaryColor: sixthColor,
                  dotSecondaryColor: Colors.red,
                ),
                // size: 100, // used when testing for closer look
                // size: 50, // size of containing circle
                likeBuilder: (bool bookmarked) {
                  return Icon(
                    Icons.bookmark,
                    color: isBookmarked ? sixthColor : Colors.white,
                    // size: 75, // used when testing for closer look
                    size: 20,
                  );
                },
                animationDuration: animationDuration,

                onTap: (bookmarked) async {
                  print(bookmarked);
                  print(isBookmarked);
                  // return !bookmarked;
                  try {
                    // // if user's email isn't verified
                    if (!user.emailVerified) {
                      showDialog(
                          context: context, builder: (ctx) => VerifyEmail());
                      return bookmarked; // do nothing
                    } else {
                      // actual functionality
                      DatabaseService(uid: user.uid)
                          .toggleToUserList('bookmarks', venue.venueId);
                      ScaffoldMessenger.of(context).hideCurrentSnackBar();
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(
                          elevation: 10,
                          backgroundColor: firstColor,
                          content: !isBookmarked
                              ? const Text('Venue Added to Bookmarks!')
                              : const Text('Venue Removed from Bookmarks'),
                          duration: const Duration(milliseconds: 2500),
                          action: SnackBarAction(
                            textColor: sixthColor,
                            label: 'UNDO',
                            onPressed: () async {
                              // widget.venue.toggleBookmarkStatus();
                              DatabaseService(uid: user.uid)
                                  .toggleToUserList('bookmarks', venue.venueId);

                              // reverse it
                              _db.updateLikesBookmarks(
                                venue.venueId,
                                venue.vendorId,
                                venue.vendorName,
                                venue.address['city'],
                                'bookmarks',
                                bookmarked,
                              );
                            },
                          ),
                        ),
                      );

                      // Track bookmarks
                      // putting "await" with the function stops the animation
                      _db.updateLikesBookmarks(
                        venue.venueId,
                        venue.vendorId,
                        venue.vendorName,
                        venue.address['city'],
                        'bookmarks',
                        !bookmarked,
                      );

                      return !bookmarked;
                    }
                  } catch (e) {
                    print(e.toString());
                    return bookmarked; // dont change it if there was an error
                  }
                },
              ),
              title: venue.ownerManaged
                  ? const Icon(
                      Icons.verified,
                      color: Colors.white,
                    )
                  : const Text(''),

              // Animated Favorites Button
              trailing: LikeButton(
                // key: _globalKey,
                isLiked: isFaved,
                circleColor: const CircleColor(
                  start: firstColor,
                  end: Colors.red,
                ),
                bubblesColor: const BubblesColor(
                  dotPrimaryColor: secondColor,
                  dotSecondaryColor: Colors.red,
                ),
                // size: 100, // used when testing for closer look
                // size: 50, // sizze of containing circle
                likeBuilder: (bool faved) {
                  return Icon(
                    Icons.favorite,
                    color: isFaved ? secondColor : Colors.white,
                    // size: 75, // used when testing for closer look
                    size: 20,
                  );
                },
                animationDuration: animationDuration,

                onTap: (faved) async {
                  // print(faved);
                  // print(isFaved);
                  // return !faved;
                  try {
                    // // if user's email isn't verified
                    if (!user.emailVerified) {
                      showDialog(
                          context: context, builder: (ctx) => VerifyEmail());
                      return faved; // do nothing
                    } else {
                      // actual functionality
                      DatabaseService(uid: user.uid)
                          .toggleToUserList('favorites', venue.venueId);
                      ScaffoldMessenger.of(context).hideCurrentSnackBar();
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(
                          // behavior: SnackBarBehavior.floating,
                          elevation: 10,
                          backgroundColor: firstColor,
                          content: !isFaved
                              ? const Text('Venue Added to Favorites!')
                              : const Text('Venue Removed from Favorites'),
                          duration: const Duration(milliseconds: 2500),
                          action: SnackBarAction(
                            textColor: sixthColor,
                            label: 'UNDO',
                            onPressed: () async {
                              // widget.venue.toggleBookmarkStatus();
                              DatabaseService(uid: user.uid)
                                  .toggleToUserList('favorites', venue.venueId);

                              // reverse it
                              _db.updateLikesBookmarks(
                                venue.venueId,
                                venue.vendorId,
                                venue.vendorName,
                                venue.address['city'],
                                'likes',
                                faved,
                              );
                            },
                          ),
                        ),
                      );

                      // Track bookmarks
                      // putting "await" with the function stops the animation
                      _db.updateLikesBookmarks(
                        venue.venueId,
                        venue.vendorId,
                        venue.vendorName,
                        venue.address['city'],
                        'likes',
                        !faved,
                      );

                      return !faved;
                    }
                  } catch (e) {
                    print(e.toString());
                    return faved; // dont change it if there was an error
                  }
                },
              ),
            );
          } else if (user.isAnonymous) {
            return GridTileBar(
              backgroundColor: Colors.black.withOpacity(0.35),
              leading: IconButton(
                icon: const Icon(
                  Icons.bookmark_border,
                  color: Colors.white,
                  size: 20,
                ),
                onPressed: () {
                  showDialog(context: context, builder: (ctx) => AnonDialog());
                },
              ),
              title: const Text(''),
              trailing: IconButton(
                icon: const Icon(
                  Icons.favorite_border,
                  size: 20,
                ),
                onPressed: () {
                  showDialog(context: context, builder: (ctx) => AnonDialog());
                },
              ),
            );
          } else {
            // fake tile bar with no functionality
            return GridTileBar(
              backgroundColor: Colors.black.withOpacity(0.35),
              leading: IconButton(
                icon: const Icon(
                  Icons.bookmark_border,
                  color: Colors.white,
                  size: 20,
                ),
                onPressed: () {},
              ),
              title: const Text(''),
              trailing: IconButton(
                icon: const Icon(
                  Icons.favorite_border,
                  size: 20,
                ),
                onPressed: () {},
              ),
            );
          }
        });
  }

  Widget _gridTileFooter(size) {
    return GridTileBar(
      backgroundColor: Colors.black87,
      // title
      title: Padding(
        padding: const EdgeInsets.only(top: 1.0, bottom: 3),
        child: Text(
          venue.venueName,
          overflow: TextOverflow.ellipsis,
          softWrap: true,
          maxLines: 2,
          textAlign: TextAlign.left,
          style: const TextStyle(
              color: Colors.white,
              fontFamily: 'Poppins',
              fontSize: 12,
              height: 1),
        ),
      ),
      // subtitle column
      subtitle: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          // price icons
          FittedBox(
            fit: BoxFit.fitWidth,
            child: PricingIconsDB(venue),
          ),
          // Google rating
          Container(
            padding: const EdgeInsets.only(
              left: 2,
              top: 2,
            ),
            width: size.width * 0.25,
            child: FittedBox(
              fit: BoxFit.fitWidth,
              child: Row(
                children: [
                  const Icon(
                    FontAwesomeIcons.google,
                    color: Colors.white70,
                    // color: firstColor,
                    size: 10,
                  ),
                  const Text(' Rating: '),
                  Text(
                    venue.googleRating == null
                        ? '-'
                        : venue.googleRating
                            .toStringAsFixed(1)
                            .replaceAll(RegExp(r"([.]*0)(?!.*d)"), ""),
                    // '4.1',
                    style: const TextStyle(color: firstColor),
                  ),
                  const Icon(
                    Icons.star,
                    // color: Colors.white,
                    color: firstColor,
                    size: 15,
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
      trailing: Container(
          height: size.width * 0.126,
          width: size.width * 0.126,
          clipBehavior: Clip.antiAlias,
          decoration: const BoxDecoration(
            shape: BoxShape.circle,
          ),
          child: venue.vendorLogoUrl == null || venue.vendorLogoUrl.isEmpty
              ? Container(
                  color: Colors.black26,
                )
// What I used as placeholders before the crashes started:
              // The gifs may be expensive in terms of RAM - 200KB - 500KB each
              // ? Image.asset(
              //     // restaurant or cafe
              //     // loadedVenue
              //     //         .businessCategories
              //     //         .contains('c2')
              //     venue.venueCategories["restaurant"]
              //         ? 'assets/images/food_logo.gif'
              //         :
              //         // bar or pub
              //         // loadedVenue
              //         //         .businessCategories
              //         //         .contains('c3')
              //         venue.venueCategories["barOrPub"]
              //             ? 'assets/images/cocktail_logo.gif'
              //             : venue.venueCategories["cafe"]
              //                 ? 'assets/images/cafe_logo.gif'
              //                 :
              //                 // cinema
              //                 'assets/images/cinema_logo.gif')
              : CachedNetworkImage(
                  imageUrl: venue.vendorLogoUrl,
                  placeholder: (context, url) =>
                      Container(color: Colors.black26),
                  errorWidget: (context, url, error) => const Icon(Icons.error),
                  fadeInDuration: const Duration(milliseconds: 700),
                )
// What I used before incorporating CachedNetworkImage for this crash 
          // : FadeInImage.assetNetwork(
          //     placeholder: venue.venueCategories["restaurant"]
          //         ? 'assets/images/food_logo.gif'
          //         :
          //         // bar or pub
          //         // loadedVenue
          //         //         .businessCategories
          //         //         .contains('c3')
          //         venue.venueCategories["barOrPub"]
          //             ? 'assets/images/cocktail_logo.gif'
          //             : venue.venueCategories["cafe"]
          //                 ? 'assets/images/cafe_logo.gif'
          //                 :
          //                 // cinema
          //                 'assets/images/cinema_logo.gif',
          //     image: venue.vendorLogoUrl,
          //     fit: BoxFit.fitHeight,
          //   ),
          ),
    );
  }
}

And here is the Sliver Grid that uses these PlaceItems:

SliverGrid(
                      
                        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                          crossAxisCount: constraints.maxWidth > 700 ? 3 : 2,
                          mainAxisSpacing: size.width * 0.025,
                          crossAxisSpacing: size.width * 0.025,
                          childAspectRatio: 1,
                        ),
                        delegate: SliverChildBuilderDelegate(
                          (ctx, i) => ChangeNotifierProvider.value(
                            value: _places[i],
                            child: PlaceItem(_places[i]),
                          ),
                          childCount: _places.length,
                        ),
                      ),

It lives inside a CustomScrollView as one of the slivers.

3

Answers


  1. Just as you would expect, the crash is related to images.
    I struggled with the same issue you have when I was building an app that uses a lot of images.

    The crash can happen when you are out of memory (a lot of live images) and/or you have many https requests running at the same time (downloading images).

    You can clear the image cache and then do fast scrolling on the grid containing images to reproduce this issue.

    The solution could be:

    Use a lower image resolution, or perhaps resize the image before displaying it.

    Alternatively, if you want to use high-resolution images, you can try to dispose the images manually by calling evict method on the ImageProvider you are using, like in the example below.

        final imageProvider = NetworkImage(bytes);
        imageProvider.evict();
    

    However, you need to make sure that the image is fully loaded before calling evict or it will have no effect.

    Be sure not to download too many images at the same time, this could be tricky. One possibility is to manually download images in a StatefulWidget and then cancel the download in the dispose method. 

    I’ve already developed a package to solve this exact issue disposable_cached_images. Give it a try, and if it solve the issue you can use it or modify the source code to get the desired behaviour.

    Login or Signup to reply.
  2. I’ve had issues scrolling through lists with images too. My issue was when they were local to the device also, but very similar to what you’re describing.

    I fixed my issue by resizing the images in memory, instead of loading it full size, and letting the constraints of the UI render the full size image down to its container.

    CachedNetworkImage has some params to minimize memory impact. Perhaps these would help you (depending on how you prefer to size the image):

    memCacheWidth

    memCacheHeight

    Usage:

    CachedNetworkImage(              
      memCacheWidth: 100,
      imageUrl: venue.venueImageUrl,
      // ...
    )
    
    Login or Signup to reply.
  3. @Marvioso have you opened a github issue? .. In Flutter repo. This looks like something that needs to be known by the Flutter team. It does look like a Flutter or iOS issue.. I’ve started the app and left it as.. without scrolling or navigating and it still runs out of memory.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search