Skip to main content
Flutter Development

Pagination in NestedScrollView using ScrollNotification in Flutter

By January 19, 2022October 31st, 20232 Comments

Pagination is an integral part of almost every app, it lets us load data in small chunks, hence not waiting an eternity for whole data to be loaded at once.

The conventional way of doing it in Flutter is by using a scroll controller; you add a listener to the scroll controller and as soon as you reach the bottom of the screen, more data is loaded and you continue scrolling till the next chunk of data is fetched, and so the cycle continues.

Problem in NestedScrollView

But what if you want a sliver effect too in your screen as well as paginate your data. Well we all know that one of the ways to achieve this is NestedScrollView.
Let’s take a look at an example app which has the above mentioned features in it.

List<int>_list=[1,2,3,4,5,6,7,8,9,10];
Widget build(BuildContext context) {
   return Scaffold(
     body: SafeArea(
       child: NestedScrollView(
         controller: scrollController,
         headerSliverBuilder: (context,val)=>[
           SliverAppBar(
             centerTitle: true,
             leading: FlutterLogo(),
             title: Text("Flutter ScrollNotification"),
           ),
           SliverPersistentHeader(delegate: Delegate(),pinned: true,)
         ],
         body: SingleChildScrollView(
           padding: EdgeInsets.symmetric(horizontal: 10),
           child: Column(
             children:List.generate(_list.length, (index) => Padding(
               padding: EdgeInsets.symmetric(vertical: 10),
               child: Material(
                 elevation: 4,
                 child: ListTile(
                   title: Text(index.toString()),
                 ),
               ),
             )),
           ),
         ),
       ),
     ),
   );
 }
class Delegate extends SliverPersistentHeaderDelegate {
 @override
 double get minExtent => 100;
 @override
 double get maxExtent => 250;
@override
 Widget build(
     BuildContext context, double shrinkOffset, bool overlapsContent) {
   return Container(
     color: Colors.purpleAccent[400],
     child: FlutterLogo(
       size: 250,
     ),
   );
 }
@override
 bool shouldRebuild(Delegate oldDelegate) {
   return false;
 }
}

We add a listener to scrollController which is assigned to NestedScrollView, to paginate our data the conventional way.

ScrollController scrollController;
initState(){
   super.initState();
   scrollController=ScrollController();
   scrollController.addListener(() {
      if (scrollController.offset >=
               scrollController.position.maxScrollExtent &&
           !scrollController.position.outOfRange){
             final tempList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
             setState(()=>_list.addAll(tempList));
           }
   });
 }

Each time we scroll to the bottom of the screen the listener should append our list and our scrolling should continue. Let’s have a look at our app.

As you can see above, pagination works perfectly fine the first time, but after that our pagination stops working, and we have to scroll a little bit up and then again scroll down for it to work.

This happens because once the outer scrollable has reached its full extent, then the inner scrollable will scroll. The outer scrollable is no longer scrolling, so it no longer receives notifications.

And if we assign a scrollController to the inner singleChildScrollView, it will break the link with NestedScrollView and we won’t have the sliver effect.

Solution

To overcome this nightmare, instead of using scrollController we use NotificationListener on inner SingleChildScrollView, as we know that scrollable widgets dispatch scrollNotifications!

We can now use a NotificationListener of type ScrollNotification to listen to notification updates of our scrolling, and as soon as we reach the bottom of the screen we can append the list, as follows:

NotificationListener<ScrollNotification>(
            onNotification: (notification) {
             if (notification is ScrollEndNotification) {
               if (notification.metrics.pixels ==
                   notification.metrics.maxScrollExtent) {
                 final tempList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
                 setState(() => _list.addAll(tempList));
                 return true;
               }
             }
             return true;
           },
           child: SingleChildScrollView(
             padding: EdgeInsets.symmetric(horizontal: 10),
             child: Column(
               children: List.generate(
                   _list.length,
                   (index) => Padding(
                         padding: EdgeInsets.symmetric(vertical: 10),
                         child: Material(
                           elevation: 4,
                           child: ListTile(
                             title: Text(index.toString()),
                           ),
                         ),
                       )),
             ),
           ),
         )

Here we go, everything is working just perfectly. To learn more about NotificationListeners head to officials docs.

You can view the complete source code here 

2 Comments

Leave a Reply