Create infinite list with Cloud Firestore in flutter

That's definitely possible, but there's nothing pre-built in the API.

You'll have to remember the last document on the first page, and then startAfter() with that document to get the second page of documents.

See the documentation on Paginating Data with Query Cursors.


I am not sure whether it is possible or not with Streambuilder. I have integrated the similar functionality in my App using the startAfter method as shown below

class Feed extends StatefulWidget {
  Feed({this.firestore});

  final Firestore firestore;

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

class _FeedState extends State<Feed> {
  ScrollController controller;
  DocumentSnapshot _lastVisible;
  bool _isLoading;
  CollectionReference get homeFeeds => widget.firestore.collection('homefeed');
  List<DocumentSnapshot> _data = new List<DocumentSnapshot>();
  final scaffoldKey = GlobalKey<ScaffoldState>();

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

  Future<Null> _getData() async {
//    await new Future.delayed(new Duration(seconds: 5));
    QuerySnapshot data;
    if (_lastVisible == null)
      data = await widget.firestore
          .collection('homefeed')
          .orderBy('created_at', descending: true)
          .limit(3)
          .getDocuments();
    else
      data = await widget.firestore
          .collection('homefeed')
          .orderBy('created_at', descending: true)
          .startAfter([_lastVisible['created_at']])
          .limit(3)
          .getDocuments();

    if (data != null && data.documents.length > 0) {
      _lastVisible = data.documents[data.documents.length - 1];
      if (mounted) {
        setState(() {
          _isLoading = false;
          _data.addAll(data.documents);
        });
      }
    } else {
      setState(() => _isLoading = false);
      scaffoldKey.currentState?.showSnackBar(
        SnackBar(
          content: Text('No more posts!'),
        ),
      );
    }
    return null;
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      key: scaffoldKey,
      appBar: new AppBar(),
      body: RefreshIndicator(
          child: ListView.builder(
        controller: controller,
        itemCount: _data.length + 1,
        itemBuilder: (_, int index) {
          if (index < _data.length) {
            final DocumentSnapshot document = _data[index];
            return new Container(
              height: 200.0,
              child: new Text(document['question']),
            );
          }
          return Center(
            child: new Opacity(
              opacity: _isLoading ? 1.0 : 0.0,
              child: new SizedBox(
                  width: 32.0,
                  height: 32.0,
                  child: new CircularProgressIndicator()),
            ),
          );
        },
      ),
        onRefresh: ()async{
            _data.clear();
            _lastVisible=null;
            await _getData();
        },
      ),
    );
  }

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

  void _scrollListener() {
    if (!_isLoading) {
      if (controller.position.pixels == controller.position.maxScrollExtent) {
        setState(() => _isLoading = true);
        _getData();
      }
    }
  }
}

Hope it helps!