Skip to main content

query_kitfor Dart and Flutter

Server data in Flutter, cached and kept fresh

A port of TanStack Query: queries, mutations and infinite lists for Dart, with a Flutter binding that needs nothing but Flutter. You describe where the data comes from; the cache decides when to fetch, share, refresh and forget it.

flutter pub add query_kit_flutter
task_list.dart
QueryObserverOptions<List<Task>> tasksQuery() => QueryObserverOptions(
queryKey: QueryKey(<Object?>['tasks']),
queryFn: (context) => api.listTasks(signal: context.signal),
);

class TaskList extends StatelessWidget {
const TaskList({super.key});


Widget build(BuildContext context) {
final tasks = context.query(tasksQuery());

return switch (tasks) {
QueryPending() => const CircularProgressIndicator(),
QueryError(:final error) => Text('Could not load: $error'),
QuerySuccess(:final data) => ListView(
children: <Widget>[
for (final task in data) Text(task.name),
],
),
};
}
}

Server state is not app state.

A selected tab or a half-typed form is yours: it stays where you left it. The orders on a server are not. Someone else can change them, your copy ages while the user reads it, three screens want it at once, and the network drops at the worst moment.

A FutureBuilder gets the first load on screen. What comes after is the real work — caching, sharing one request between readers, refreshing when the app comes back, retrying, cancelling, invalidating after a write, paging. query_kit does that work the way TanStack Query does it on the web, so a screen only says what it shows.

The longer version

A write on screen before the server answers.

The showcase's optimistic-updates screen, running in this page against an in-memory backend. Add a todo and it is in the list at once. Then switch on Refuse next write, choose Via cache and add another: the row appears, the server refuses it, and the cache rolls back.

Live demoOptimistic updatesShow the write before the server answers — two ways.~3 MB, runs in your browser; no server involved.

The work after the first load, done by the cache.

Caching and deduplication
Five widgets reading one key make one request and share its answer.
Stale-while-revalidate
A second visit renders from the cache at once, and refetches behind it when the data is older than its staleTime.
Refetch on focus and reconnect
The app returning to the foreground, or the network returning, refreshes what is on screen.
Mutations and invalidation
A write names the keys it made stale, and whatever shows them fetches again.
Optimistic updates
Show the write before the server answers, and roll it back when the server refuses.
Infinite and paginated lists
Pages fetched in either direction, kept under one key, refetched in order.
Retries and cancellation
Failed fetches retry with backoff; a fetch whose function reads context.signal is cancelled when its last reader leaves.
Offline-aware
Told when the device is offline, queries wait for the network instead of failing, and writes made offline pause until it is back.
Four equal ways to read
context.query, QueryBuilder, QueryMixin or a ValueListenable. None of them is the default.
Sealed results
A switch over QueryPending, QueryError and QuerySuccess is exhaustive, so there is no data!.
A pure-Dart core
The cache runs without Flutter: in a CLI, on a server, in a shared package.
Nothing but Flutter
No hooks, signals or connectivity package required. Connectivity is a Stream<bool> you bring yourself.

Upstream's own tests say it behaves the same.

TanStack Query's test suite is ported alongside the code, under upstream's own test names, so the two files read side by side. Every case that is not ported is listed with its reason, and where the Dart port differs on purpose, the differences are written down as behaviour.

Thank you to Tanner Linsley and to everyone who has built and maintained TanStack Query. Every good idea here is theirs, published under their MIT licence, whose notice each package carries in LICENSE-TANSTACK.

It is not theirs. Not affiliated with, endorsed by, reviewed by, or connected in any way to Tanner Linsley, the TanStack team, or the TanStack organisation. Please do not take problems with this package to them — they belong here.

StartQuick start BrowseExamples ReadOverview