Core Concepts

Offline & Network

What Qora does with reads and writes while the device is offline, what happens on reconnect, and what survives an app restart.

Most libraries treat "offline" as one more error. Qora treats it as a state: reads pause instead of failing, writes can be queued, and both resume on their own when the network returns. This page describes the model; the Network-Aware recipe covers configuration in detail.

How Qora knows the device is offline

The client listens to a ConnectivityManager. QoraScope.auto attaches FlutterConnectivityManager, built on connectivity_plus, so a standard Flutter setup needs nothing more.

Two consequences worth knowing:

  • Without a connectivity manager, the client assumes it is online. Nothing pauses, and nothing replays on reconnect. A pure Dart app can attach its own with client.attachConnectivityManager(...).
  • Connectivity means a network interface, not a reachable server. On a captive portal or with the API down, the device counts as online: requests run, are retried, and end in Failure.

Two axes: what you have, and what is happening

A query has a state and a fetch status, and offline handling lives in the second one:

  • QoraState is what you have: Initial, Loading, Success, Failure, each carrying data when there is some.
  • FetchStatus is what the engine is doing: fetching, paused (waiting for the network) or idle.

QoraBuilder passes both to your builder. The usual offline combinations:

StatefetchStatusMeaning
Loading, previousData setpausedOffline, cached data available, refetch waiting
Loading, no previousDatapausedOffline and nothing cached: show an offline screen, not a spinner
SuccesspausedOffline in offlineFirst mode: cached data served, refetch waiting
SuccessidleFresh data, nothing to do

A fetch started while offline does not throw inside widgets. Called imperatively, fetchQuery returns the cached data if there is some, and otherwise throws QoraOfflineException.

Reads: NetworkMode

networkMode decides what a query does when it must fetch while offline. Set it per query in QoraOptions, or for every query in defaultOptions.

ModeWhile offlineOn reconnect
online (default)The fetch is not sent. State becomes Loading(previousData: cached), status pausedThe fetch runs
offlineFirstWith cached data: state stays Success, status paused. Without: same as onlineThe fetch runs
alwaysThe fetch is sent anyway, and is retried and fails like any errorNothing special

Use always for fetchers that do not need the network, such as a local database. Use offlineFirst when a screen should never flip to a loading state just because the device went offline.

Writes: the offline mutation queue

A mutation does not pause. Without opt-in, a mutation called while offline sends its request and fails. With offlineQueue: true in its MutationOptions, it is queued instead:

mutate(variables) while offline
  1. onMutate runs now           → optimistic cache updates work offline
  2. the call is added to the queue
  3. state: MutationSuccess(isOptimistic: true) if optimisticResponse is set,
            MutationPending otherwise

reconnect
  4. the call is sent, once, in the order it was queued
     success → onSuccess, invalidates, onSettled
     failure → onError(error, variables, context from step 1), onSettled

Three properties matter in practice:

  • The queue belongs to the client, not to the widget. A queued write is sent even if the screen that made it was closed.
  • A failed replay is not retried. onError runs with the snapshot from onMutate, so the rollback you wrote for the online case also covers this one. Tell the user there.
  • Order is preserved, dependencies are not checked. If a queued "create" is rejected, a queued "update" of the same item is still sent, and fails in turn.

optimisticResponse only sets the mutation's own state, for example to show a "queued" badge. Changes to cached query data belong in onMutate.

QoraMutationBuilder and useMutation both use the queue (useMutation since qora_hooks 1.3.1). A raw MutationController needs isOnline and offlineQueue passed explicitly.

Reconnect

When the connectivity manager reports the network is back, the client:

  1. Replays paused queries in batches. ReconnectStrategy sets the batch size and a random delay between batches (default: 5 at a time, up to 100 ms), so a hundred paused queries do not hit the server at once.
  2. Then replays the mutation queue, after the reads, one mutation at a time.

Independently, QoraScope.auto also refetches stale on-screen queries when the app returns to the foreground.

Across restarts

WhatSurvives a restart?
Cached query dataYes, with PersistQoraClient and a StorageAdapter. See Persistence
Queued mutationsNo. The queue lives in memory; persisting it is planned for 1.4

An app that must not lose writes made offline before being killed should, for now, also save those writes itself (for example to a local outbox table) and resend them at startup.

Next Steps

Copyright © 2026