Advanced Pagination - Flutter

A controller-first pagination package for Flutter with page/offset/cursor support, clean widgets, cache + prefetch, and customizable loading/empty/error UI.

Features

  • Page, offset, and cursor-based pagination.
  • PagedListView, PagedGridView, and sliver variants.
  • Scroll prefetch to load ahead of the viewport.
  • Cache manager with stale-while-revalidate support.
  • PagedLayoutBuilder and per-widget builders for custom states.

Installation

Add to your pubspec.yaml:

dependencies:
  advanced_pagination: ^0.1.0

Then run:

flutter pub get

Quick Start

1) Create a controller

final controller = PaginationController.pageBased(
  firstPageKey: 1,
  pageSize: 20,
  fetchPage: (request) async {
    final page = request.pageKey ?? 1;
    final items = await api.fetchPage(page, request.pageSize);

    return PageResult(
      items: items,
      hasNext: page < 10,
      hasPrevious: page > 1,
    );
  },
);

2) Render a list

PagedListView<int, Article>(
  controller: controller,
  itemBuilder: (context, item, index) => ArticleTile(item),
);

Controller Types

Page-based

final controller = PaginationController.pageBased(
  firstPageKey: 1,
  pageSize: 20,
  fetchPage: (request) async {
    final page = request.pageKey ?? 1;
    final items = await api.fetchPage(page, request.pageSize);

    return PageResult(
      items: items,
      hasNext: page < 10,
      hasPrevious: page > 1,
    );
  },
);

Offset-based

final controller = PaginationController.offsetBased(
  initialOffset: 0,
  pageSize: 20,
  fetchPage: (request) async {
    final offset = request.pageKey ?? 0;
    final items = await api.fetchOffset(offset, request.pageSize);

    return PageResult(
      items: items,
      hasNext: items.isNotEmpty,
      hasPrevious: offset > 0,
    );
  },
);

Cursor-based

final controller = PaginationController.cursorBased<String, Article>(
  initialCursor: "0",
  pageSize: 20,
  fetchPage: (request) async {
    final result = await api.fetchCursor(request.pageKey, request.pageSize);

    return PageResult(
      items: result.items,
      hasNext: result.nextCursor != null,
      hasPrevious: result.prevCursor != null,
      nextKey: result.nextCursor,
      prevKey: result.prevCursor,
    );
  },
);

Widgets

PagedGridView

PagedGridView<int, Article>(
  controller: controller,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    mainAxisSpacing: 8,
    crossAxisSpacing: 8,
  ),
  itemBuilder: (context, item, index) => ArticleTile(item),
);

Sliver list/grid

CustomScrollView(
  slivers: [
    const SliverAppBar(title: Text("Sliver Grid")),
    PagedSliverGrid<int, Article>(
      controller: controller,
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
        mainAxisSpacing: 8,
        crossAxisSpacing: 8,
        childAspectRatio: 1.6,
      ),
      itemBuilder: (context, item, index) => ArticleTile(item),
    ),
  ],
);

Custom States

PagedLayoutBuilder

PagedLayoutBuilder<int, Article>(
  controller: controller,
  loadingBuilder: (_) => const Center(child: CircularProgressIndicator()),
  emptyBuilder: (_) => const Center(child: Text("No items")),
  errorBuilder: (_, error) => const Center(child: Text("Something went wrong")),
  builder: (context, state) {
    return PagedListView<int, Article>(
      controller: controller,
      itemBuilder: (context, item, index) => ArticleTile(item),
    );
  },
);

Per-widget builders

PagedListView<int, Article>(
  controller: controller,
  loadingBuilder: (_) => const CircularProgressIndicator(),
  statusBuilder: (context, status) {
    if (status == PaginationStatus.error) {
      return const Center(child: Text("Oops"));
    }
    return const SizedBox.shrink();
  },
  itemBuilder: (context, item, index) => ArticleTile(item),
);

Scroll Prefetch

PagedListView<int, Article>(
  controller: controller,
  enableScrollPrefetch: true,
  prefetchOffset: 700,
  preloadOffset: 200,
  itemBuilder: (context, item, index) => ArticleTile(item),
);

Cache + Stale-While-Revalidate

final cache = MemoryPaginationCacheManager<int, Article>(maxEntries: 10);

final controller = PaginationController.pageBased(
  firstPageKey: 1,
  pageSize: 20,
  fetchPage: (request) async { /* ... */ },
  cacheManager: cache,
  cacheKey: "articles-feed",
  cachePolicy: const PaginationCachePolicy(
    maxAge: Duration(minutes: 10),
    staleWhileRevalidate: true,
  ),
);

Parameters (Most Used)

  • autoLoad: if true, loads the first page after build.
  • enableRefresh: enables pull-to-refresh in PagedListView.
  • preloadOffset: how close to the end before fetchNext() triggers.
  • enableScrollPrefetch: enables early prefetchNext().
  • prefetchOffset: how early prefetch starts.
  • loadingBuilder: custom loader widget (defaults to CircularProgressIndicator).

Controller API

  • refresh(): reload from start.
  • fetchNext(): append next page.
  • fetchPrevious(): prepend previous page.
  • prefetchNext(): silent prefetch (no loader).
  • retry(): repeat last failed request.
  • cancel(): cancel active request.

Quick FAQ

You can read the Quick-FAQ here: https://github.com/patoliavishal/flutter_advanced_pagination/wiki/Quick-FAQ

Running the Example App

The example is a full Flutter app with android/ and ios/ folders.

cd example
flutter pub get
flutter run

For iOS on macOS, run flutter run and allow Xcode to handle signing if prompted.

Additional Information

Pull requests and feedback are welcome. If you encounter any issues, please open a GitHub issue and include a minimal reproducible example.