scrolled_pagination 1.9.0
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
PaginationControllerand the SliverSectionControllernow 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) => …), pluspreferIncoming(default) andpreferExisting. Pass it as the newduplicateResolver: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 aSortManageris enabled) sorted.
1.8.0 #
Added #
rxdart-powered realtime stream merging, isolated behind aRealtimeStreamServiceinlib/src/stream/(the only place that importsrxdart). The public API stays stable; everything below is additive.- Multiple realtime sources per section —
streamList,streamGrid,hybridList,hybridGridtakeextraStreamsplus aRealtimeStreamOptions(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 severalStream<PaginationPage<T>>backends viacombineLatest, de-duplicated byidOf.replaceRealtimeStreams([...])— swaps the whole source set at runtime;switchMapcancels the previous combination automatically (no leaked subscriptions).replaceRealtimeStreamnow delegates to it.- Duplicate-emission suppression via
distinct(deep list equality) and burst-coalescing viadebounce/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.listenpath;rxdartis engaged only when it adds value (2+ sources or any operator).loadMore/refresh/ sorting / item mutations remainBuildContext-free and unchanged.
- Multiple realtime sources per section —
1.7.0 #
Added #
scrollview_observerintegration — 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-index —
jumbTo/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) —
PaginationControllernow exposesfirstVisibleIndex,lastVisibleIndex,visibleIndices,visibleItems,isIndexVisible(i)andhasVisibilityInfo, plus anonVisibleItemsChangedcallback. 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: truemode 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/importsscrollview_observer(aScrollObserverAdapterservice + a one-widgetObserverScrollViewwrapper). ThePaginationControllerremains free ofBuildContext; allloadMore/refresh/ item / batch / sort operations are untouched.
- Accurate scroll-to-index —
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:
scrollToFirstFloatingButtonandscrollToLastFloatingButtononPaginatedScrollView(and onScrolledPaginationView). Both are disabled by default (null→ no button).scrollToFirstFloatingButtonscrolls / jumps to the first item;scrollToLastFloatingButtonto the last.- New
ScrollEdgeFabconfiguration object: customicon,label,tooltip,margin,extendedPadding,heroTag,backgroundColor,foregroundColor,shape,elevation, ananimateflag (animate vs. jump) and per-buttonscrollOptions. Render it as aScrollFabType.regular,.small,.extendedor a fully.customwidget viabuilder. - Visibility via
ScrollFabVisibility:auto(default) hides the "first" button while already at the top and the "last" button while already at the end;alwayskeeps it visible. The edge slack is tunable withfloatingActionButtonHideThreshold. - Placement & animation:
floatingActionButtonLocationandfloatingActionButtonAnimatorare forwarded to an internal, transparentScaffoldthat hosts the buttons;floatingActionButtonSpacingsets 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 ofjumbToFirst/jumbToLastthat drive the new FABs (the animated counterpartsanimateToFirst/animateToLastalready existed).ScrolledPaginationController.animateToFirst()/animateToLast()— aliases ofanimateToTop/animateToBottom, alongside the existingjumpToFirst/jumpToLast.
1.5.0 #
Added #
- Static leading / trailing widgets — optional fixed widgets that frame the
list without being part of the data:
leadingWidgetrenders before the first data item,trailingWidgetafter the last — onPaginationController(as initial values + thesetLeadingWidget/removeLeadingWidget/setTrailingWidget/removeTrailingWidget/clearStaticWidgetsmethods) and onPaginatedScrollView(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.itemBuilderindexes andinsertItem/deleteWhere/updateWhere/jumbTo/focusAt/moveTocontinue to address data items only. - Setting one calls
notifyListenerswithout firingonItemsChangedor 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(defaultfalse— theemptyBuilder/firstPageLoadingBuilderare not replaced unless opted in), andtrailingPosition(StaticTrailingPosition.beforeReachEndBuilder/afterReachEndBuilder) to place the trailing widget around the reach-end / load-more footer. - Works across
ListView,GridView, separated andSliverListlayouts. All methods are null- and empty-list-safe.
1.4.0 #
Added #
- Reach-end state — an explicit
hasReachedEndflag onPaginationController(andSectionController):trueonce there is nothing more to load,falsewhile more pages can still be fetched — the inverse ofhasMore.- Recomputed after every
loadMoreand after everyrefresh. - Detection order: a custom
detectReachEnd(page, items)callback, then the data source'shasMoreflag, then an empty next page, then a short page (fewer items thanpageSize). loadMore()is a no-op whilehasReachedEndistrue, so the end is never re-fetched (no duplicate requests).
reachEndBuilder— an optional footer onPaginatedScrollView(and on the paginatedScrolledPaginationSectionfactories) shown oncehasReachedEndistrue, 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,GridViewandSliverListlayouts. - Supports custom UI such as a "No more items" label.
- Optional — when omitted, falls back to the legacy
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 ownListenableBuilder, so one section's realtime update or "load more" rebuilds only that section, never the whole screen.ScrolledPaginationSectionfactories:futureList,streamList,hybridList,localList,futureGrid,streamGrid,hybridGrid,staticBox,staticSliver,loading,empty,errorState,customSliver.SectionController<T>— per-section state (items · status · hasMore · error · focus) withinitialize/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, withBatchInsertStrategy(append · prepend · insertAtIndex · sortedInsert · custom) andDuplicateStrategy(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 byidOf). - Stable
ValueKey(idOf(item))row identity +findChildIndexCallback, so appending a page never rebuilds visible unchanged items. ScrolledPaginationController— shared scroll controller with view-leveljumpToFirst/jumpToLast/animateToTop/animateToBottom.
- The optional
SortManagerand 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 onpackage:bloc):PaginationBloc<T>— event-driven facade overPaginationController. 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), soBlocBuilderonly 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/.customconstructors; ascending & descending viaSortOrder.- 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 changingorder/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
identicalto 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.findChildIndexCallbackwiring so moved rows keep theirElement(and any internal state).preserveScrollPosition(defaulttrue) — the first visible row stays anchored after inserts / deletes / sorts above it.
- Stable per-item keys (from
- 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, butnotifyListeners+onItemsChangedfire once per transaction.
1.0.0 #
Initial release.
Added #
PaginationController<T>(aChangeNotifier) — the MVC model + controller:- Providers:
futureProvider,streamProvider,syncProvider. - Pagination: page-based and cursor-based, custom
pageSizeandinitialPageParam,hasMoreflag, 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— withScrollOptions(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.
- Providers:
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.
- Layouts:
ScrolledPaginationControllerFactory— a static cache that reuses one controller perkey+ type +userId:create(withforce),get,dispose,disposeAll.- Example app demonstrating the full API with a simulated network source.