scrolled_pagination 1.9.0 copy "scrolled_pagination: ^1.9.0" to clipboard
scrolled_pagination: ^1.9.0 copied to clipboard

A reusable, MVC-style infinite-scroll pagination toolkit for Flutter — one controller, many providers (Future/Stream/sync), page or cursor based, eight builders, hasReachedEnd, item + batch ops, key-b [...]

Changelog #

All notable changes to scrolled_pagination are documented here. This project adheres to Semantic Versioning.

1.9.0 #

Added #

  • Guaranteed unique items + "newest wins" duplicate resolution. Both PaginationController and the Sliver SectionController now refuse to hold two items with the same key (keyOf / idOf). Before any item is added, the list is checked for an item with the same key; if one exists it is replaced in place (position and, when it wins, identity preserved — unchanged rows never rebuild) instead of being duplicated.
    • DuplicateResolver<T> decides which of two same-key items to keep: DuplicateResolver.byUpdatedAt((i) => i.updatedAt), DuplicateResolver.byVersion((i) => i.version), DuplicateResolver.custom((existing, incoming) => …), plus preferIncoming (default) and preferExisting. Pass it as the new duplicateResolver: argument on either controller. When omitted, the incoming item replaces the old one by default.
    • Applied to every path that adds items: first load, loadMore (paginated re-sends), refresh, addItem, insertItem, insertItems, addBatch, updateAll, replaceAll / replace*, realtime stream merges, and multi-source / hybrid Future+Stream merges. A final de-duplication pass after each item operation is the safety net, so the list is always clean, unique and (when a SortManager is enabled) sorted.

1.8.0 #

Added #

  • rxdart-powered realtime stream merging, isolated behind a RealtimeStreamService in lib/src/stream/ (the only place that imports rxdart). The public API stays stable; everything below is additive.
    • Multiple realtime sources per sectionstreamList, streamGrid, hybridList, hybridGrid take extraStreams plus a RealtimeStreamOptions (mergeMode: merge | combineLatest | concat, debounce, throttle, distinct). Sources are combined with the matching Rx combinator instead of hand-rolled stream plumbing, then merged into the list by id (no duplicates, no clears).
    • mergedStreamProvider — assembles one logical page from several Stream<PaginationPage<T>> backends via combineLatest, de-duplicated by idOf.
    • replaceRealtimeStreams([...]) — swaps the whole source set at runtime; switchMap cancels the previous combination automatically (no leaked subscriptions). replaceRealtimeStream now delegates to it.
    • Duplicate-emission suppression via distinct (deep list equality) and burst-coalescing via debounce / throttle — visible rows are not rebuilt when a stream re-emits unchanged data.
    • Fallback preserved — a single source with no options keeps the original lightweight Stream.listen path; rxdart is engaged only when it adds value (2+ sources or any operator). loadMore / refresh / sorting / item mutations remain BuildContext-free and unchanged.

1.7.0 #

Added #

  • scrollview_observer integration — accurate, index-based scrolling and live visible-item tracking, isolated behind a small adapter so the rest of the library stays package-agnostic. The public API is unchanged; everything below is additive.
    • Accurate scroll-to-indexjumbTo / animateTo / ensureVisibleAt / focus scrolling now resolve the target row through the observer's pixel-accurate positioning instead of an offset estimate. When the package cannot serve a request (no mounted sliver yet), the previous estimate-and-retry logic runs as a fallback, so behaviour never regresses.
    • Visible-item tracking (new, opt-in)PaginationController now exposes firstVisibleIndex, lastVisibleIndex, visibleIndices, visibleItems, isIndexVisible(i) and hasVisibilityInfo, plus an onVisibleItemsChanged callback. These are fed by the view's observer and never rebuild the list — ideal for analytics, prefetch, read-receipts and autoplay.
    • Item-based FAB edges — the scroll-navigation FABs auto-hide based on whether the first / last item is visible (accurate with variable-height rows), falling back to pixel comparison when no observation exists.
    • Reverse / chat-like position keeping — in reverse: true mode the list uses the observer's clamping physics and arms position-keeping the instant a load-more begins, so loading older pages no longer makes the viewport jump. Forward lists keep the existing scroll-anchor preservation.
    • Decoupling — only lib/src/observer/ imports scrollview_observer (a ScrollObserverAdapter service + a one-widget ObserverScrollView wrapper). The PaginationController remains free of BuildContext; all loadMore / refresh / item / batch / sort operations are untouched.

