Getting Started

What is Qora?

Qora fetches, caches and synchronizes server data in Flutter and Dart, and keeps your app usable when the network is not.

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:

without_qora.dart
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:

user_screen.dart
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 / onSuccess for 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

PackageUse it for
qora_flutterFlutter apps. Widgets (QoraScope, QoraBuilder, QoraMutationBuilder, InfiniteQueryBuilder), lifecycle and connectivity managers. Includes qora.
qora_hooksApps using flutter_hooks: useQuery, useMutation, useInfiniteQuery, useFetchStatus.
qoraPure Dart: CLIs, servers, shared packages, tests. No Flutter dependency.
qora_devtools_overlayThe 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 FutureBuilder is 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_flutter and qora_hooks)

Next steps

Copyright © 2026