mydomain
No ADS
No ADS

FlutterArtist ListView Infinite Scroll Pagination Example

  1. Structure of the example
  2. Country48aShelf
  3. Country48aListItemsView
  4. Using Sentinel Widget
Infinite Scroll pagination is a application design technique where content loads dynamically as the user scrolls down a page, rather than being divided into discrete pages like traditional pagination. Instead of clicking through page numbers, users experience a continuous stream of content without interruptions. This method is often used on applications with large amounts of media-heavy content or where the goal is to encourage users to explore continuously, like social media platforms.
Example scenario: Demo48a
This example displays a list of Countries in a ListView. When the user scrolls to the last element, the system automatically fetches more data and appends it to the current list.
No ADS
Các ví dụ liên quan:
  • FlutterArtist Grid ItemsView Infinite Scroll Example
FlutterArtist Demo:
  • Download FlutterArtist Demo
  • Flutter ListView tutorial with examples

1. Structure of the example

2. Country48aShelf

The structure of Country48aShelf is straightforward, consisting of a single Block. Note the pageable parameter in BlockConfig, which allows you to set the maximum number of ITEM(s) to be loaded per request.
country48a_shelf.dart
class Country48aShelf extends Shelf {
  @override
  ShelfStructure defineShelfStructure() {
    return ShelfStructure(
      filterModels: {},
      blocks: [
        Country48aBlock(
          name: Country48aBlock.blkName,
          description: null,
          config: BlockConfig(
            pageable: Pageable(pageSize: 20),
          ),
          filterModelName: null,
          formModel: null,
          childBlocks: [],
        ),
      ],
    );
  }

  Country48aBlock findCountry48aBlock() {
    return findBlock(Country48aBlock.blkName) as Country48aBlock;
  }
}

3. Country48aListItemsView

The key to this technique lies in using NotificationListener to capture the ScrollEndNotification. When the user finishes scrolling and the scroll position reaches the bottom, we trigger the data loading process.
NotificationListener<ScrollEndNotification>(
  onNotification: _onScrollEndNotification,
  child: ListView(
    padding: const EdgeInsets.fromLTRB(0, 0, 8, 0),
    children: block.items
        .map(
          (item) => Country48aListItem(
            countryInfo: item,
            selected: block.isCurrentItem(item),
            onCountryPressed: _onCountry48aPressed,
          ),
        )
        .toList(),
  ),
)
No ADS
This function checks if the user has reached the end of the list and if there are more pages available to fetch.
_onScrollEndNotification()
bool _onScrollEndNotification(ScrollEndNotification notification) {
  PaginationInfo? pagination = block.paginationInfo;
  if (pagination == null) {
    return false;
  }
  if (notification.metrics.pixels == notification.metrics.maxScrollExtent &&
      pagination.currentPage < pagination.totalPages) { 
    _loadMore();
    return true;
  }
  return false;
}
Utilizing the queryMore() method, which automatically increments the currentPage and merges the new results into the Block's current list.
_loadMore()
Future<void> _loadMore() async {
  await block.queryMore();
}
Full code:
country48a_list_items_view.dart
class Country48aListItemsView extends BlockItemsView<Country48aBlock> {
  const Country48aListItemsView({required super.block, super.key});

  @override
  Widget buildContent(BuildContext context) {
    return NotificationListener<ScrollEndNotification>(
      onNotification: _onScrollEndNotification,
      child: ListView(
        padding: const EdgeInsets.fromLTRB(0, 0, 8, 0),
        children: block.items
            .map(
              (item) => Country48aListItem(
                countryInfo: item,
                selected: block.isCurrentItem(item),
                onCountryPressed: _onCountry48aPressed,
              ),
            )
            .toList(),
      ),
    );
  }

  bool _onScrollEndNotification(ScrollEndNotification notification) {
    PaginationInfo? pagination = block.paginationInfo;
    if (pagination == null) {
      return false;
    }
    if (notification.metrics.pixels == notification.metrics.maxScrollExtent &&
        pagination.currentPage < pagination.totalPages) {
      _loadMore();
      return true;
    }
    return false;
  }

  Future<void> _loadMore() async {
    await block.queryMore();
  }

  void _onCountry48aPressed(CountryInfo countryInfo) {
    // Write Log:
    FlutterArtist.codeFlowLogger.addMethodCall(
      ownerClassInstance: this,
      currentStackTrace: StackTrace.current,
      parameters: null,
    );
    block.refreshItemAndSetAsCurrent(item: countryInfo, navigate: null);
  }
}

4. Using Sentinel Widget

Besides listening to scroll events via NotificationListener, there is another more elegant and modern technique: the Sentinel Trigger. Instead of calculating scroll coordinates, we place a special Widget at the end of the list. When this Widget appears in the ViewPort, it automatically triggers the data loading command.
Implementation Concept:
We utilize ListView.builder and append a virtual element to the end of the list as long as there is more data to fetch.
ListView.builder
ListView.builder(
  itemCount: items.length + (hasMore ? 1 : 0),
  itemBuilder: (context, index) {
    if (index < items.length) {
      return CountryListItem(countryInfo: items[index]);
    }
    // Sentinel Widget
    return _buildSentinelTrigger();
  },
)
_buildSentinelTrigger()
Widget _buildSentinelTrigger() {
  return Builder(
    builder: (context) {
      // Automatically trigger the query when this widget is rendered.
      Future.microtask(() => block.queryMore());
      
      // Display a custom loading indicator here.
      return const CustomLoadingIndicator();
    },
  );
}
Demo48b
This technique keeps your source code extremely clean and maintainable as Flutter automatically manages when to fire the "trigger."
The complete source code for this modern approach is available in the Demo48b example.
No ADS
No ADS