v1.3.1 is now available

Server state for Flutter apps that keep working offline

Qora fetches, caches and synchronizes server data, and keeps your app usable when the network is not. Saved data shows on a cold start, queries pause offline and resume on reconnect, and writes made offline are queued and replayed in order.
user_screen.dart
// Cached data on the first frame, kept during refetches and errors.
QoraBuilder<User>(
  queryKey: ['users', userId],
  fetcher: () => api.getUser(userId),
  builder: (context, state, fetchStatus) => Column(children: [
    if (fetchStatus == FetchStatus.paused) const OfflineBanner(),
    if (state.dataOrNull case final user?) UserCard(user),
  ]),
)

// Written offline: shown now, sent when the device reconnects.
QoraMutationBuilder<Comment, String, void>(
  mutator: (text) => api.postComment(text),
  options: MutationOptions(
    offlineQueue: true,
    optimisticResponse: (text) => Comment.draft(text),
    invalidates: [['comments']],
  ),
  builder: (context, state, mutate) => SendButton(onSend: mutate),
)

Offline, built in

Not a refetch-on-reconnect flag: three pieces that work together, wired by one widget.
    Saved data on cold start
    PersistQoraClient writes successful fetches to your storage and restores them before the first frame. Stale entries show immediately and revalidate in the background.
    Queries that know they are offline
    Queries pause while the device is offline and report it through FetchStatus.paused, so the UI can tell "no network" from "loading". They resume on reconnect, with jitter to avoid a thundering herd.
    Writes that wait for the network
    Mutations made offline return an optimistic result right away and are replayed in order on reconnect. The queue is held in memory today; persisting it across app restarts is the focus of 1.4.

Everything else you expect from a query library

    Stale-while-revalidate
    Cached data renders on the first frame; fresh data replaces it in the background.
    Request deduplication
    Ten widgets watching the same key share one cache entry and one network call.
    Precise invalidation
    By exact key, by key prefix, by predicate, or by tag. Mounted queries refetch automatically.
    Optimistic updates
    Update the cache in onMutate, restore the snapshot in onError, invalidate on success.
    Two-axis state
    A sealed QoraState you can match exhaustively, plus a separate FetchStatus. Previous data is never lost on refetch or error.
    Infinite queries
    Cursor or page based pagination with a maxPages memory window.
    Builders or hooks
    QoraBuilder widgets, or useQuery and useMutation with qora_hooks.
    In-app DevTools
    A debug-only overlay that shows every query, its state, its staleness and the mutation queue.
    Pure Dart core
    No Flutter dependency and no code generation. Works in CLIs, servers and tests.

Works with what you already use

Qora owns server data. Keep your state management for UI state, your HTTP client for transport.
    Riverpod
    Expose Qora queries to providers.
    BLoC
    Feed cubits and blocs from the cache.
    Dio and GraphQL
    Map transport errors, refresh tokens, wrap graphql and ferry.
    Coming from another library
    API mapping from cached_query, flutter_query and TanStack Query.

Your first query in five minutes

Add one package, wrap your app with QoraScope.auto, and bind a query to a widget. No code generation, no provider scaffolding.
Copyright © 2026