infinite_scroll_pagination 2.0.0 copy "infinite_scroll_pagination: ^2.0.0" to clipboard
infinite_scroll_pagination: ^2.0.0 copied to clipboard

outdated

Load and display pages of items as the user scrolls down your screen.

example/README.md

Cookbook #

All the snippets below were extracted from the example project.

Simple Usage #

class CharacterListView extends StatefulWidget {
  @override
  _CharacterListViewState createState() => _CharacterListViewState();
}

class _CharacterListViewState extends State<CharacterListView> {
  static const _pageSize = 20;

  final PagingController<int, CharacterSummary> _pagingController =
      PagingController(firstPageKey: 0);

  @override
  void initState() {
    _pagingController.addPageRequestListener((pageKey) {
      _fetchPage(pageKey);
    });
    super.initState();
  }

  void _fetchPage(int pageKey) {
    RemoteApi.getCharacterList(pageKey, _pageSize).then((newItems) {
      final isLastPage = newItems.length < _pageSize;
      if (isLastPage) {
        _pagingController.appendLastPage(newItems);
      } else {
        final nextPageKey = pageKey + newItems.length;
        _pagingController.appendPage(newItems, nextPageKey);
      }
    }).catchError((error) {
      _pagingController.error = error;
    });
  }

  @override
  Widget build(BuildContext context) => PagedListView<int, CharacterSummary>(
        pagingController: _pagingController,
        builderDelegate: PagedChildBuilderDelegate<CharacterSummary>(
          itemBuilder: (context, item, index) => CharacterListItem(
            character: item,
          ),
        ),
      );

  @override
  void dispose() {
    _pagingController.dispose();
    super.dispose();
  }
}

Separators #

@override
Widget build(BuildContext context) =>
    PagedListView<int, CharacterSummary>.separated(
      pagingController: _pagingController,
      builderDelegate: PagedChildBuilderDelegate<CharacterSummary>(
        itemBuilder: (context, item, index) => CharacterListItem(
          character: item,
        ),
      ),
      separatorBuilder: (context, index) => const Divider(),
    );

Works for both PagedListView and PagedSliverList.

Preceding/Following Items #

If you need to add preceding or following widgets that are expected to scroll along with your list, such as a header or a footer, you should use our Sliver widgets. Infinite Scroll Pagination comes with PagedSliverList and PagedSliverGrid, which works almost the same as PagedListView or PagedGridView, except that they need to be wrapped by a CustomScrollView. That allows you to give them siblings, for example:

@override
Widget build(BuildContext context) => CustomScrollView(
      slivers: <Widget>[
       CharacterSearchInputSliver(
          onChanged: (searchTerm) => _updateSearchTerm(searchTerm),
        ),
        PagedSliverList<int, CharacterSummary>(
          pagingController: _pagingController,
          builderDelegate: PagedChildBuilderDelegate<CharacterSummary>(
            itemBuilder: (context, item, index) => CharacterListItem(
              character: item,
            ),
          ),
        ),
      ],
    );

Notice that your preceding/following widgets should also be Slivers. CharacterSearchInputSliver, for example, is nothing but a TextField wrapped by a SliverToBoxAdapter.

If you're adding a single widget, as in the example, SliverToBoxAdapter will do the job. If you need to add a list of preceding/following items, you can use a SliverList.

Searching/Filtering/Sorting #

In the preceding recipe, you can see how to add a search bar widget as a list header. That example calls a function named _updateSearchTerm every time the user changes the search input. That function isn't part of the package, it's just a suggestion on how to implement searching. Here's the complete code:

class CharacterSliverList extends StatefulWidget {
  @override
  _CharacterSliverListState createState() => _CharacterSliverListState();
}

class _CharacterSliverListState extends State<CharacterSliverList> {
  static const _pageSize = 17;

  final PagingController _pagingController =
      PagingController<int, CharacterSummary>(firstPageKey: 0);

  Object _activeCallbackIdentity;

  String _searchTerm;

  @override
  void initState() {
    _pagingController.addPageRequestListener((pageKey) {
      _fetchPage(pageKey);
    });
    
    super.initState();
  }

  void _fetchPage(pageKey) {
    final callbackIdentity = Object();

    _activeCallbackIdentity = callbackIdentity;
    RemoteApi.getCharacterList(pageKey, _pageSize, searchTerm: _searchTerm)
        .then((newItems) {
      if (callbackIdentity == _activeCallbackIdentity) {
        final isLastPage = newItems.length < _pageSize;
        if (isLastPage) {
          _pagingController.appendLastPage(newItems);
        } else {
          final nextPageKey = pageKey + newItems.length;
          _pagingController.appendPage(newItems, nextPageKey);
        }
      }
    }).catchError((error) {
      if (callbackIdentity == _activeCallbackIdentity) {
        _pagingController.error = error;
      }
    });
  }

  @override
  Widget build(BuildContext context) => CustomScrollView(
        slivers: <Widget>[
          CharacterSearchInputSliver(
            onChanged: _updateSearchTerm,
          ),
          PagedSliverList<int, CharacterSummary>(
            pagingController: _pagingController,
            builderDelegate: PagedChildBuilderDelegate<CharacterSummary>(
              itemBuilder: (context, item, index) => CharacterListItem(
                character: item,
              ),
            ),
          ),
        ],
      );

  void _updateSearchTerm(String searchTerm) {
    _searchTerm = searchTerm;
    _pagingController.refresh();
  }

