PaginatedCache<T> class
Infinite-list cache over a Syncache: fetches pages one at a time, accumulates them into a windowed list stored as a normal cached value under key, and exposes it as reactive watch state.
Because the window is a plain cached List<T>, everything the cache
already does applies to it: persistence via any Store, optimistic
mutations via SyncacheListExtension.mutateList / ListOperation
(durable through a MutationOutbox), and invalidation by pattern or tag
— invalidatePattern('posts?*') reaches the window key like any other.
Semantics
- loadMore appends the next page. Page-fetch failures keep the window and the cursor unchanged (safe to retry) and surface via PagedViewState.error — the returned future never throws. Concurrent calls coalesce into one fetch.
- refresh re-fetches the first page and merges: fresh items win at
the front, duplicates (by
==) are removed from the retained tail. - External invalidation of key (pattern, tag, or direct) drops the
tail and re-fetches page 1 when
autoRefreshOnInvalidationis true (default). Eviction andclearreset the paging state but never auto-refetch (capacity events cause no refill storms). maxItemsbounds the window by dropping from the front; the server cursor is unaffected.- The page cursor is in-memory. After a process restart the stored
window rehydrates, the cursor resets to firstPageKey, and a
loadMoreon a non-empty window refresh-merges instead of appending — so it can never duplicate the rehydrated items.
Mutating the window
Do not go through PaginatedCache — mutate the underlying cache directly; the state stream follows automatically:
final posts = PaginatedCache<Post>(
cache: listCache, // Syncache<List<Post>>
key: 'posts?feed=home',
fetchPage: (pageKey) async {
final response = await api.fetchPosts(cursor: pageKey as String?);
return PageResult(response.items, nextPageKey: response.nextCursor);
},
maxItems: 500,
);
await posts.loadMore();
// Optimistic append, durable via the configured outbox:
await listCache.mutateList(
key: 'posts?feed=home',
operation: ListOperation.append(newPost),
send: () => api.createPost(newPost),
// Invalidate ITEM keys, never the window itself — a mutation that
// invalidates its own window key drops the tail on every sync.
invalidates: ['post:*'],
);
One PaginatedCache per cache key: two instances over the same key
would both fetch and fight over the window. For scoped caches, pass the
underlying Syncache and the already-prefixed key
(cache.scoped('s').cache / 's:posts').
Constructors
-
PaginatedCache({required Syncache<
List< cache, required String key, required Future<T> >PageResult< fetchPage(Object? pageKey), Object? firstPageKey, int? maxItems, bool autoRefreshOnInvalidation = true})T> > -
Creates a paginated cache over
cache's window atkey.
Properties
- firstPageKey → Object?
-
The key of the very first page (opaque; often
nullfor cursor APIs).final - hashCode → int
-
The hash code for this object.
no setterinherited
- key → String
-
final
- maxItems → int?
-
Maximum number of items kept in the window. When exceeded, items are
dropped from the front (the oldest pages).
null= unbounded.final - runtimeType → Type
-
A representation of the runtime type of the object.
no setterinherited
-
state
→ PagedViewState<
T> -
The current window and fetch state.
no setter
-
watch
→ Stream<
PagedViewState< T> > -
Emits a new PagedViewState whenever the window or fetch state
changes. Seeded: every listener first receives the current state.
A stream over a disposed instance completes immediately.
no setter
Methods
-
dispose(
) → void - Detaches from the cache. The stored window survives; in-flight fetch results are discarded; the watch stream closes.
-
invalidate(
) → Future< void> - Drops the window and re-fetches the first page.
-
loadMore(
) → Future< void> - Fetches the next page and appends it to the window.
-
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a nonexistent method or property is accessed.
inherited
-
refresh(
) → Future< void> -
Re-fetches the first page and merges it into the window: fresh items
win at the front, duplicates (by
==) are dropped from the retained tail. Use this for pull-to-refresh semantics — scroll position and tail survive. -
toString(
) → String -
A string representation of this object.
inherited
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited