Infinite List in Flutter Application

The easiest way is to use a ListView.builder without specifying the itemCount parameter.

Here is the simplest example:

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Infinite List"),
      ),
      body: ListView.builder(
        itemBuilder: (context, index) {
          return Text("$index");
        },
      ),
    );
  }
}

Later, you can enhance this by fetching real data. You could show a 'CircularProgressIndicator' in the last item of the list while waiting for the new data.

  body: ListView.builder(
    itemBuilder: (context, index) {
      if (index < data.length) {
        // Show your info
        return Text("$index");
      } else {
        getMoreData();
        return Center(child: CircularProgressIndicator());
      }
    },
    itemCount: data.length + 1,
  ),

You can see that we trick the list by adding an index, and calling for more data when displaying that final index.

getMoreData() would include a call to setState() to force a rebuild and to take into account the new data.


Below is a simple infinite list widget based on chemamolins's answer. It accepts an itemBuilder to build the current item and onRequest callback to request more data when the user scrolls to the bottom.

import 'package:flutter/material.dart';

typedef Future<List<T>> RequestFn<T>(int nextIndex);
typedef Widget ItemBuilder<T>(BuildContext context, T item, int index);

class InifiniteList<T> extends StatefulWidget {
  final RequestFn<T> onRequest;
  final ItemBuilder<T> itemBuilder;

  const InifiniteList(
      {Key? key, required this.onRequest, required this.itemBuilder})
      : super(key: key);

  @override
  _InifiniteListState<T> createState() => _InifiniteListState<T>();
}

class _InifiniteListState<T> extends State<InifiniteList<T>> {
  List<T> items = [];
  bool end = false;

  _getMoreItems() async {
    final moreItems = await widget.onRequest(items.length);
    if (!mounted) return;

    if (moreItems.isEmpty) {
      setState(() => end = true);
      return;
    }
    setState(() => items = [...items, ...moreItems]);
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (context, index) {
        if (index < items.length) {
          return widget.itemBuilder(context, items[index], index);
        } else if (index == items.length && end) {
          return const Center(child: Text('End of list'));
        } else {
          _getMoreItems();
          return const SizedBox(
            height: 80,
            child: Center(child: CircularProgressIndicator()),
          );
        }
      },
      itemCount: items.length + 1,
    );
  }
}

Usage

child: InifiniteList<String>(
  onRequest: requestItems,
  itemBuilder: (context, item, index) => Container(
    padding: const EdgeInsets.all(30),
    color: index % 2 == 0 ? Colors.purple.shade100 : Colors.lime.shade100,
    child: Text(item, style: Theme.of(context).textTheme.headline6),
  ),
),
// normally this is the place where you request the next batch of items
// on the network.
Future<List<String>> requestItems(int nextIndex) {
  const pageSize = 15;
  var result = List<String>.generate(pageSize, (i) => "Item: ${nextIndex + i + 1}");
  return Future<List<String>>.delayed(
    const Duration(milliseconds: 500),
    () => result,
  );
}

Live Demo