flutter_query 0.11.0
flutter_query: ^0.11.0 copied to clipboard
Fetch, cache, and sync server data with automatic state management and background updates, inspired by TanStack Query.
0.11.0 - 2026-07-12 #
-
Breaking: The
seedandseedUpdatedAtoptions onuseQuery,useInfiniteQuery,QueryOptions,InfiniteQueryOptions, and theQueryClientfetch/prefetch/ensure methods now take the sealed typesSeed<TData>andSeedUpdatedAt, which add a lazy callback form alongside the plain value form.Seed application semantics have also changed: a seed (either form) is applied only while the query has no data yet. Previously, a seed could overwrite data already in the cache — including fetched data — when the seed value differed or carried a newer
seedUpdatedAt; now cached data is never overwritten by a seed.// Before: useQuery(['user'], fetchUser, seed: cachedUser); // After: useQuery(['user'], fetchUser, seed: Seed.value(cachedUser)); // Or lazily — invoked only when the query has no data yet; returning // null skips seeding: useQuery( ['user', id], fetchUser, seed: Seed.lazy(() => client .getQueryData<List<User>>(['users']) ?.firstWhereOrNull((user) => user.id == id)), seedUpdatedAt: SeedUpdatedAt.lazy( () => client.getQueryState(['users'])?.dataUpdatedAt, ), );On Dart 3.12+ the constructors work with dot shorthand:
seed: .value(cachedUser)/seed: .lazy(() => ...). -
Breaking: The
placeholderoption onuseQuery,useInfiniteQuery,QueryOptions, andInfiniteQueryOptionsnow takes the sealed typePlaceholder<TData>, which adds a lazy callback form alongside the plain value form.// Before: useQuery(['user'], fetchUser, placeholder: User.anonymous()); // After: useQuery(['user'], fetchUser, placeholder: Placeholder.value(User.anonymous())); // Or lazily — the callback receives the data of the last query the hook // had data for (e.g. before the query key changed); returning null shows // no placeholder: useQuery( ['results', page], fetchResults, placeholder: Placeholder.lazy((previous) => previous), );The keep-previous-data pattern shown above is also available as the constant
Placeholder.keepPrevious, which keeps the previous query key's data on screen while the new query fetches:useQuery(['results', page], fetchResults, placeholder: Placeholder.keepPrevious);On Dart 3.12+ the constructors work with dot shorthand:
placeholder: .value(user)/placeholder: .keepPrevious.Note: the name
Placeholdercollides with Flutter'sPlaceholderwidget. In files that use flutter_query'sPlaceholder, import Flutter withimport 'package:flutter/material.dart' hide Placeholder;(or refer to the widget through an import prefix). -
Breaking: Graduated the sealed snapshot API out of
experiments.dartand made it the main API.useQuery,useInfiniteQuery, anduseMutation(and their options-object counterparts) now return the pattern-matchableQuerySnapshot,InfiniteQuerySnapshot, andMutationSnapshotrespectively, in place of the flatQueryResult/InfiniteQueryResult/MutationResultclasses, which have been removed along with thepackage:flutter_query/experiments.dartentry point.// Before: import 'package:flutter_query/experiments.dart'; import 'package:flutter_query/flutter_query.dart' hide useQuery, useMutation, useInfiniteQuery; // After — a single import: import 'package:flutter_query/flutter_query.dart'; final result = useQuery(['greeting'], fetchGreeting); return switch (result) { QuerySuccess(:final data) => Text(data), QueryPending() => const Text('Loading...'), QueryError(:final error) => Text('Error: $error'), };A
switchover a snapshot is checked for exhaustiveness,datais non-nullable on the success variant, anderroris non-nullable on the error variant.Migrating from the flat API: read last-known data via
dataOrNull(instead ofdata); replacestatus == QueryStatus.successwithisSuccess(and likewiseisPending/isError); read a definiteerrorby matching theQueryErrorvariant. Placeholder data now presents as aQuerySuccesswithisPlaceholder: true.refetch()/fetchNextPage()/fetchPreviousPage()now complete with the corresponding snapshot type. -
Fixed a "
ValueNotifierused after being disposed" crash when a widget observing a query unmounted between a build-phase update and the deferred post-frame callback that delivers it — for example, scrolling a list whose items share a query key. The deferred write now bails when the observing element is no longer mounted. AffectsuseQuery,useInfiniteQuery, and theiruseQueryOptions/useInfiniteQueryOptionsforms.
0.10.0 - 2026-06-25 #
-
Added an optional
shouldRebuildparameter touseQueryanduseInfiniteQuery. It is abool Function(TResult previous, TResult next)predicate that decides, per result update, whether the observing widget rebuilds — returningtrueto rebuild orfalseto suppress. When omitted, the widget rebuilds on every change, exactly as before.// Rebuild only when the data changes; ignore background-fetch flips. useQuery( ['todos'], fetchTodos, shouldRebuild: (previous, next) => previous.data != next.data, );previousis always the last accepted result (the value the widget is currently showing), so a suppressed update is invisible to later comparisons.(_, __) => falsesubscribes and fetches without ever rebuilding. The experimental variants type the predicate overQuerySnapshot/InfiniteQuerySnapshot.This mirrors the
shouldRebuild/buildWhenpredicates already established byproviderandflutter_bloc.
0.9.0 - 2026-06-24 #
-
Expanded the experimental, pattern-matchable snapshot API (opt in via
import 'package:flutter_query/experiments.dart';) to cover mutations and infinite queries alongsideuseQuery:useMutationnow returns asealedMutationSnapshotwith four variants matchingMutationStatus(MutationIdle,MutationPending,MutationSuccess,MutationError), exposing non-nullvariableson every variant except idle.useInfiniteQuerynow returns asealedInfiniteQuerySnapshotwithInfiniteQueryPending,InfiniteQuerySuccess, andInfiniteQueryErrorvariants, with page-level fetch flags on the base class.
Because the experimental library reuses the canonical hook names, hide them when importing it alongside the main library:
import 'package:flutter_query/flutter_query.dart' hide useQuery, useMutation, useInfiniteQuery; import 'package:flutter_query/experiments.dart';As part of this work,
QuerySnapshotnow exposes the activity axis via a singlefetchStatusenum, withisFetching/isPaused/isIdleretained as derived conveniences. These types remain experimental and may change in a future minor release.
0.8.0 - 2026-06-23 #
-
Added an experimental, Dart-idiomatic query API, opt in via
import 'package:flutter_query/experiments.dart';. It exposes auseQuerythat returns aQuerySnapshot: asealedhierarchy (QueryPending,QuerySuccess,QueryError) supporting exhaustiveswitchmatching, with non-nullabledata/erroron the success/error variants and the activity axis exposed viaisFetching/isPaused/isIdle.import 'package:flutter_query/flutter_query.dart' hide useQuery; import 'package:flutter_query/experiments.dart'; final snapshot = useQuery(['user', id], () => fetchUser(id)); final widget = switch (snapshot) { QueryPending() => const CircularProgressIndicator(), QuerySuccess(:final data) => Text(data.name), QueryError(:final error) => Text('$error'), };This API is annotated
@experimentaland may change in a future minor release.
0.7.0 - 2026-03-08 #
-
Fixed crashes when multiple screens share the same query key (#40).
-
Added
ensureQueryDatamethod toQueryClient. This method returns cached data if available (even if stale) or fetches it if missing. It also supports arevalidateIfStaleoption to trigger a background refetch if the cached data is stale. -
Exposed
MutateFntypedef. -
BREAKING: Restored
RefetchTypeenum.invalidateQueries()is now async again and accepts arefetchTypeparameter (defaults toRefetchType.active) to control which queries are automatically refetched after invalidation.// Invalidate and refetch active queries (default) await client.invalidateQueries(queryKey: ['users']); // Invalidate without refetching client.invalidateQueries( queryKey: ['users'], refetchType: RefetchType.none, );
0.6.0 - 2026-02-03 #
This release contains breaking changes to improve API consistency and usability.
-
Added
useMutationStatehook that returns mutation states from the mutation cache. Useful for tracking mutation progress across your application.// Get all mutation states final states = useMutationState(); // Filter by mutation key final todoMutations = useMutationState(mutationKey: ['todos']); // Filter with predicate (e.g., pending mutations) final pending = useMutationState( predicate: (key, state) => state.status == MutationStatus.pending, ); -
Added
networkModeoption touseQuery,useInfiniteQuery, anduseMutationfor controlling behavior based on network connectivity. Requires passing aconnectivityChangesstream toQueryClient.NetworkMode.online(default): Pauses when offline, resumes when onlineNetworkMode.always: Never pauses, ignores network stateNetworkMode.offlineFirst: First execution runs regardless of network, retries pause when offline
-
Added
useIsFetchinghook that returns the count of queries currently fetching. -
Added
useIsMutatinghook that returns the count of mutations currently pending. -
BREAKING: The
mutatefunction returned byuseMutationis now fire-and-forget. It returnsvoidand does not throw errors. UsemutateAsyncto await the result or handle errors directly.// Before final result = useMutation(...); try { final data = await result.mutate(variables); } catch (e) { // handle error } // After (fire-and-forget) result.mutate(variables); // returns void, errors handled via onError callback // After (async with error handling) try { final data = await result.mutateAsync(variables); } catch (e) { // handle error } -
Added
refetchOnReconnectoption touseQueryanduseInfiniteQueryfor controlling refetch behavior when network connectivity is restored. Requires passing aconnectivityChangesstream toQueryClient. -
Added
metaproperty toDefaultQueryOptionsandDefaultMutationOptionsfor setting default metadata across all queries and mutations. This metadata is deep-merged with query/mutation-specific meta and observer meta. -
BREAKING: Simplified
getQueryData<TData>()andgetInfiniteQueryData<TData, TPageParam>()signatures by removing theTErrorgeneric type parameter. The error type is not needed when retrieving cached data.// Before final data = client.getQueryData<String, Error>(const ['key']); final infiniteData = client.getInfiniteQueryData<String, Error, int>(const ['key']); // After final data = client.getQueryData<String>(const ['key']); final infiniteData = client.getInfiniteQueryData<String, int>(const ['key']); -
BREAKING:
QueryCacheandMutationCacheare no longer part of the public API. ThecacheandmutationCacheconstructor parameters have been removed fromQueryClient. The caches are now created and managed internally. -
BREAKING:
Query,Mutation,QueryObserver, andMutationObserverare no longer part of the public API. -
BREAKING: Renamed
InfiniteQueryObserverOptionstoInfiniteQueryOptions. -
Fixed gc timer to start after fetch completes rather than at query creation time.
-
Added
resetQueriesmethod onQueryClientfor resetting queries to their initial state. UnlikeinvalidateQueries(which marks queries as stale),resetQueriescompletely resets the query state - queries with seed data are reset to that seed, while queries without seed have their data cleared. Active queries are automatically refetched after reset. -
Added
removeQueriesmethod onQueryClientfor removing queries from the cache. UnlikeinvalidateQueries, removed queries are completely discarded and must be fetched from scratch when accessed again. -
Added
getQueryStatemethod onQueryClientfor retrieving the full query state (status, error, dataUpdatedAt, etc.) instead of just the data. -
Added
setQueryDatamethod onQueryClientfor imperatively setting or updating cached query data. Useful for optimistic updates in mutation callbacks. -
BREAKING:
fetchQuery,prefetchQuery,fetchInfiniteQuery, andprefetchInfiniteQuerynow takequeryKeyandqueryFnas positional parameters instead of named parameters.// Before await client.fetchQuery<String, Object>( queryKey: const ['users', id], queryFn: (context) async => fetchUser(id), ); // After await client.fetchQuery<String, Object>( const ['users', id], (context) async => fetchUser(id), ); -
BREAKING: Predicate callbacks now receive immutable
QueryStateandMutationStateobjects instead ofQueryandMutationinstances, with the key passed as a separate first parameter.// Before client.invalidateQueries(predicate: (query) => query.key.first == 'users'); // After client.invalidateQueries(predicate: (key, state) => key.first == 'users'); -
BREAKING:
MutationFunctionContext.metais now a non-nullableMap<String, dynamic>(defaults to an empty map when not provided in options). -
BREAKING: The
queryClientparameter onuseQuery,useMutation, anduseInfiniteQueryhooks has been renamed toclientfor simplicity. -
BREAKING:
QueryClientProviderAPI changed. Theclientparameter has been replaced with acreatefactory function. UseQueryClientProvider.value()for existing clients.// Before QueryClientProvider( client: queryClient, child: MyApp(), ) // After (managed lifecycle) QueryClientProvider( create: (context) => QueryClient(), child: MyApp(), ) // After (existing client) QueryClientProvider.value( queryClient, child: MyApp(), ) -
QueryClientProvidernow supports lazy initialization via thelazyparameter -
QueryClientProvidernow automatically clears theQueryClientwhen removed from the widget tree (when using the default constructor)
0.5.1 - 2026-01-14 #
This release adds support for infinite/paginated queries.
useInfiniteQueryhook for paginated data fetching with automatic page accumulationfetchNextPageandfetchPreviousPagefor bidirectional paginationhasNextPageandhasPreviousPagestate for pagination controlsmaxPagesoption to limit the number of accumulated pages- Full support for all standard query options (caching, refetching, retry, etc.)
0.4.0 - 2026-01-08 #
This release aligns the API with TanStack Query v5.
useQueryhook for data fetching with caching, refetching, and cancellationuseMutationhook for mutations with optimistic updatesuseQueryClienthook for imperative cache operations- Client-level default options for queries and mutations
0.3.7 and earlier #
Legacy codebase. Not maintained.