0

How to apply Lazy Loading or other better option to load more than 5000 of topping list in the Listview.builder inside another Listview.builder?

Below is post.json which will be loaded from local assets

{
    "Food": [
      {
        "name": "Cake",
        "id": "0001",
        "description": [
          {
            "category": "AAA",
            "Size": "Regular",
            "topping": []
          },
          {
            "category": "BBB",
            "Size": "Small",
            "topping": []
          },
          {
            "category": "CCC",
            "Size": "Medium",
            "topping": [
                {
                    "ingredient": "Chocolate with Sprinkles",
                    "price": "$70"
                },
                {
                    "ingredient": "Maple",
                    "price": "$99"
                },
                {
                    "ingredient": "Blueberry",
                    "price": "$123"
                }, ... // more than 5000 of topping list
            ]
          }
        ]
      },
      {
        "name": "Raised",
        "id": "0002",
        "description": ... // same structure as above
      }
    ]
}

Below is the Main file to display List of foods

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

// Load local Json file
Future<Post> getFoodList() => Future.delayed(Duration(seconds: 1), ()  async {
  final getResponse = await rootBundle.loadString('assets/post.json');
  var data = jsonDecode(getResponse);
  return Post.fromJson(data);
});

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Expanded(
            child: FutureBuilder<Post>(
              future: getFoodList(),
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  var getFoodName = snapshot.data!.food;
                  return SingleChildScrollView(
                    child: Wrap(
                      children: [
                        for (final food in getFoodName!)
                          GestureDetector(
                            child: Text(foodName.name.toString()),
                            onTap: () {
                              Navigator.push(context, MaterialPageRoute(builder: (context) => foodDetail(food: food)));
                            },
                          ),
                      ],
                    ),
                  );
                } else {
                  return Text('Loading');
                }
              },
            ),
          ),
        ],
      ),
    );
  }
}

Food Detail page - display more than 5000 of topping list

class FoodDetail extends StatefulWidget {
  final Food food;
  const FoodDetail({super.key, required this.food});

  @override
  State<FoodDetail> createState() => _FoodDetailState();
}

class _FoodDetailState extends State<FoodDetail> {
  late ScrollController controller;
  // This example is taken from Flutter ListView lazy loading
  List<String> items = List.generate(100, (position) => 'Hello $position');

  @override
  void initState() {
    super.initState();
    controller = ScrollController()..addListener(_scrollListener);
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Padding(
            padding: const EdgeInsets.all(8.0),
            child: SingleChildScrollView(
              child: Column(
                children: [
                  // This is the first ListView.build to display category and size under description
                  ListView.builder(
                      itemCount: widget.food.description!.length,
                      itemBuilder: (context, index) {
                        // IF category is AAA || BBB, display the size
                        if (widget.food.description![index].category == "AAA" || widget.food.description![index].category == "BBB") {
                          return Text(widget.food.description![index].size.toString());
                        }
                        // IF category is CCC display the List of Topping (Note: this topping list is more than 5000)
                        else {
                          // This is the second ListView.build to display the topping information
                          // How do I apply ScollController or Lazy Loading to load 5000 list of topping?
                          return ListView.builder(
                              controller: controller,
                              itemCount: widget.food.description![index].topping!.length,
                              itemBuilder: (context, position) {
                                return Column(
                                  children: [
                                   Text(widget.food.description![index].topping![position].ingredient.toString())
                                   Text(widget.food.description![index].topping![position].price.toString())
                                  ],
                                );
                              });
                            }
                          }),
                    ],
                  ),
            )));
  }
  // This example is taken from Flutter ListView lazy loading
  void _scrollListener() {
    if (controller.position.extentAfter < 500) {
      setState(() {
        items.addAll(List.generate(42, (index) => 'Inserted $index'));
      });
    }
  }
}

Output of the workflow: enter image description here

Joven Dev
  • 189
  • 2
  • 14

1 Answers1

0

I think your best bet is to try to try to refactor it in a way to use only a single ListView.builder. To do this you need to flatten the data you are working with in some way. Here is an example that also uses nested data that transforms it in a way so a single ListView displays it. I'm not sure if it's the best way to do it but I think you could do something similar for your code maybe. This might give you an idea of how to do it:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  final data = const [
    {
      "name": "A",
      "subnames": ["A1", "A2", "A3", "A4", "A5"]
    },
    {
      "name": "B",
      "subnames": ["B1", "B2", "B3", "B4", "B5"]
    },
    {
      "name": "C",
      "subnames": ["C1", "C2", "C3", "C4", "C5"]
    },
  ];

  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    final newData = data
        .map((e) => [
              {"type": 1, "name": e["name"]},
              for (final name in (e["subnames"] as List))
                {"type": 2, "name": name}
            ])
        .expand((e) => e.toList())
        .toList();

    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
          body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: ListView.builder(
            itemCount: newData.length,
            itemBuilder: (context, index) {
              if (newData[index]['type'] == 1) {
                return Text(
                  newData[index]['name'],
                  style: const TextStyle(color: Colors.red),
                );
              } else {
                return Text(
                  newData[index]['name'],
                  style: const TextStyle(color: Colors.blue),
                );
              }
            }),
      )),
    );
  }
}

Output:

enter image description here

Ivo
  • 18,659
  • 2
  • 23
  • 35
  • ok i will try on it – Joven Dev Sep 23 '22 at 13:58
  • Hi @Ivo, I have refactor the JSON data to use only a single ListView.builder. Can you have a look at this https://stackoverflow.com/questions/73848938/how-to-appy-lazy-loading-on-the-local-json-data, how to apply lazy load on this – Joven Dev Sep 26 '22 at 02:16