qora_flutter
Flutter integration for Qora, the server-state layer for apps that have to keep working offline. Bind server data to your UI with one widget: cached data on the first frame, background refresh, refetch on resume and on reconnect.
Part of the Qora monorepo.
Features
| Feature | Description |
|---|---|
| Declarative UI | QoraBuilder renders cached data on the first frame and keeps it during refetches and errors |
| Lifecycle aware | Stale queries refetch when the app returns to the foreground |
| Network aware | Queries pause while offline and refetch when the connection is restored |
| Offline writes | QoraMutationBuilder queues mutations made offline and replays them on reconnect |
| Zero boilerplate | Loading, error, and data states handled in one place |
Install
dependencies:
qora_flutter: ^1.3.0
qora_flutter automatically includes qora and connectivity_plus as dependencies; no extra packages needed.
Setup
Wrap your app with QoraScope.auto once. It creates and owns the lifecycle and connectivity managers:
import 'package:flutter/foundation.dart';
import 'package:qora_flutter/qora_flutter.dart';
void main() {
final client = QoraClient(
config: const QoraClientConfig(debugMode: kDebugMode),
);
runApp(QoraScope.auto(client: client, child: const MyApp()));
}
Pass your own FlutterLifecycleManager / FlutterConnectivityManager to the default QoraScope constructor when you need custom settings.
Fetch and display data
QoraBuilder<T> fetches on mount, caches the result, and rebuilds on every state change:
QoraBuilder<User>(
queryKey: ['users', userId],
fetcher: () => api.getUser(userId),
builder: (context, state, fetchStatus) => switch (state) {
Loading(:final previousData) => previousData != null
? UserCard(user: previousData, isRefreshing: true)
: const CircularProgressIndicator(),
Success(:final data) => UserCard(user: data),
Failure(:final error, :final previousData) => ErrorScreen(
message: error.toString(),
onRetry: () => context.qora.invalidate(['users', userId]),
),
_ => const SizedBox.shrink(),
},
)
Invalidate after a mutation
await api.createPost(payload);
context.qora.invalidate(['posts']);
context.qora.invalidateWhere((k) => k.firstOrNull == 'posts');
Every QoraBuilder subscribed to a matching key re-fetches automatically.
Write while offline
With offlineQueue: true, a mutation triggered offline returns an optimistic result immediately and is replayed in order 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) => switch (state) {
MutationSuccess(:final data, :final isOptimistic) => ListTile(
title: Text(data.text),
trailing: isOptimistic ? const Icon(Icons.schedule) : null,
),
MutationFailure(:final error) => ErrorBanner(error),
_ => TextButton(
onPressed: state.isPending ? null : () => mutate('Nice post'),
child: const Text('Send'),
),
},
)
The queue is held in memory: it survives connectivity changes, not an app restart.
Optimistic updates
final client = context.qora;
final key = ['users', userId];
final snapshot = client.getQueryData<User>(key);
client.setQueryData<User>(key, user.copyWith(name: newName));
try {
final updated = await api.updateUser(userId, newName);
client.setQueryData<User>(key, updated);
} catch (_) {
client.restoreQueryData<User>(key, snapshot);
}
Observe without fetching
QoraStateBuilder<T> mirrors a query's state without triggering a fetch; ideal for badges, counters, or secondary displays:
QoraStateBuilder<List<Notification>>(
queryKey: ['notifications'],
builder: (context, state) {
final count = state.dataOrNull?.length ?? 0;
return Badge(label: Text('$count'), child: const Icon(Icons.notifications));
},
)
Documentation
Full guides and API reference: qora.meragix.dev
Libraries
- qora_flutter
- Flutter integration layer for Qora.