Search code examples
firebaseflutterpaginationgoogle-cloud-firestorestream-builder

flutter firestore pagination using streambuilder


hello i am trying to use pagination in my list. and the list data is coming from firebase. i am not sure how to paginate data using stream builder. now i am doing the pagination by calling getdocuments in the didChangeDependencies() function. but after adding new data the list is not updated. if anyone can help then it will be great for me. here is what i am doing now..

 didChangeDependencies() {
    super.didChangeDependencies();

   getProducts();

    _scrollController.addListener(() {
      double maxScroll = _scrollController.position.maxScrollExtent;
      double currentScroll = _scrollController.position.pixels;
      double delta = MediaQuery.of(context).size.height * 0.20;
      if (maxScroll - currentScroll <= delta) {
        getProducts();
      }
    });
  }

  getProducts() async {
    if (!hasMore) {
      return;
    }
    if (isLoading) {
      return;
    }
    setState(() {
      isLoading = true;
    });
    QuerySnapshot querySnapshot;
    if (lastDocument == null) {
      querySnapshot = await firestore
          .collection('products')
          .limit(documentLimit)
          .orderBy('timestamp', descending: true)
          .getDocuments();
    } else {
      querySnapshot = await firestore
          .collection('products')
          .startAfterDocument(lastDocument)
          .limit(documentLimit)
          .orderBy('timestamp', descending: true)
          .getDocuments();
    }
    if (querySnapshot.documents.length < documentLimit) {
      hasMore = false;
    }
    if (querySnapshot.documents.isNotEmpty) {
      lastDocument =
          querySnapshot.documents[querySnapshot.documents.length - 1];
      products.addAll(querySnapshot.documents);
      setState(() {
        isLoading = false;
      });
    }
  }

Solution

  • Take a try with below code:

    class ProductList extends StatefulWidget {
      @override
      _ProductListState createState() => _ProductListState();
    }
    
    class _ProductListState extends State<ProductList> {
      StreamController<List<DocumentSnapshot>> _streamController =
      StreamController<List<DocumentSnapshot>>();
      List<DocumentSnapshot> _products = [];
    
      bool _isRequesting = false;
      bool _isFinish = false;
    
      void onChangeData(List<DocumentChange> documentChanges) {
        var isChange = false;
        documentChanges.forEach((productChange) {
          if (productChange.type == DocumentChangeType.removed) {
            _products.removeWhere((product) {
              return productChange.document.documentID == product.documentID;
            });
            isChange = true;
          } else {
    
            if (productChange.type == DocumentChangeType.modified) {
              int indexWhere = _products.indexWhere((product) {
                return productChange.document.documentID == product.documentID;
              });
    
              if (indexWhere >= 0) {
                _products[indexWhere] = productChange.document;
              }
              isChange = true;
            }
          }
        });
    
        if(isChange) {
          _streamController.add(_products);
        }
      }
    
      @override
      void initState() {
        Firestore.instance
            .collection('products')
            .snapshots()
            .listen((data) => onChangeData(data.documentChanges));
    
        requestNextPage();
        super.initState();
      }
    
      @override
      void dispose() {
        _streamController.close();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
    
        return NotificationListener<ScrollNotification>(
            onNotification: (ScrollNotification scrollInfo) {
              if (scrollInfo.metrics.maxScrollExtent == scrollInfo.metrics.pixels) {
                requestNextPage();
              }
              return true;
            },
            child: StreamBuilder<List<DocumentSnapshot>>(
              stream: _streamController.stream,
              builder: (BuildContext context,
                  AsyncSnapshot<List<DocumentSnapshot>> snapshot) {
                if (snapshot.hasError) return new Text('Error: ${snapshot.error}');
                switch (snapshot.connectionState) {
                  case ConnectionState.waiting:
                    return new Text('Loading...');
                  default:
                    log("Items: " + snapshot.data.length.toString());
                    return ListView.separated(
                      separatorBuilder: (context, index) => Divider(
                        color: Colors.black,
                      ),
                      itemCount: snapshot.data.length,
                      itemBuilder: (context, index) => Padding(
                        padding: const EdgeInsets.symmetric(vertical: 32),
                        child: new ListTile(
                          title: new Text(snapshot.data[index]['name']),
                          subtitle: new Text(snapshot.data[index]['description']),
                        ),
                      ),
                    );
                }
              },
            ));
      }
    
      void requestNextPage() async {
        if (!_isRequesting && !_isFinish) {
          QuerySnapshot querySnapshot;
          _isRequesting = true;
          if (_products.isEmpty) {
            querySnapshot = await Firestore.instance
                .collection('products')
                .orderBy('index')
                .limit(5)
                .getDocuments();
          } else {
            querySnapshot = await Firestore.instance
                .collection('products')
                .orderBy('index')
                .startAfterDocument(_products[_products.length - 1])
                .limit(5)
                .getDocuments();
          }
    
          if (querySnapshot != null) {
            int oldSize = _products.length;
            _products.addAll(querySnapshot.documents);
            int newSize = _products.length;
            if (oldSize != newSize) {
              _streamController.add(_products);
            } else {
              _isFinish = true;
            }
          }
          _isRequesting = false;
        }
      }
    }
    

    JSON for product item:

    {
      "index": 1,
      "name": "Pork",
      "description": "Thịt heo/lợn"
    }
    

    Link Github example: https://github.com/simplesoft-duongdt3/flutter_firestore_paging