'Flutter - Infinite Scroll & Images causes App Crash

We are building a product listing page in flutter and trying to load products as the user scrolls and reaches the end of the page. It's basically a grid view builder with network images. When i run the app using the profile mode, the app crashes once we have around 1300 images in the screen and if we run the app using the release mode, the app crashes once we have around 5000 images in the screen. Shouldn't the flutter GC kick in so that the images that are not in view is no longer held in memory?

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Infinite Scroll Test',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Infinite Scroll Crash Sample'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  ScrollController scrollController;
  bool autoReloadInProgress = false;
  int noOfElements = 50;
  String baseUrl = "https://cdn.shopify.com/s/files/1/0286/0344/9475/products/";
  
  // short list, actual list has thousands of image urls
  List<String> images = [
    "imagejpg_1024x1024_2x_032a1436-7cb4-42de-9289-ea58b510d750.jpg?v=1579293459",
    "Raven_Brow_Balm_1024x_fa88c686-8c89-4647-babb-440ef8292d5b.png?v=1577445154",
    "BioTuff_GeneralUseBinLinersMed25_8_1024_1024.jpg?v=1587709003",
    "34eyeshadow_neutralnude_1024x1024_2x_08d10c39-1c3f-431c-b03b-436f2ff55cfa.png?v=1579411298",
    "Ballet_1024x1024_a61af758-0757-454e-938f-96065d0c5e40.jpg?v=1581403220",
    "pure-organic-beeswax-tealight-refills.png?v=1586942441",
    "Holding_Cherry_Red_2000x2000_d6ce67a2-e740-4a99-b599-86196146400b.png?v=1577858762",
    "WCP-FNS1-004_1024x1024_2x_43f2e247-eecb-4a56-89c6-c3ecc8e3031e.jpg?v=1579295821",
    "BabyWipes20sfront2D_1024x_01ad5c64-1ff6-402c-9995-5b7489555029.jpg?v=1587698137",
    "imageedit_214_7359687840_1.png?v=1577437619",
    "Organic_Chocolate_Maple_Pecan_1800x_10844ae0-d26c-4946-ab8b-d8c8d166b37b.png?v=1577863201",
    "FACIAL_POLISH_1_1000x_87f987c5-95da-4fe9-9a83-0f8a3d4cd74a.jpg?v=1581373634",
    ];
  @override
  void initState() {
    super.initState();
    scrollController = new ScrollController()..addListener(_scrollListener);
  }

  @override
  void dispose() {
    scrollController.removeListener(_scrollListener);
    super.dispose();
  }

  void _scrollListener() async{
    if(scrollController.position.extentAfter < 800) {
      setState(() {
        noOfElements += 50;
      });
    }
  }

  getProductGrid(){

    double deviceWidth = MediaQuery.of(context).size.width;
    double aspectRatio = 0.64;
    double cardWidth = deviceWidth < 200.0 ? deviceWidth : (deviceWidth <= 375 ? 180 : 200);
    double cellHeight = cardWidth / aspectRatio;
    int cardCount = (deviceWidth/cardWidth).toInt();
    double extraWidth = deviceWidth - (cardWidth * cardCount);
    return GridView.builder(
      itemCount: noOfElements,
      padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
      primary: false,
      shrinkWrap: true,
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: cardCount, crossAxisSpacing: extraWidth / (cardCount-1),
          childAspectRatio: aspectRatio, mainAxisSpacing: 20
      ),
      itemBuilder: (BuildContext context, int index){
        return Container(
          //height: 200,
          //width: 200.0,
          color: Theme.of(context).accentColor,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text((index+1).toString(), textAlign: TextAlign.center, style: TextStyle(color: Colors.white),),
              Image.network(baseUrl + images[index%images.length]),
            ],
          )
        );
      },
    );

  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Scrollbar(
          child:ListView(
            controller: scrollController,
            padding: EdgeInsets.all(10),
            children: <Widget>[
              Column(
                children: <Widget>[
                  getProductGrid(),
                ],
              ),
            ],
          )
      )
    );
  }
}

Have added the link to the sample code as it has lot of image source and it doesn't fit the character limitations of post. Any insight into the issue?

https://drive.google.com/file/d/1vHqawSw18HSWP-YV_39_fFfo_IUGgQPv/view?usp=sharing

Update - Working code

Scrollbar(
        child:CustomScrollView(
          controller: scrollController,
          physics: AlwaysScrollableScrollPhysics(),
          slivers: [
            SliverList(
             delegate: new SliverChildListDelegate([
                  //widgets go here
             ])
            )
          ]
       )
);


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source