Home > Blockchain >  Flutter - Detect if SingleChildScrollView is scrollable yet for endless scroll pagination
Flutter - Detect if SingleChildScrollView is scrollable yet for endless scroll pagination

Time:04-09

I'm trying to implement some endless scroll pagination in a SingleChildScrollView.

I'm setting 2 const values:

///describes how many items are loaded into right away
const int initialWidgetsLoadCount = 3;
///describes how many items are loaded after the initial load
///(when triggered by reaching the end of the SingleChildScrollView)
const int nextWidgetsLoadCount = 1;

For this minimal example I'm just filling up a Column with Text widgets:

SingleChildScrollView(
    controller: _scrollController,
    child: Column(children: _texts),
)

For adding Text widgets I use this method:

void _addTextWidgets(int numberOfWidgets) {
  setState(() {
    for (int i = 0; i < numberOfWidgets; i  ) {
      _texts.add(Text("someText${  counter}"));
    }
  });
}

Now for making the pagination work, in initState() I first add some initial widgets and start listening to the scrollbar reaching the bottom to load more:

@override
void initState() {
  //this is the initial fill request
  _addTextWidgets(initialWidgetsLoadCount);

  _scrollController.addListener(() {
    if (_scrollController.position.maxScrollExtent ==
        _scrollController.offset) {
      //the bottom of the scrollbar is reached
      //adding more widgets
      _addTextWidgets(nextWidgetsLoadCount);
    }
  });
  super.initState();
} 

The problem:

If I set the initialWidgetsLoadCount to a high enough number, the height of the Column will be large enough for the ScrollBar of the SingeChildScrollView to appear. Then everything works fine. However, if I set initialWidgetsLoadCount to a lower number (e.g. 1), the Column height will be too small for the ScrollBar to appear. Therefore the end of the SingeChildScrollView cannot be reached by the user and no more items will be loaded.

I could set the initialWidgetsLoadCount to a high enough number for my device, but there might be a device with a huge resolution.

So I'm tring to figure out a way to tell if the ScrollBar is there or not. And while the ScrollBar is not there yet I can keep loading the initialWidgetsLoadCount of widgets.

Note: The Add-Button in this example is just for testing purposes! In a real environment I want the user to be able to load all items without having to use a FloatingActionButton, even with very small values for initialWidgetsLoadCount.

Nother Note: The size of the list is unknown. I keep fetching data until there is no more data.

Here is a DartPad of this example.

CodePudding user response:

I found a way using the ScrollController. For this solution it does not matter whether you are using ListView.builder or SingeChildScrollView.

I changed the initState() method and put the line that adds the initial widgets after the actual super.initState() call:

  @override
  void initState() {
    _scrollController.addListener(() {
      if (_scrollController.position.maxScrollExtent ==
          _scrollController.offset) {
        //the bottom of the scrollbar is reached
        //adding more widgets
        _addTextWidgets(nextWidgetsLoadCount);
      }
    });
    super.initState();

    //after initState add the initial widgets
    _addTextWidgets(initialWidgetsLoadCount);

    _checkInitialExtent();
  }

In the _checkInitialExtent() I keep adding the initial widgets until the ScrollController tells me that it is scrollable:

void _checkInitialExtent() {
    //after the frame
    WidgetsBinding.instance?.addPostFrameCallback((_) {
      if (_scrollController.hasClients) {
        debugPrint(_scrollController.position.maxScrollExtent.toString());
        //if the list is not scrollable yet
        if (_scrollController.position.maxScrollExtent == 0) {
          //add the initial widgets again
          _addTextWidgets(initialWidgetsLoadCount);
          //recursively check again
          _checkInitialExtent();
        } else {
          //finally enough widgets to make it scrollable (maxScrollExtent > 0 now)
          debugPrint("maxScrollExtent > 0 now");
        }
      }
    });
  }

CodePudding user response:

Any list has the end that you need to know that just calculate it inside the listener. Update your code with my example and test if matches your preferences.

Note: use ListView.builder for longer and larger lists.

/// The max list length.
const len = 200;

/// Decide the initial length.
const int initialWidgetsLoadCount = len < 15 ? len : 15;

/// Decide how many items you want to add.
const int nextWidgetsLoadCount = 15;

@override
  void initState() {
    // This is the initial fill request
    _addTextWidgets(initialWidgetsLoadCount);
    
    _scrollController.addListener(() {
      // And listen to the list length to calculate the next items load length
      if (_scrollController.position.maxScrollExtent ==
          _scrollController.offset) {
        if(counter < len) {
          if((len - counter) < nextWidgetsLoadCount) {
            _addTextWidgets(len - counter);
          } else {
            _addTextWidgets(nextWidgetsLoadCount);
          }
        }
      }
    });
    super.initState();
  }
  • Related