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

3,577

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.

Share:
3,577
Dyary
Author by

Dyary

Updated on December 14, 2022

Comments

  • Dyary
    Dyary over 1 year

    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.