Flutter app crashes a lot when cached_network_image Or Image.Network is populated with data , just showing lost connection to device nothing more
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.
Dyary
Updated on December 14, 2022Comments
-
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.