flutter_query_client 2.0.0
flutter_query_client: ^2.0.0 copied to clipboard
A TanStack Query-inspired server state management library for Flutter. Handles caching, background refetching, pagination, mutations, and network-aware fetching out of the box.
2.0.0 #
Breaking changes #
-
MultiQueryProvider.providerstype changed fromList<Widget Function(Widget child)>toList<QueryProviderWidget>— callers must replace builder functions with plain provider instances:// Before (1.2.0) MultiQueryProvider( providers: [ (child) => QueryProvider<PostsController, List<Post>>( create: (_) => PostsController(), child: child, ), ], child: HomeScreen(), ) // After (2.0.0) MultiQueryProvider( providers: [ QueryProvider<PostsController, List<Post>>( create: (_) => PostsController(), ), ], child: HomeScreen(), ) -
MutationController<T>→MutationController<T, P>— mutations now take a typed params genericP, matching theQueryController<T, P>pattern. Subclasses must overridemutationFn(P params)instead of passing a closure tomutate(). For mutations that don't need params, usevoidas the second type argument.// Before (1.2.0) class CreatePostMutation extends MutationController<Post> { Future<void> create(String title) async { await mutate(() => postService.createPost(title: title)); } } // After (2.0.0) class CreatePostMutation extends MutationController<Post, ({String title})> { @override Future<Post> mutationFn(({String title}) params) { return postService.createPost(title: params.title); } } // Usage: context.query<CreatePostMutation>().mutate((title: 'Hello'))
New features #
QueryProviderWidget— new public abstract base class that bothQueryProviderandInfiniteQueryProviderextend, enablingMultiQueryProvidercomposition; custom provider wrappers can also extend it to participate inMultiQueryProviderQueryClientProvider(observer:)— register aQueryObserverdirectly inQueryClientProvideralongside defaults and logging, keeping all global setup in one place; the constructor callsQueryClient.setObserverinternally.QueryClient.setObserverremains available as a lower-level escape hatch for registering outside the widget treeQueryDefaults.initialPageParam— global default first-page parameter for everyInfiniteQueryController; defaults to0. Set to1for one-indexed APIs or any custom value for cursor-based APIs — no per-controller override needed unless that controller differs from the global defaultQueryDefaults.limit— global default page size for everyInfiniteQueryController; defaults to20. Set once inQueryClientProviderinstead of repeating@override int get limit => Nin every subclassInfiniteQueryController.initialPageParam— no longer abstract; falls back toQueryDefaults.initialPageParamcast toPageParam. Must still be overridden when the controller'sPageParamtype or starting value differs from the global defaultInfiniteQueryController.limit— now falls back toQueryDefaults.limitrather than a hardcoded20; override per-controller when needed
Performance & memory optimizations #
- Fixed:
NetworkConnectivityObserverStreamController leak — the broadcastStreamControllerwas eagerly allocated and never closed (singleton lifetime). It is now created lazily and tracks active listeners viaonListen/onCancelcallbacks. AddedlistenerCountgetter for diagnostics - Fixed:
QueryClientsingleton had no diagnostic visibility — addedactiveStaleTimerCount,activeGcTimerCount,activeInvalidateCallbackCount,activeReconnectCallbackCount, andcacheEntryCountgetters for debugging timer and callback leaks - Fixed: dangling callback references in
QueryClient— if a controller was garbage-collected withoutclose(), its_onInvalidateand_onReconnectcallbacks persisted forever._notifyInvalidateCallbacksand_onConnectivityChangenow catch exceptions from stale callbacks and auto-prune them - Fixed: unnecessary deep copies in
InfiniteQueryController._saveToCache— replacedList<T>.from(p)(O(n) element-by-element copy) withUnmodifiableListView<T>(p)fromdart:collection(O(1) zero-copy wrapper)._restoreFromCacheandhandleRemountuseList<T>.of()instead ofList<T>.from()to skip per-element type checks.handleRemountno longer allocates a flat list just to compare lengths - Fixed: flat-cache thrashing on optimistic updates —
updateItemnow patches_flatCachein-place via_flatIndexOf()instead of nulling and rebuilding the entire flat list.prependItemandappendIteminsert/add directly into_flatCache. OnlyremoveItem(which changes list length) invalidates the cache - Fixed: redundant stale-listener re-registration —
StaleListenerHandle.register()now returns early when params are unchanged, avoiding unnecessarySet.remove()+ closure allocation +Set.add()on every fetch. AddedisRegisteredandregisteredParamsgetters - Fixed: redundant
isStalecache lookups on reconnect —_handleReconnect()in bothQueryControllerandInfiniteQueryControllerno longer performs a redundantclient.get(key)?.isStalelookup;state.isStale(maintained by the stale-timer callback) is sufficient
Notes #
QueryClient.updateInfiniteQuery<T>—- Preferred (controller reachable via
BuildContext) — call the controller's own helpers directly:prependItem,appendItem,updateItem, orremoveItem. These patch_flatCachein O(1) and emit a new state immediately.updateInfiniteQueryis then optional (use it when provider access is not present in the context - and you will see changes when navigated to desired page).
- Preferred (controller reachable via
Example #
- Added Issues tab to the example app with interactive before/after benchmarks for every optimization above; full documentation extracted to
example/lib/features/inefficiency_demos/OPTIMIZATIONS.md - Updated Posts form screen to use
MutationController<T, P>typed params,MultiQueryListener, andQueryClient.instance.updateto patch the flat'posts'cache from within mutation listeners — noflutter_blocimport required - Updated Products form screen to use
MutationController<T, P>typed params andMultiQueryProvider/MultiQueryListener; the paginated list screen demonstratesupdateInfiniteQuery+prependItemto sync both the cache and the live controller after a create - Updated Posts list screen to sync the live
PostsQueryControllerfrom the already-patched cache viaupdateCache((posts) => posts)after a create, avoiding a double-prepend
1.2.0 #
New features #
MultiQueryProvider— nest multipleQueryProviderandInfiniteQueryProviderwidgets without deep indentation; providers are applied top-to-bottomQueryConsumer<C, T>— combinesQueryBuilder+QueryListenerin a single widget, eliminating the need to nest them; works with bothQueryControllerandMutationControllerInfiniteQueryConsumer<C, T>— same asQueryConsumerfor infinite queries; theList<T>wrapper is baked into the type so only the item type is requiredQuerySelector<C, T, S>— aBlocSelectorscoped toQueryState<T>; rebuilds only when the selected derived valueSchanges, ideal for counters, flags, and other narrow slices of stateInfiniteQuerySelector<C, T, S>— same asQuerySelectorfor infinite queries;List<T>is baked inInfiniteQueryListener<C, T>— mirrorsInfiniteQueryBuilderfor the listener side; eliminates the verboseQueryListener<C, List<T>>type annotationQueryObserver— aBlocObserversubclass that filters events to query and mutation controllers and re-exposes them as typed, cache-key–aware hooks (onQueryCreate,onQueryChange,onQueryError,onQueryClose);onQueryChangereceives bothcurrentStateandnextState, matching standardBlocObserver.onChangesemantics- Mutation widgets —
MutationController<T>emitsQueryState<T>, soQueryBuilder,QueryListener,QueryConsumer, andQuerySelectorall work with mutations out of the box; no separateMutationBuilderorMutationListenerneeded
Example #
- Added a Widgets tab to the example app with live interactive demos of every widget in the package, including all mutation-controller combinations
1.1.0 #
Fixes #
- Fixed race condition in
InfiniteQueryController._executeFirstPagecache-hit branch — missing_filterVersioncheck afterawait Future.delayed(Duration.zero)could cause stale filter data to be emitted ifsetParamswas called concurrently - Fixed race condition in
InfiniteQueryController._executeFirstPagepause branch — same missing version check allowed apausedstate emit to overwrite state set by a concurrentsetParamscall - Fixed
InfiniteQueryController.loadMorenot calling_startRefetchInterval()on success — polling would never start ifloadMorewas the first successful fetch operation
1.0.1 #
Fixes #
- Removed unnecessary
package:meta/meta.dartimport fromQueryControllerandInfiniteQueryController— elements are already available viapackage:flutter/foundation.dart - Removed
@internalannotation fromhandleRemount()in both controllers - Fixed unresolved dartdoc references in
QueryLoggerandRefetchOnMount— replaced[Logger.root.onRecord]and[staleTime]with backtick code spans
1.0.0 #
Initial stable release.
Features #
- QueryController — fetch and cache server data with automatic stale-while-revalidate, retry with exponential backoff, refetch on mount, and refetch on reconnect
- MutationController — user-triggered mutations with lifecycle hooks (
onSuccess,onMutationError,onSettled) and optimistic cache update support - InfiniteQueryController — paginated / infinite-scroll queries with
loadMore(),hasMore, cursor or page-number pagination, and item-level cache helpers (updateItem,removeItem,appendItem,prependItem) - QueryClient — singleton two-level cache (
baseKey+ serialized params) with stale-time tracking, garbage collection, and active observer registry - QueryClientProvider —
InheritedWidgetfor injectingQueryClientand globalQueryDefaultsinto the widget tree - QueryProvider / InfiniteQueryProvider —
StatefulWidgetwrappers with remount detection viaTickerModeforIndexedStackandVisibilitysupport - QueryBuilder / InfiniteQueryBuilder — reactive builders that rebuild on state changes
- QueryListener / MultiQueryListener — side-effect widgets that respond to success and error without rebuilding the tree
- QueryState — Freezed-based immutable state with
status,fetchStatus,isStale, and convenience getters - NetworkConnectivityObserver — true L7 connectivity verification (HTTP HEAD), debounced events (500ms), lazy initialization
- QueryDefaults — global configuration for stale time, gc time, retry count, retry delay, refetch interval, network mode, error transform, and logging
- QueryLogger — opt-in structured logging with customizable handlers