Flutter app crashes a lot when cached_network_image Or Image.Network is populated with data , just showing lost connection to device nothing more

Viewed 4930

I have build a flutter app that is complex enough, Every thing was working fine until I started getting images from an api that are located on AWS. But after populating the image.network widget and cached_network_image with actual data I started getting a lot of crashes randomly and more when navigating to other pages with images in them.

Flutter doesn't show me any errors only "lost connection to device".

I am testing this app on both android an iOS device , It's the same : a lot of crashes.

the images are of size of about 200-400 KB each, but the crashes happen even when I display 6 of them on the screen.

At first I wasn't didn't know that the crashes where caused by images so I tried a lot of methods and changed the code a lot. like making most of my widgets stateless, tried changing the Cached_Network_Image to Image.Network widgets, made widgets smaller so that the rebuild doesn't take a lot of memory when In set state. I also tried using devTools to diagnose the problem to know veil.

devTools only shows memory surge before the app crashes.

I am now certain the the images are the cause for these crashes.

here is the code in main.js not including imports , though I can gladly provide if needed.

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await allTranslations.init();
  User user = await getLocalUserObject();

  runApp(Bestiee(user));
}

class Bestiee extends StatefulWidget {
  User user;

  Bestiee(this.user);

  @override
  _BestieeState createState() => _BestieeState();
}

class _BestieeState extends State<Bestiee> {
  SpecificLocalizationDelegate _localeOverrideDelegate;
  String currentLocal = "en";
  Widget startScreen;
  GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();

  @override
  void initState() {

    bypassLogin();

    _localeOverrideDelegate = new SpecificLocalizationDelegate(null);
    applic.onLocaleChanged = onLocaleChange;

    NotificationHandler.scaffoldKey = scaffoldKey;
    new NotificationHandler().initializeFcmNotification();

    super.initState();

//    allTranslations.onLocaleChangedCallback = _onLocaleChanged;
  }