  @override
  void dispose() {
    _activeCallbackIdentity = null;
    _pagingController.dispose();
    super.dispose();
  }
}

The same structure can be applied to all kinds of filtering and sorting and works with any layout (not just Slivers).

Pull-to-Refresh #

Simply wrap your PagedListView, PagedGridView or CustomScrollView with a RefreshIndicator (from the material library) and inside onRefresh, call refresh on your PagingController instance:

@override
Widget build(BuildContext context) => RefreshIndicator(
      onRefresh: () => Future.sync(
        () => _pagingController.refresh(),
      ),
      child: PagedListView<int, CharacterSummary>(
        pagingController: _pagingController,
        builderDelegate: PagedChildBuilderDelegate<CharacterSummary>(
          itemBuilder: (context, item, index) => CharacterListItem(
            character: item,
          ),
        ),
      ),
    );

Listening to Status Changes #

If you need to execute some action when the list status changes, such as displaying a dialog/snackbar/toast, or sending a custom event to a BLoC or so, add a status listener to your PagingController. For example:

@override
void initState() {
  _pagingController.addPageRequestListener((pageKey) {
    _fetchPage(pageKey);
  });

  _pagingController.addStatusListener((status) {
    if (status == PagingStatus.subsequentPageError) {
      Scaffold.of(context).showSnackBar(
        SnackBar(
          content: const Text(
            'Something went wrong while fetching a new page.',
          ),
          action: SnackBarAction(
            label: 'Retry',
            onPressed: () => _pagingController.retryLastRequest(),
          ),
        ),
      );
    }
  });

  super.initState();
}

Custom Layout #

In case PagedListView, PagedSliverList, PagedGridView and PagedSliverGrid doesn't work for you, you should create a new sliver layout.

Creating a new layout is just a matter of using PagedSliverBuilder and provide it builders for the completed, in progress with error and in progress with loading layouts. For example, take a look at how PagedSliverGrid is built:

@override
Widget build(BuildContext context) =>
    PagedSliverBuilder<PageKeyType, ItemType>(
      pagingController: pagingController,
      builderDelegate: builderDelegate,
      completedListingBuilder: (
        context,
        itemBuilder,
        itemCount,
      ) =>
          SliverGrid(
        gridDelegate: gridDelegate,
        delegate: _buildSliverDelegate(
          itemBuilder,
          itemCount,
        ),
      ),
      loadingListingBuilder: (
        context,
        itemBuilder,
        itemCount,
        progressIndicatorBuilder,
      ) =>
          SliverGrid(
        gridDelegate: gridDelegate,
        delegate: _buildSliverDelegate(
          itemBuilder,
          itemCount,
          statusIndicatorBuilder: progressIndicatorBuilder,
        ),
      ),
      errorListingBuilder: (
        context,
        itemBuilder,
        itemCount,
        errorIndicatorBuilder,
      ) =>
          SliverGrid(
        gridDelegate: gridDelegate,
        delegate: _buildSliverDelegate(
          itemBuilder,
          itemCount,
          statusIndicatorBuilder: errorIndicatorBuilder,
        ),
      ),
    );

Notice that your resulting widget will be a Sliver, and as such, you need to wrap it with a CustomScrollView before adding to the screen.

BLoC #

Infinite Scroll Pagination is designed to work with any state management approach you prefer in any way you'd like. Because of that, for each approach, there's not only one, but several ways in which you could work with this package. Below, it's just one of the possible ways to integrate it with BLoCs:

class _CharacterSliverGridState extends State<CharacterSliverGrid> {
  final CharacterSliverGridBloc _bloc = CharacterSliverGridBloc();
  final PagingController<int, CharacterSummary> _pagingController =
      PagingController(firstPageKey: 0);
  StreamSubscription _blocListingStateSubscription;

  @override
  void initState() {
    _pagingController.addPageRequestListener((pageKey) {
      _bloc.onPageRequestSink.add(pageKey);
    });

    // We could've used StreamBuilder, but that would unnecessarily recreate
    // the entire [PagedSliverGrid] every time the state changes.
    // Instead, handling the subscription ourselves and updating only the
    // _pagingController is more efficient.
    _blocListingStateSubscription =
        _bloc.onNewListingState.listen((listingState) {
      _pagingController.value = PagingState(
        nextPageKey: listingState.nextPageKey,
        error: listingState.error,
        itemList: listingState.itemList,
      );
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) => CustomScrollView(
        slivers: <Widget>[
          CharacterSearchInputSliver(
            onChanged: (searchTerm) => _bloc.onSearchInputChangedSink.add(searchTerm),
          ),
          PagedSliverGrid<int, CharacterSummary>(
            pagingController: _pagingController,
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
              childAspectRatio: 100 / 150,
              crossAxisSpacing: 10,
              mainAxisSpacing: 10,
              crossAxisCount: 3,
            ),
            builderDelegate: PagedChildBuilderDelegate<CharacterSummary>(
              itemBuilder: (context, item, index) => CharacterGridItem(
                character: item,
              ),
            ),
          ),
        ],
      );

  @override
  void dispose() {
    _pagingController.dispose();
    _blocListingStateSubscription.cancel();
    super.dispose();
  }
}

Check out the example project for the complete source code.

3161
likes
0
pub points
99%
popularity

Publisher

verified publisheredsonbueno.com

Load and display pages of items as the user scrolls down your screen.

Repository (GitHub)
View/report issues

License

unknown (LICENSE)

Dependencies

flutter

More

Packages that depend on infinite_scroll_pagination