TanStack Query-inspired data fetching, caching, retries, mutations, and reactive state for Dart and Flutter.
| Package | Purpose |
|---|---|
tanquery |
Pure-Dart query and mutation core. It provides QueryClient, QueryCache, QueryObserver, retries, hydration, storage, streamed queries, and cache utilities. |
tanquery_flutter |
Flutter integration with DartQueryProvider, QueryBuilder, MutationBuilder, InfiniteQueryBuilder, and QueriesBuilder. |
tanquery_devtools |
Optional runtime overlay for inspecting query and mutation state in a debug build. |
All packages currently use version 0.8.0. The workspace requires Dart 3.5 or newer. The Flutter packages require Flutter 3.24 or newer and are verified against the stable Flutter channel by CI.
The core package has no Flutter dependency and can be used from a Dart server, command-line application, or any other Dart environment:
dependencies:
tanquery: ^0.8.0import 'package:tanquery/tanquery.dart';
Future<List<String>> fetchTodos() async {
return ['Buy milk', 'Walk the dog'];
}
Future<void> main() async {
final client = QueryClient();
final todos = await client.fetchQuery<List<String>>(
queryKey: QueryKey(['todos']),
queryFn: fetchTodos,
staleTime: const Duration(minutes: 5),
);
print(todos);
}fetchQuery returns fresh cached data when available and fetches stale or missing data. Use prefetchQuery to warm the cache without propagating errors, ensureQueryData to return cached data while optionally revalidating it, and setQueryData for direct cache updates. Freshness accepts either a Duration or StaleTime.static_; dehydrated and stored snapshots preserve that policy.
Add the Flutter adapter:
dependencies:
tanquery_flutter: ^0.8.0Provide one client above the widgets that use tanquery:
import 'package:flutter/material.dart';
import 'package:tanquery_flutter/tanquery_flutter.dart';
void main() {
final client = QueryClient();
runApp(
DartQueryProvider(
client: client,
child: const MaterialApp(home: TodosPage()),
),
);
}
class TodosPage extends StatelessWidget {
const TodosPage({super.key});
@override
Widget build(BuildContext context) {
return QueryBuilder<List<String>>(
queryKey: QueryKey(['todos']),
queryFn: () async => ['Buy milk', 'Walk the dog'],
builder: (context, state) {
if (state.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (state.isError) {
return Center(child: Text('Error: ${state.error}'));
}
return ListView(
children: [
for (final todo in state.data ?? <String>[]) ListTile(title: Text(todo)),
],
);
},
);
}
}DartQueryProvider mounts the client, forwards Flutter app lifecycle changes to the focus manager, and unmounts the client when it is disposed. QueryBuilder manages a QueryObserver and exposes the observer’s QueryObserverResult to the builder.
| Widget | Use it when | Main inputs |
|---|---|---|
QueryBuilder<T> |
One reactive query | queryKey, queryFn, builder, staleTime, enabled, meta, retry and placeholder options |
| MutationBuilder<TData, TVariables> | A create, update, or delete operation | mutationFn, mutation callbacks, and a builder receiving mutate and mutateAsync |
| InfiniteQueryBuilder<TPage, TParam> | Page-based or cursor-based lists | queryFn, initialPageParam, page callbacks, separate page loading flags, and page errors |
| QueriesBuilder | Several independent queries | A list of QueryConfig values and an ordered result list |
For complete constructor signatures and examples, see the tanquery_flutter README.
Add the optional overlay in debug builds:
dependencies:
tanquery_devtools: ^0.8.0import 'package:flutter/foundation.dart';
import 'package:tanquery_devtools/tanquery_devtools.dart';
DartQueryProvider(
client: client,
child: DartQueryDevtools(
enabled: kDebugMode,
child: const MaterialApp(home: TodosPage()),
),
)The overlay lists cached queries and mutations, supports key and status filtering, and provides query detail actions for invalidation, refetching, resetting, and removal. Fresh/stale status uses each query’s configured staleTime; query details also show the freshness policy and remaining fresh time. A header refresh button refetches active queries, and optional pollInterval enables periodic active-query refreshes. See the tanquery_devtools README for the complete UI description.
The pure-Dart example is executable:
dart run packages/tanquery/example/tanquery_example.dartThe Flutter and devtools examples are package entrypoint examples rather than standalone Flutter applications with their own pubspec.yaml. They are compile-checked through the workspace analyzer:
flutter analyze packages/tanquery_flutter/example/tanquery_flutter_example.dart
flutter analyze packages/tanquery_devtools/example/tanquery_devtools_example.dartRun the complete verification suite from the repository root:
dart format --output=none --set-exit-if-changed packages
dart analyze .
dart test packages/tanquery
flutter test packages/tanquery_flutter
flutter test packages/tanquery_devtoolsThe repository’s CI workflow runs the same formatting, analysis, and test checks against the current stable Flutter channel.
The workspace is intentionally layered. tanquery contains the framework-independent cache, query, mutation, retry, hydration, persistence, and streaming logic. tanquery_flutter adapts QueryObserver and MutationObserver to Flutter widget lifecycles. tanquery_devtools consumes the public cache APIs to render an optional debugging overlay. The core package does not import Flutter.
Read CONTRIBUTING.md before submitting changes. New behavior should include a focused test, public API changes should update the relevant package README and changelog, and generated .dart_tool, build, and coverage artifacts should not be committed.
MIT