1.6.0 #

Added #

  • Optional scroll-navigation floating action buttons — two opt-in FABs that scroll the list to its first / last item, fully independent of the pagination data:
    • scrollToFirstFloatingButton and scrollToLastFloatingButton on PaginatedScrollView (and on ScrolledPaginationView). Both are disabled by default (null → no button). scrollToFirstFloatingButton scrolls / jumps to the first item; scrollToLastFloatingButton to the last.
    • New ScrollEdgeFab configuration object: custom icon, label, tooltip, margin, extendedPadding, heroTag, backgroundColor, foregroundColor, shape, elevation, an animate flag (animate vs. jump) and per-button scrollOptions. Render it as a ScrollFabType.regular, .small, .extended or a fully .custom widget via builder.
    • Visibility via ScrollFabVisibility: auto (default) hides the "first" button while already at the top and the "last" button while already at the end; always keeps it visible. The edge slack is tunable with floatingActionButtonHideThreshold.
    • Placement & animation: floatingActionButtonLocation and floatingActionButtonAnimator are forwarded to an internal, transparent Scaffold that hosts the buttons; floatingActionButtonSpacing sets the gap when both buttons show. One button or two are both supported.
    • No list rebuilds on visibility change — the FAB overlay is a sibling of the list and re-renders itself only when the edge state flips; the list above keeps its cached rows, so pagination state, scroll position, focus and selection are all preserved. Works across List, Grid, Separated, Reverse, Sliver and the multi-section CustomScrollView.
  • PaginationController.jumpToFirst() / jumpToLast() — conventional-spelling aliases of jumbToFirst / jumbToLast that drive the new FABs (the animated counterparts animateToFirst / animateToLast already existed).
  • ScrolledPaginationController.animateToFirst() / animateToLast() — aliases of animateToTop / animateToBottom, alongside the existing jumpToFirst / jumpToLast.

1.5.0 #

Added #

  • Static leading / trailing widgets — optional fixed widgets that frame the list without being part of the data:
    • leadingWidget renders before the first data item, trailingWidget after the last — on PaginationController (as initial values + the setLeadingWidget / removeLeadingWidget / setTrailingWidget / removeTrailingWidget / clearStaticWidgets methods) and on PaginatedScrollView (as props; the controller value takes precedence).
    • They are real widgets, not data items: never counted in count / items, never touched by insert / update / delete / replace / sort / refresh, and they hold a fixed position when items are added or removed. itemBuilder indexes and insertItem / deleteWhere / updateWhere / jumbTo / focusAt / moveTo continue to address data items only.
    • Setting one calls notifyListeners without firing onItemsChanged or re-sorting — only the static-widget area rebuilds, the data rows keep their cached widgets, and scroll position is preserved across add / remove.
    • Visibility options: showLeadingWhenEmpty, showTrailingWhenEmpty, showLeadingWhenLoading, showTrailingWhenLoading (default false — the emptyBuilder / firstPageLoadingBuilder are not replaced unless opted in), and trailingPosition (StaticTrailingPosition.beforeReachEndBuilder / afterReachEndBuilder) to place the trailing widget around the reach-end / load-more footer.
    • Works across ListView, GridView, separated and SliverList layouts. All methods are null- and empty-list-safe.

1.4.0 #