  @override
  Widget build(BuildContext context) {
    print('-------------------------------------------');
    print('main is called');
    return MaterialApp(
      localizationsDelegates: [
        _localeOverrideDelegate,
        const TranslationsDelegate(),
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
//        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: applic.supportedLocales(),
//      locale: Locale("en", "UK"),
//      locale: Locale("fa", "IR"),
//      locale : Locale(allTranslations.currentLanguage),
      locale: Locale(currentLocal),

//      home: WelcomScreen(changeLanguageCallBack: changeLanguageCallBack,),

      home: Scaffold(
        key: scaffoldKey,
        body: QuickActionsManager(
          child: startScreen,
        ),
      ),
      routes: {
        ItemSearchResultScreen.id: (context) =>
            ItemSearchResultScreen(screenName: 'Items'),
        ItemSearchResultScreen.usedItemsId: (context) =>
            ItemSearchResultScreen(screenName: 'Used Items'),
        PlacesSearchResultScreen.id: (context) => 


}

... 20 More other routes

HotScreen.dart in which most of the crashes happen when navigating from and to other routes :

class HotScreen extends StatefulWidget {
  static List<Category> categories = [];
  static List<Subcategory> subcategories = [];
  static User user = User();
  static Location userLocation = Location();

  static List<Item> items ;
  static List<Item> usedItems;
  static List<Place> places;
  static List<Person> people ;



  @override
  _HotScreenState createState() => _HotScreenState();
}

class _HotScreenState extends State<HotScreen> {


  @override
  void initState() {

    HotScreen.items = [];
    HotScreen.usedItems = [];
    HotScreen.places = [];
    HotScreen.people = [];

    getItemPlacesPeople();
    getAllCategories();
    getAllSubcategories();

    super.initState();
  }

  @override
  Widget build(BuildContext context) {

  print('hot screen called');

    SingleItemScreen.isScreenCalledFromAddUsedItemScreen = false;

    return Scaffold(
      body: ModalProgressHUD(
        inAsyncCall: false,
        child: Container(
          decoration: kPageMainBackgroundColorBoxDecoration,
          child: SafeArea(
            child: Padding(
              padding: const EdgeInsets.all(8.0),

              //main screen scrollable widgets
              child: ListView(
                shrinkWrap: true,
                children: <Widget>[
                  Text(
                    getTranslation('Bazzar24', context),
                    style: kBazarGalleryTitleStyle,
                  ),
                  FeaturedItems(isScreenCalledFromMyPropertiesScreen: false),
                  FeaturedUsedItems(),
                  FeaturedIPlaces(isCalledFromMyPropertiesScreen: false),
                  FeaturedPeople(isCalledFromMyPropertiesScreen: false),
//                  NewPlaces(
//                    places: places,
//                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }

  getAllCategories() async {
    List<Category> _categories = [];

    http.Response response = await getRequest(baseURL + plainCategoryAPI);
    var allCategoriesMap = jsonDecode(response.body);

    for (int i = 0; i < allCategoriesMap.length; i++) {
      var json = allCategoriesMap[i];
      Category category = Category.fromJson(json);
      category.id = allCategoriesMap[i]['_id'];

      //NOTE here I have used SueperCategory instead of camelCase superCategory because the data is already saved in this way
      category.superCategory = allCategoriesMap[i]['SuperCategory'];

      _categories.add(category);
    }

    HotScreen.categories.clear();
    HotScreen.categories.addAll(_categories);

    allCategoriesMap.clear();
  }

  getAllSubcategories() async {
    List<Subcategory> _subcategories = [];

    http.Response response = await getRequest(baseURL + plainSubcategoryAPI);
    var allSubcategoriesMap = jsonDecode(response.body);

    for (int i = 0; i < allSubcategoriesMap.length; i++) {
      var json = allSubcategoriesMap[i];
      Subcategory subcategory = Subcategory.fromJson(json);
      subcategory.id = allSubcategoriesMap[i]['_id'];

      _subcategories.add(subcategory);
    }

    HotScreen.subcategories.clear();
    HotScreen.subcategories.addAll(_subcategories);

    allSubcategoriesMap.clear();
  }

  getItemPlacesPeople() async {
    await getAllItems(setItemsAndUsedItemsStateCallback);
    await getAllPlaces(setPlaceStateCallback);
   await getAllPeople(setPeopleStateCallback);
  }

  setItemsAndUsedItemsStateCallback(List<List<Item>> items){

    if(this.mounted){
      setState(() {
        HotScreen.items.clear();
        HotScreen.usedItems.clear();

        HotScreen.items.addAll(items[0]);
        HotScreen.usedItems.addAll(items[1]);

      });
    }

  }

  setPlaceStateCallback(List<Place> places){

    if(this.mounted){
      setState(() {
        HotScreen.places.clear();
        HotScreen.places.addAll(places);
      });
    }

  }

  setPeopleStateCallback(List<Person> people){

    if(this.mounted){
      setState(() {
        HotScreen.people.clear();
        HotScreen.people.addAll(people);
      });
    }

  }

}

sample widgets inside HotScreen:

FeaturedItems.dart :



class FeaturedItems extends StatelessWidget {
  FeaturedItems({this.isScreenCalledFromMyPropertiesScreen});

  final bool isScreenCalledFromMyPropertiesScreen;
  final List<Item> items = HotScreen.items;
  final myUsedItems = MyPlacesJobsItems.items;

  @override
  Widget build(BuildContext context) {

    print('featured items widget called');

    List<Item> _items;
    if (isScreenCalledFromMyPropertiesScreen == true) {
      _items = items;
    } else {
      _items = myUsedItems;
    }

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        SizedBox(
          height: 20,
        ),
        Align(
          alignment: Alignment.topLeft,
          child: Padding(
            padding: const EdgeInsets.only(bottom: 10),
            child: Text(
                isScreenCalledFromMyPropertiesScreen
                    ? 'My Items'
                    : 'Featured Items',
                style: kFeatureTitleTextStyle),
          ),
        ),
        Container(
          height: 700 / 3.5,
          child: ListView.builder(
            scrollDirection: Axis.horizontal,
            shrinkWrap: true,
            itemCount: items.length < 10 ? HotScreen.items.length : 10,
            itemBuilder: (contet, int index) {
              return SingleItemCard(
                item: _items.length > 10
                    ? HotScreen.items[_items.length - 10 + index]
                    : HotScreen.items[index],
                isPersonItem: false,
                moduleName: _items.length > 10
                    ? HotScreen.items[_items.length - 10 + index].moduleName
                    : HotScreen.items[index].moduleName,
                isScreenCalledFromMyPropertiesScreen:
                    isScreenCalledFromMyPropertiesScreen,
              );
            },
          ),
        ),

        SizedBox(
          height: 10,
        ),

        //more button
        Visibility(
          visible: !isScreenCalledFromMyPropertiesScreen,
          child: Align(
            alignment: Alignment.topLeft,
            child: Container(
              width: 80,
              height: 30,
              child: SmallRoundMoreButton(onPressed: () {
                Navigator.pushNamed(context, MoreHotItemsScreen.id);
              }),
            ),
          ),
        ),

        SizedBox(
          height: 20,
        ),
      ],
    );
  }
}

And a sample page that causes random crashes when going back and forth from the HotScreen.dart .

here is SinglePlaceScreen.dart :


class SinglePlaceScreen extends StatelessWidget {
  SinglePlaceScreen(
      {this.place, this.isScreenCalledFromOwnerSelfRegistrationScreen = false});

  static const id = 'singlePlaceScreen';

  final Place place;
  final bool isScreenCalledFromOwnerSelfRegistrationScreen;
  final PageController pageController = PageController(initialPage: 2);
  final int activePageInt = 0;

  final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();

  double getSocialMediaScreenSize() {
    SocialMedia socialMedia = place.socialMedia;
    double requiredScreenSpace = 0;

    if (socialMedia.facebook != '') requiredScreenSpace += 140;
    if (socialMedia.instagram != '') requiredScreenSpace += 140;
    if (socialMedia.twitter != '') requiredScreenSpace += 140;
    if (socialMedia.googlePlus != '') requiredScreenSpace += 140;
    if (socialMedia.pinterest != '') requiredScreenSpace += 140;
    if (socialMedia.youTube != '') requiredScreenSpace += 140;
    if (socialMedia.snapChat != '') requiredScreenSpace += 140;
    return requiredScreenSpace;
  }

  @override
  Widget build(BuildContext context) {
    double screenWith = MediaQuery.of(context).size.width - 30;

    print('single place screen called');
    return Scaffold(
      key: scaffoldKey,
      appBar: AppBar(
        title: Text(place.name),
      ),
      body: Container(
        decoration: kPageMainBackgroundColorBoxDecoration,
        child: ListView(
          shrinkWrap: true,
          children: <Widget>[
            //column for upper button and image sections and lower comments sections
            Column(
              children: <Widget>[
                //stack for the top image components and the middle buttons component
                Stack(
                  alignment: Alignment.topCenter,
                  children: <Widget>[
                    TopWidgets(
                        place,
                        isScreenCalledFromOwnerSelfRegistrationScreen,
                        getSocialMediaScreenSize,
                        scaffoldKey),

                    //middle buttons section
                    Positioned(
                      top: 230,
                      child: Container(
                        decoration: kAppSingleItemScreenMainCardsBoxDecoration,
                        width: screenWith,
                        height: 1000 +
                            ((place.description.length / 100) * 30) +
                            (place.tags.length * 5) +
                            getSocialMediaScreenSize(),
                        child: Column(
                          children: <Widget>[
                            ItemNameCircleRaterWidget(
                              name: place.name,
                              isScreenCalledFromOwnerSelfRegistrationScreen:
                                  isScreenCalledFromOwnerSelfRegistrationScreen,
                            ),
                            //tags header
                            TagsWidget(
                              tags: place.tags,
                            ),
                            //descriptions header

                            DescriptionWidget(
                              description: place.address,
                              headerText: 'Adress',
                            ),

                            DescriptionWidget(
                              headerText: 'Description',
                              description: place.description,
                            ),

                            //phone number buttons
                            FittedBox(
                              child: Container(
                                height: 130,
                                child: Row(
                                  children: <Widget>[
                                    PhoneNumberNumberWidgets(
                                      phoneNumbers: place.phoneNumbers,
                                    ),
                                    PhoneNumberOwnerWidgets(
                                      ownerOne: place.phoneNumbers[0].owner,
                                      ownerTow: place.phoneNumbers[1].owner,
                                    ),
                                  ],
                                ),
                              ),
                            ),
//                            Container(
//                              height: 300,
//                              child: MyGoogleMaps(place.location, place.name, isScreenCalledFromOwnerSelfRegistrationScreen),
//                            ),
                            ServicesWidget(
                              scaffoldKey: scaffoldKey,
                              services: place.services,
                            ),

                            //social media section
                            SocialMediaWidgets(
                              place: place,
                            ),
                          ],
                        ),
                      ),
                    )
                  ],
                ),

                //top tow albums Sections
                Albums(
                  place: place,
                ),
                //lower comments section
                Comments(
                  place: place,
                ),

                SizedBox(
                  height: 30,
                ),

                // done button
                Visibility(
                  visible: place != null,
                  child: DoneButton(
                    onPressed: () {
                      Navigator.pushAndRemoveUntil(
                          context,
                          MaterialPageRoute(builder: (context) => HomeScreen()),
                          (Route<dynamic> r) => false);
                    },
                  ),
                )
              ],
            )
          ],
        ),
      ),
    );
  }
}


I would expect the app not to crash since I am not using that many Images ( 6 images of 200 KB's ).

When it crashes only "lost connection to device" is shown as an error;

Any help is appreciated guys. Thanks.

3 Answers

For long image lists or grids, I used Image.network with a reasonable cacheHeight and cacheWidth parameter (around 200 +/-). That fixed the memory increase problem for me. Otherwise I have not found a solution to this using cachedimage. Even the simplest cachednetworkimage sample code from the tutorial doesn't work if we provide it with a list of even 10 odd elements if the images are large (large a.k.a. jpgs with 2 odd Mb each). Immediately memory goes to 3-4-500Mb and then we get out of memory error.

But another issue is, Image.network does not have good error handling.

The images could just be a manifestation of an underlying issue. You stated that you have over 20 routes. How are you managing your state when you fetch data. Apart from the images, it seems like your still using the network to fetch other data which may result in high resource usage. 6 images of 200-400KB is considerably small. Since you had the issue with both image.asset and the CacheNetworkImage widgets, I suspect the issue is you are not caching the data you had fetched. Possibly, every time you scroll back up, you find blank spaces which results in fetching the resources again. The Listview.builder builds on demand.

You can use cached_network_image. Set

memCacheHeight and memCacheWidth

attributes of CachedNetworkImage. This worked for loading the large number of images.

Related