Cached and fresh
Every widget that uses a key shares one cache entry and one request. Stale data stays on screen while it refetches in the background, and a refetch that returns the same items keeps them identical, so list rows don’t rebuild.
// A QueryObserver<List<Todo>>, with no type arguments.final todos = Query.use( queryKey: ['todos'], queryFn: (_) => api.getTodos(),);
QueryBuilder( query: todos, builder: (context, state) => switch (state.data) { final data? => TodoList(data), // a List<Todo> null => const CircularProgressIndicator(), },)Cached and fresh
Every widget that uses a key shares one cache entry and one request. Stale data stays on screen while it refetches in the background, and a refetch that returns the same items keeps them identical, so list rows don’t rebuild.
Drops into your app
Start with one screen. Creating a query needs no BuildContext, and there
is nothing to replace.
Runs where your code runs
A pure Dart core, so widgets, cubits, services, CLIs, and servers use the same query object.
No type arguments
Types are inferred from your query and mutation functions, all the way to widgets, results, and callbacks. No code generation either.
Built for real networks
Retries with backoff, refetches when the app resumes, pauses while offline, and cancels requests nobody needs anymore without reporting them as errors. Polls until a condition holds, and folds a stream into the cache.
Persisted and inspectable
Keep queries across restarts with any key-value storage, and inspect the cache inside the running app, on a device.
Tested. Both packages have 100% line coverage, enforced in CI on the
oldest supported Flutter and the latest. The core’s tests run under fake time, and a
regression suite keeps fixed edge cases fixed. Every timer is cancelled on its destroy
path and time comes from package:clock, so your own fake_async
and widget tests don’t leak timers. See Testing.
Fuery’s caching and refetching model is inspired by TanStack Query.