Third Party

With Provider

Add Qora to an existing Provider app to specialize server-state handling while preserving Provider for UI state and dependency injection.

Provider and ChangeNotifier are effective for UI state and dependency injection. Server data management introduces repetitive boilerplate in each notifier: isLoading, error, manual cache guards, and inconsistent retry handling.

Qora takes over that specific responsibility. Provider keeps everything else.


The Problem with ChangeNotifier for Server Data

A typical data-fetching notifier:

lib/providers/user_provider.dart
class UserNotifier extends ChangeNotifier {
  User? data;
  bool isLoading = false;
  Object? error;
  int? _loadedId;

  Future<void> fetchUser(int id) async {
    if (_loadedId == id && data != null) return; // manual cache, no TTL, no SWR
    isLoading = true;
    error = null;
    notifyListeners();
    try {
      data = await UserApi.getUser(id);
      _loadedId = id;
    } catch (e) {
      error = e; // no retry, no backoff
    } finally {
      isLoading = false;
      notifyListeners();
    }
  }
}
lib/user_screen.dart
Consumer<UserNotifier>(
  builder: (context, notifier, _) {
    if (notifier.isLoading) return const CircularProgressIndicator();
    if (notifier.error != null) return ErrorWidget('${notifier.error}');
    if (notifier.data == null) {
      notifier.fetchUser(userId); // side effect in build, fragile lifecycle
      return const SizedBox.shrink();
    }
    return UserCard(notifier.data!);
  },
)

This pattern is written identically for every API call. It also misses: stale-while-revalidate, background refetch on focus/reconnect, request deduplication, and offline support.


After: Let Qora Own Server Data

lib/main.dart
final qoraClient = QoraClient(
  config: const QoraClientConfig(
    defaultOptions: QoraOptions(staleTime: Duration(minutes: 5)),
  ),
);

runApp(
  MultiProvider(
    providers: [
      ChangeNotifierProvider(create: (_) => ThemeNotifier()),
      ChangeNotifierProvider(create: (_) => AuthNotifier()),
    ],
    child: QoraScope(
      client: qoraClient,
      lifecycleManager: FlutterLifecycleManager(qoraClient: qoraClient),
      connectivityManager: FlutterConnectivityManager(),
      child: const MyApp(),
    ),
  ),
);
lib/user_screen.dart
QoraBuilder<User>(
  queryKey: ['users', userId],
  fetcher: () => UserApi.getUser(userId),
  builder: (context, state, fetchStatus) => switch (state) {
    Success(:final data) => UserCard(data),
    Loading() => const CircularProgressIndicator(),
    Failure(:final error) => ErrorWidget('$error'),
    Initial() => const SizedBox.shrink(),
  },
)

The Qora version removes manual loading flags, notifier notifications, and custom cache guards. Retry, request deduplication, background revalidation, and SWR behavior are built in.

The third builder argument fetchStatus distinguishes FetchStatus.fetching from FetchStatus.paused in offline conditions. Use this state to render a waiting-for-connection indicator instead of a loading spinner.


Division of Responsibilities

ResponsibilityKeep in ProviderMove to Qora
Data fetched from an API (users, products, feeds)QoraBuilder + fetcher:
Mutations (create, update, delete)QoraMutationBuilder
UI state (selected tab, modal open, form values)ChangeNotifier
Authentication session (token in memory)ChangeNotifier
Dependency injection (services, repositories)Provider / ChangeNotifierProvider

Key API Mapping

Provider patternQora equivalent
ChangeNotifier + fetch() methodQoraBuilder with fetcher:
Consumer + ternary on isLoading / error / dataswitch (state) on QoraState
Manual if (data != null) returnstaleTime in QoraOptions
ChangeNotifierProvider (data layer)QoraScope
notifyListeners() after writeclient.invalidate(key)

Checklist

  • Add qora_flutter to pubspec.yaml and run flutter pub get
  • Nest QoraScope inside existing MultiProvider root (not a replacement)
  • Identify ChangeNotifier classes that only fetch and emit loading/error/data and replace them with QoraBuilder (fetcher:, 3-arg builder)
  • Replace Consumer loading/error/data ternaries with switch (state)
  • Replace manual cache guards with staleTime
  • Replace notifyListeners() post-write with client.invalidate(key)
  • Keep ChangeNotifierProvider entries for UI state, auth, and DI

Copyright © 2026