Added #

  • Reach-end state — an explicit hasReachedEnd flag on PaginationController (and SectionController):
    • true once there is nothing more to load, false while more pages can still be fetched — the inverse of hasMore.
    • Recomputed after every loadMore and after every refresh.
    • Detection order: a custom detectReachEnd(page, items) callback, then the data source's hasMore flag, then an empty next page, then a short page (fewer items than pageSize).
    • loadMore() is a no-op while hasReachedEnd is true, so the end is never re-fetched (no duplicate requests).
  • reachEndBuilder — an optional footer on PaginatedScrollView (and on the paginated ScrolledPaginationSection factories) shown once hasReachedEnd is true, after the last item:
    • Optional — when omitted, falls back to the legacy noMoreBuilder; when both are absent, nothing is rendered at the end.
    • Never replaces the emptyBuilder, and never appears during first loading or the error state.
    • Only the footer area rebuilds when reach-end flips — the rows above keep their cached widgets, so scroll position is preserved. Works across ListView, GridView and SliverList layouts.
    • Supports custom UI such as a "No more items" label.

1.3.0 #

Added #

  • Multi-section, Sliver-based engine — a second way to use the package, built on a single CustomScrollView + slivers:
    • ScrolledPaginationView — assembles many independent sections into one scroll view; each section's slivers are wrapped in their own ListenableBuilder, so one section's realtime update or "load more" rebuilds only that section, never the whole screen.
    • ScrolledPaginationSection factories: futureList, streamList, hybridList, localList, futureGrid, streamGrid, hybridGrid, staticBox, staticSliver, loading, empty, errorState, customSliver.
    • SectionController<T> — per-section state (items · status · hasMore · error · focus) with initialize / loadMore / refresh / retry, all the required item operations (insertItem(s), updateFirstWhere, updateWhere, updateAll, replaceFirstWhere, deleteFirstWhere, deleteWhere, moveTo, moveToFirst, moveToLast, clear) and an optional focus manager (focusAt, focusWhere, focusLastOpeningTime).
    • addBatch — add many items in one pass, notify once, with BatchInsertStrategy (append · prepend · insertAtIndex · sortedInsert · custom) and DuplicateStrategy (skip · replace · update · custom).
    • Hybrid Future + Stream merging via RealtimeMergeStrategy (append · prepend · replaceExistingOnly · sortedInsert · custom): realtime emissions update existing items in place and insert new ones without duplicating or clearing the paginated list (identity by idOf).
    • Stable ValueKey(idOf(item)) row identity + findChildIndexCallback, so appending a page never rebuilds visible unchanged items.
    • ScrolledPaginationController — shared scroll controller with view-level jumpToFirst / jumpToLast / animateToTop / animateToBottom.
  • The optional SortManager and the focus manager work across single inserts, addBatch, pagination and realtime updates in these sections too.

1.2.0 #

Added #

  • Bloc bindings (new entrypoint package:scrolled_pagination/scrolled_pagination_bloc.dart, built on package:bloc):
    • PaginationBloc<T> — event-driven facade over PaginationController. Events cover loading (PaginationStarted, PaginationRefreshRequested, PaginationLoadMoreRequested, PaginationRetryRequested), single-item operations, batch operations (PaginationAllUpdated, PaginationItemsBatchInserted / Replaced / Deleted / Moved, PaginationTransactionRequested), sorting (PaginationSortChanged, PaginationResortRequested), selection and focus.
    • PaginationCubit<T> — method-call facade with the same states, delegating the full controller API (including scroll actions).
    • PaginationBlocState<T> — immutable, value-equal snapshots (status, items, hasMore, pageParam, error, focusedIndex, selectedKeys), so BlocBuilder only rebuilds on real changes.
    • The controller remains the single source of truth — a batch event / transaction emits exactly one state.
  • Dependency: bloc: ^8.1.0 (the widgets layer — flutter_bloc — stays in your app).

1.1.0 #

