What is Qora?
Qora is a server-state library for Flutter and Dart. It fetches, caches and synchronizes the data your app loads from an API, and keeps the app usable when the network is not.
If you know TanStack Query, you already know the model: server data is a cache with a lifecycle, not global UI state. Qora brings that model to Dart 3 with sealed classes and pattern matching, and no code generation.
The problem it solves
Data that comes from a server is not like a counter or a form field. It can be stale, it is shared between screens, it changes when someone writes to it, and it disappears when the network does. Handled by hand, every screen ends up with the same code:
class _UserScreenState extends State<UserScreen> {
User? _user;
Object? _error;
bool _loading = false;
@override
void initState() {
super.initState();
_load();
}
Future<void> _load() async {
setState(() => _loading = true);
try {
_user = await api.getUser(widget.userId); // fetched again on every visit
_error = null;
} catch (e) {
_error = e; // and the previous user is lost on error
} finally {
setState(() => _loading = false);
}
}
// ...and nothing refreshes this screen when another one edits the user.
}
With Qora, the screen declares which data it needs and how to render each state:
QoraBuilder<User>(
queryKey: ['users', userId],
fetcher: () => api.getUser(userId),
builder: (context, state, fetchStatus) => switch (state) {
Success(:final data) => UserCard(data),
Failure(:final error, :final previousData) =>
previousData != null ? UserCard(previousData) : ErrorView(error),
Loading(:final previousData) =>
previousData != null ? UserCard(previousData) : const LoadingView(),
Initial() => const SizedBox.shrink(),
},
)
The second visit renders the cached user on the first frame and refreshes it in the background. Two widgets asking for ['users', userId] share one request. After client.invalidate(['users', userId]), every screen showing that user refetches. The compiler checks that every state is handled.
What you get
Fetching and caching
- Stale-while-revalidate: cached data first, fresh data in the background
- Request deduplication and automatic retries with exponential backoff
- Invalidation by exact key, key prefix, predicate or tag
- Infinite queries, dependent queries, polling, prefetching
Offline
PersistQoraClient: cached data saved to storage and restored before the first frame- Queries that pause while offline, report it through
FetchStatus.paused, and resume on reconnect - An offline mutation queue that replays writes in order when the network returns (held in memory; persisted across restarts in 1.4)
Writes
- Mutations with
onMutate/onError/onSuccessfor optimistic updates and rollback - Automatic invalidation after a successful mutation
Tooling
- An in-app DevTools overlay showing every query and mutation in debug builds
Packages
| Package | Use it for |
|---|---|
qora_flutter | Flutter apps. Widgets (QoraScope, QoraBuilder, QoraMutationBuilder, InfiniteQueryBuilder), lifecycle and connectivity managers. Includes qora. |
qora_hooks | Apps using flutter_hooks: useQuery, useMutation, useInfiniteQuery, useFetchStatus. |
qora | Pure Dart: CLIs, servers, shared packages, tests. No Flutter dependency. |
qora_devtools_overlay | The in-app debug panel, as a dev dependency. |
Server state and client state
Qora manages server state: data that lives on a server and is fetched, cached and re-synchronized. It does not replace your state management for client state: the selected tab, form input, the current theme. Keep Riverpod, BLoC or Provider for those; the integration guides show how they fit together.
When not to use Qora
- A read-only app that is always online and never shows the same data twice. A
FutureBuilderis enough. - Real-time data pushed over a socket. You can write socket events into the cache with
setQueryData, but the stream itself is not Qora's job.
For a side-by-side look at cached_query and flutter_query, see the comparison.
Requirements
- Dart 3.5 or later
- Flutter 3.24 or later (for
qora_flutterandqora_hooks)
Next steps
- Installation: add Qora and wrap your app
- Quick Start: a list and a detail screen, then take them offline
- Coming from TanStack Query: the API mapped option by option