Intégrations tierces

Avec Riverpod

Ajouter Qora à un projet Riverpod pour compléter FutureProvider avec SWR, le comportement hors ligne et les APIs de requête orientées hooks.

Provider et NotifierProvider de Riverpod sont efficaces pour l'injection de dépendances et l'état client. FutureProvider gère les lectures asynchrones mais ne fournit pas par défaut la sémantique SWR, retry/backoff, ni la pause et le rejeu hors ligne.

Qora ajoute ces capacités d'état serveur sans remplacer Riverpod. Riverpod reste concentré sur les graphes de dépendances et l'état client.


Ce que FutureProvider manque

lib/providers/user_provider.dart
final userProvider = FutureProvider.family<User, int>((ref, id) async {
  return UserApi.getUser(id);
});
lib/user_screen.dart
final userAsync = ref.watch(userProvider(userId));

return userAsync.when(
  loading: () => const CircularProgressIndicator(),
  error: (e, _) => ErrorWidget('$e'),
  data: (user) => UserCard(user),
);

Limitations :

  • Spinner plein écran à chaque rafraîchissement: les anciennes données sont supprimées, pas de stale-while-revalidate.
  • Pas de retry avec backoff: un seul échec est affiché immédiatement.
  • Pas de refetch en arrière-plan au focus de l'app ou à la reconnexion.
  • Pas de support hors ligne: pas de FetchStatus, pas de comportement pause/rejeu à la reconnexion.
  • AsyncValue n'a pas de previousData: pas de chemin de dégradation gracieuse.

Chemin avec les hooks

Si flutter_hooks est déjà utilisé (ce que font souvent les développeurs Riverpod via hooks_riverpod), qora_hooks fournit un hook useQuery qui semble natif :

pubspec.yaml
dependencies:
  qora_flutter: ^1.3.1
  qora_hooks: ^1.3.1   # nécessite flutter_hooks
lib/user_screen.dart
class UserScreen extends HookWidget {
  const UserScreen({required this.userId, super.key});
  final int userId;

  @override
  Widget build(BuildContext context) {
    final state = useQuery<User>(
      key: ['users', userId],
      fetcher: () => UserApi.getUser(userId),
      options: const QoraOptions(staleTime: Duration(minutes: 5)),
    );

    return switch (state) {
      Success(:final data) => UserCard(data),
      Loading() => const CircularProgressIndicator(),
      Failure(:final error) => ErrorWidget('$error'),
      Initial() => const SizedBox.shrink(),
    };
  }
}

useQuery retourne QoraState<T>, le même type sealed utilisé dans toutes les APIs Qora. Stale-while-revalidate, retry, refetch en arrière-plan et déduplication sont intégrés.

Dégradation gracieuse avec previousData

Loading et Failure portent previousData, qui est la dernière valeur réussie :

lib/user_screen.dart
final state = useQuery<User>(
  key: ['users', userId],
  fetcher: () => UserApi.getUser(userId),
);

return switch (state) {
  Success(:final data) => UserCard(data),
  Loading(:final previousData) when previousData != null =>
    Stack(children: [
      UserCard(previousData),
      const LoadingOverlay(),
    ]),
  Loading() => const CircularProgressIndicator(),
  Failure(:final error, :final previousData) when previousData != null =>
    Column(children: [
      UserCard(previousData),
      ErrorBanner('$error'),
    ]),
  Failure(:final error) => ErrorWidget('$error'),
  Initial() => const SizedBox.shrink(),
};

Mutations avec useMutation

lib/update_name_form.dart
class UpdateNameForm extends HookWidget {
  const UpdateNameForm({required this.userId, super.key});
  final int userId;

  @override
  Widget build(BuildContext context) {
    final handle = useMutation<User, String>(
      mutator: (name) => UserApi.updateUser(userId, name),
      options: MutationOptions(
        onSuccess: (data, _, __) async {
          QoraScope.of(context).setQueryData(['users', userId], data);
          QoraScope.of(context).invalidateWhere((k) => k.firstOrNull == 'users');
        },
      ),
    );

    return ElevatedButton(
      onPressed: handle.isPending ? null : () => handle.mutate(nameController.text),
      child: handle.isPending
          ? const CircularProgressIndicator()
          : const Text('Enregistrer'),
    );
  }
}

MutationHandle expose : isIdle, isPending, isSuccess, isError, data, error, mutate(variables), mutateAsync(variables), reset().


Alternative avec l'API widget

Pour rester avec StatefulWidget plutôt que HookWidget, QoraBuilder fournit les mêmes capacités :

lib/user_screen.dart
QoraBuilder<User>(
  queryKey: ['users', userId],
  fetcher: () => UserApi.getUser(userId),
  options: const QoraOptions(staleTime: Duration(minutes: 5)),
  builder: (context, state, fetchStatus) {
    if (fetchStatus == FetchStatus.paused) {
      return OfflineBanner(staleData: state.dataOrNull);
    }
    return switch (state) {
      Success(:final data) => UserCard(data),
      Loading() => const CircularProgressIndicator(),
      Failure(:final error) => ErrorWidget('$error'),
      Initial() => const SizedBox.shrink(),
    };
  },
)

Le troisième argument du builder fetchStatus expose FetchStatus.paused en états hors ligne. Utiliser QoraBuilder quand ce signal est requis par l'UI.


Conserver Riverpod pour l'injection de dépendances et l'état client

Provider et NotifierProvider de Riverpod sont inégalés pour l'injection de dépendances et l'état client. Les deux périmètres se composent proprement :

lib/main.dart
final qoraClient = QoraClient(
  config: const QoraClientConfig(debugMode: kDebugMode),
);

runApp(
  ProviderScope(
    overrides: [
      userApiProvider.overrideWithValue(UserApi(baseUrl: env.apiUrl)),
    ],
    child: QoraScope(
      client: qoraClient,
      lifecycleManager: FlutterLifecycleManager(qoraClient: qoraClient),
      connectivityManager: FlutterConnectivityManager(),
      child: const MyApp(),
    ),
  ),
);

Utiliser les providers Riverpod pour injecter UserApi, QoraClient ou les repositories, puis les consommer depuis useQuery ou QoraBuilder.


Comparaison des fonctionnalités

FonctionnalitéFutureProvideruseQuery / QoraBuilder
SWR: servir périmé, refetch en arrière-planNonOui
previousData pendant le refetchNonOui
Retry automatique avec backoffNonOui
Refetch en arrière-plan au focus / reconnexionNonOui
FetchStatus.paused (indicateur hors ligne)NonOui
Persistance hors ligneNonOui
Requêtes infinies / paginéesNonOui
Déduplication des requêtes en coursOuiOui

Checklist

  • Ajouter qora_flutter et qora_hooks à pubspec.yaml
  • Imbriquer QoraScope à l'intérieur de ProviderScope
  • Remplacer FutureProvider.family par useQuery (hooks) ou QoraBuilder (widgets)
  • Remplacer les méthodes de fetch de données de AsyncNotifier par useMutation ou QoraMutationBuilder
  • Conserver NotifierProvider de Riverpod pour l'état client, l'injection de dépendances et les graphes de services
  • Remplacer ref.invalidate(provider) par client.invalidate(key) ou client.invalidateWhere

Voir aussi

Copyright © 2026