2

I have implemented a pagination scheme in my flutter app but im not sure if its performance friendly and if it may result to trouble on production in future so i would like to get advice on it. here is my implementation

First, i get data using a stream provider in my parent widget.


class BuyerSellerPostsPage extends StatefulWidget {

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

class _BuyerSellerPostsPageState extends State<BuyerSellerPostsPage> {

   ...some code here...

   bool isAtBottom = false;
   int postToDisplay=10;

   @override
   void initState() {
     super.initState();
     // Setup the listener.
     _scrollController.addListener(() {
       if (_scrollController.position.atEdge) {
         if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
          setState(() {
            isAtBottom=true;
            postToDisplay+=10;
            print('now true');
          });
         }else{
           setState(() {
             print('now false');
            isAtBottom=false;
           });
         }
       }
     });
   }
  @override
  void dispose(){
     _scrollController.dispose();
     super.dispose();
  }

...some code here..

  @override
  Widget build(BuildContext context) {

    Position coordinates=Provider.of<Position>(context);

...some code here...

        body: SingleChildScrollView(
          controller: _scrollController,
            child: StreamProvider<List<SellerPost>>.value(
              value: SellerDatabaseService(
                  currentLocation: new GeoPoint(coordinates.latitude, coordinates.longitude,)
                  ,filters: _setFilters,
                  selectedCategory: _selectedCategory,
                selectedTag: _selectedTag,
                postsToDisplay: postToDisplay
              ).inRangeSellerPosts ,

                  ...some code here...

                                  );
                                }else{
                                  return Container(
                                  );
                                }
                              },
                            ),
                          ),
                          //post list
                          BSellerPostList(),

                       ...some code here...



  }
}

The initial posts to display are 10. In my initstate i have used a listener to my scroll controller so that when the user scrolls to the bottom more items(+10) are loaded on screen.

In my stream provider i pass the postsToDisplay int to my stream in the backend below

  Stream <List<SellerPost>> get inRangeSellerPosts {

    try {
      return sellerPostCollection
          .where("expireTime" , isGreaterThan: DateTime.now())
          .orderBy('expireTime',descending: true)
          .snapshots()
          .map(yieldSellerPosts);
    } catch (e) {
      print(e.toString());
      return null;
    }
  }
 
  List<SellerPost> yieldSellerPosts(QuerySnapshot snapshot) {
    List<String> l = [];
    print(snapshot.documents.length);
    try {
      return snapshot.documents.map((doc) {
        return SellerPost(
         ...some code here...
        );
      }).take(postsToDisplay)
          .toList();
    } catch (e) {
      print(e.toString());
      return null;
    }
  }

I now get the snapshots and use the take method on the list to get only the required number(postsToDisplay). This method works fine in my debug mode. Im not sure how it will behave in production or with large data sets. Could someone scrutinise it, i would appreciate alot.

Bright
  • 707
  • 2
  • 10
  • 22
  • I'm getting up to speed on StreamProvider and pagination would led me to your post. I see that you use `take` in `yieldSellerPosts`. If I understand correctly, that would load your first page. How do you load your second page? – buttonsrtoys Feb 05 '21 at 20:46
  • 1
    First off, that method was poor for performance and cost because the pagination was done on the client (Mobile) meaning i have wasted CPU time querying for 100+ docs only to render 10 of them. Anyway, the trick to display more posts was in the `_scrollController` listener where for each event listened to i did `posts+=10` to get the new value to 20 and so on. So again, it was a bad method. – Bright Feb 05 '21 at 21:07
  • Thanks @Bright. So you got something working that you're happy with? If so, would you mind answering your question with a code sample of what your got working? It would help me a lot and anyone else that finds your question. I've been using Flutter for 18 months and paginating streams has been the most challenging problem I've run into. – buttonsrtoys Feb 05 '21 at 22:18
  • 1
    @buttonsrtoys Let me post the final method i used – Bright Feb 05 '21 at 22:34

1 Answers1

0

I personally used a modified version of this answer posted in a previous similar question. Both my implementation and that other guys implementation have the pros and cons. His method leads to listening to document changes for documents you may never need to use considering you may need only 10 paginated items. My method on the other hand does not work along with the stream. It uses a future to query for document snapshots to update the list as you proceed.

Here is the sample code

bool _isRequesting = false;
bool _isFinish = false;
final _scrollController = ScrollController();

List<DocumentSnapshot> _posts = [];
   @override
   void initState() {
     _scrollController.addListener(() {
       if (_scrollController.position.atEdge) {
         if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
           setState(() {
             requestNextPage();
           });
         }
       }
     });
     requestNextPage();
     super.initState();
   }
   void requestNextPage() async {
     try{
     if (!_isRequesting && !_isFinish) {
       QuerySnapshot querySnapshot;
       _isRequesting = true;
       if (_posts.isEmpty) {
           //check if _posts list is empty so that we may render the first list of 10 items.
           querySnapshot = await Firestore.instance
               .collection('sellerPost')
               .limit(10)
               .getDocuments();

       } else {
        //if _posts list is not empty it means we have already rendered the first  10 items so we start querying from where we left off to avoid repetition.
           querySnapshot = await Firestore.instance
               .collection('sellerPost')
               .startAfterDocument(_posts[_posts.length - 1])
               .limit(10)
               .getDocuments();

       }

       if (querySnapshot != null) {
         int oldSize = _posts.length;
         _posts.addAll(querySnapshot.documents);
         int newSize = _posts.length;
         if (oldSize == newSize) {
           _isFinish = true;
         }
       _isRequesting = false;

     }else{
       _isFinish = false;
       _isRequesting = false;
     }
     }catch(e){
       print(e.toString());
     }

   }

So in this above code i used the scroll controller to detect when the user scrolls to the bottom of the page with the paginated items e.g 10 posts. This event triggers my function requestNextPage(); Note that on inititState we also call the requestNextPage(); to render the initial 10 posts. So now, the each time a scroll to bottom is detected, 10 extra posts are added to _posts

Bright
  • 707
  • 2
  • 10
  • 22