Added #

  • Batch updates — collect all changes first, apply them in one transaction, notify the UI exactly once (no per-item notifyListeners):
    • updateAll(updater) — update every item, single notify.
    • insertItemsBatch(List<BatchInsert<T>>) — many positional inserts.
    • replaceItemsBatch(List<BatchReplace<T>>) — many keyed replacements.
    • deleteItemsBatch(List<Object> keys) — many keyed deletes.
    • moveItemsBatch(List<BatchMove>) — many moves.
    • transaction(body) — compose any item operations into one notify; transactions nest.
  • SortManager<T> (optional, disabled by default — insertion order is kept when off):
    • SortManager.byField / .byDate / .byId / .byIndex / .byPriority / .custom constructors; ascending & descending via SortOrder.
    • Stable sort — equal items keep their relative order, so unaffected rows never jump.
    • Re-sorts automatically after load, insert, update, replace and batch operations (once per transaction); controller.resort() re-sorts manually after changing order / comparator.
  • Key-based selection that survives insert, update, delete, sort and refresh: select, deselect, toggleSelected, selectAll, clearSelection, isSelected, isKeySelected, selectedKeys, selectedItems. Deleting an item prunes its selection key.
  • Diff-based view updates in PaginatedScrollView:
    • Stable per-item keys (from getKey) instead of index keys — existing row widgets stay mounted across insert / delete / move / sort.
    • Built-row cache: a row whose item is identical to the previous build is reused as the same widget instance, so Flutter skips its rebuild entirely; only added / removed / changed rows do any work.
    • shouldRebuildItem(oldItem, newItem) — keep a row alive even when the item instance changed but its visible data did not.
    • findChildIndexCallback wiring so moved rows keep their Element (and any internal state).
    • preserveScrollPosition (default true) — the first visible row stays anchored after inserts / deletes / sorts above it.
  • Focus is preserved by key across sorts and batch updates (in addition to the existing refresh restore).

Changed #

  • Item-level callbacks (onItemInserted, …) still fire per item inside a batch, but notifyListeners + onItemsChanged fire once per transaction.

1.0.0 #

Initial release.

Added #

  • PaginationController<T> (a ChangeNotifier) — the MVC model + controller:
    • Providers: futureProvider, streamProvider, syncProvider.
    • Pagination: page-based and cursor-based, custom pageSize and initialPageParam, hasMore flag, duplicate-request guard, stale-response cancellation on refresh.
    • Loading: loadFirst, refresh, loadMore, retry.
    • Item operations: addItem, insertItem, insertItems, updateItem, updateWhere, updateFirstWhere, removeItem, deleteWhere, deleteFirstWhere, replaceAt, replaceFirstWhere, replaceWhere, replaceAll, clear, moveItem, moveToFirst, moveToLast, swapItems.
    • Scroll actions: jumbTo, jumbToFirst, jumbToLast, jumbWhere, animateTo, animateToFirst, animateToLast, moveTo, ensureVisibleAt, ensureVisibleWhere — with ScrollOptions (alignment, duration, curve, offset, keepScrollPosition).
    • Focus manager (opt-in): focusAt, focusWhere, focusFirst, focusLast, focusNext, focusPrevious, clearFocus; hasFocus, focusedIndex, focusedItem, focusLastOpeningTime; focus kept and restored across refresh.
    • Callbacks: onItemsChanged, onItemInserted, onItemUpdated, onItemDeleted, onItemReplaced, onItemMoved, onFocusChanged, onJumbFailed, onIndexOutOfRange.
  • PaginatedScrollView<T> — the view:
    • Layouts: list, separated, grid, sliver.
    • Seven builders, eight states.
    • Pull-to-refresh, retry-on-error, custom scrollController, physics, shrinkWrap, padding, gap, reverse, loadMoreThreshold.
    • Focus ring on the focused row; option-aware scroll executor that waits until the scroll view is attached and the row is built.
  • ScrolledPaginationControllerFactory — a static cache that reuses one controller per key + type + userId: create (with force), get, dispose, disposeAll.
  • Example app demonstrating the full API with a simulated network source.
1
likes
140
points
46
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

A reusable, MVC-style infinite-scroll pagination toolkit for Flutter — one controller, many providers (Future/Stream/sync), page or cursor based, eight builders, hasReachedEnd, item + batch ops, key-based dedup (DuplicateResolver are byUpdatedAt/byVersion/custom), SortManager, selection, focus, scroll-to-index, rxdart realtime merging, bloc bindings (PaginationBloc/PaginationCubit), and a multi-section Sliver engine for mixed list + grid feeds.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

bloc, flutter, rxdart, scrollview_observer

More

Packages that depend on scrolled_pagination