Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create infinite list with Cloud Firestore in flutter

I am currently using Cloud Firestore with the Streambuilder widget in order to populate a ListView widget with Firestore documents.

new StreamBuilder<QuerySnapshot>(
  stream: Firestore.instance.collection('videos').limit(10).snapshots(),
  builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
    if (!snapshot.hasData) return new Center(
      child: new CircularProgressIndicator(),
    );
    return new ListView(
      children: snapshot.data.documents.map((DocumentSnapshot document) {
        new Card(child: ...)
      }).toList(),
    );
  },
);

This setup however only allows for the querying of the first x results (in this case x=10), with x being a fixed number that will sooner or later be exceeded by the number of Card widgets the user wants to see as he or she scrolls down.

Would it now be possible to query the first x results, and after the user hits a scroll threshold to query the next x+10 results from Cloud Firestore and so on?
This would allow for a dynamic list length which would also benefit the Firestore data consumption.

like image 223
stefanmuke Avatar asked May 27 '18 18:05

stefanmuke


1 Answers

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!

like image 177
Shyju M Avatar answered Sep 18 '22 15:09

Shyju M