Concepts clés

Stale-While-Revalidate

Le SWR de Qora retourne les données en cache immédiatement et revalide en arrière-plan pour maintenir les UIs réactives et les données à jour.

Le problème que SWR résout

La récupération de données traditionnelle présente un arbitrage :

  • Sans cache : les données sont toujours à jour, mais chaque navigation déclenche une requête réseau et affiche un spinner.
  • Cache permanent : les réponses sont instantanées, mais les données deviennent périmées et l'UI reflète des informations obsolètes.

SWR (Stale-While-Revalidate) résout cet arbitrage.


Fonctionnement

Quand fetchQuery ou watchQuery est appelé avec des données périmées en cache :

  1. Retourner les données périmées immédiatement. L'UI s'affiche sans spinner.
  2. Déclencher une revalidation en arrière-plan. Le fetcher s'exécute silencieusement.
  3. Mettre à jour le cache et tous les streams actifs quand les données fraîches arrivent.
t=0:00  Success(user: Alice, updatedAt: 10:00)   ← staleTime : 5 min
t=5:01  fetchQuery appelé
         ├── retourne Alice immédiatement (données périmées)
         └── déclenche un fetch en arrière-plan
t=5:02  Success(user: Alice Martin, updatedAt: 10:05)   ← mis à jour
         └── les streams watchQuery émettent le nouvel état

Configuration du stale time

user_repository.dart
// Défaut : Duration.zero : toujours périmé, revalide toujours
// (sert quand même les données en cache instantanément à chaque appel)
options: QoraOptions(staleTime: Duration.zero),

// Frais pendant 5 minutes : pas d'appel réseau dans la fenêtre
options: QoraOptions(staleTime: Duration(minutes: 5)),

// Données de configuration : revalide uniquement toutes les heures
options: QoraOptions(staleTime: Duration(hours: 1)),

Choix du staleTime par type de données

Type de donnéesstaleTime recommandé
Prix en direct, notificationsDuration.zero
Profil utilisateur, paramètresDuration(minutes: 5)
Contenu de post / articleDuration(minutes: 10)
Configuration qui change rarementDuration(hours: 1)
Données de référence statiquesDuration(days: 1)

SWR avec fetchQuery

Quand fetchQuery atteint une entrée de cache périmée, il retourne les données périmées de façon synchrone et déclenche une revalidation en arrière-plan. L'appel se résout immédiatement :

user_repository.dart
// Retourne immédiatement avec les données en cache si disponibles
final user = await client.fetchQuery<User>(
  key: ['users', userId],
  fetcher: () => api.getUser(userId),
  options: QoraOptions(staleTime: Duration(minutes: 5)),
);

// Si les données étaient périmées :
// - `user` est la valeur périmée (retournée instantanément)
// - Un fetch en arrière-plan est déjà en cours
// - Les streams watchQuery actifs recevront l'utilisateur mis à jour quand le fetch se termine

SWR avec watchQuery

Les streams watchQuery sont l'interface principale pour le SWR. Les abonnés reçoivent le dernier état, y compris le résultat de la revalidation en arrière-plan :

user_page.dart
StreamBuilder<QoraState<User>>(
  stream: client.watchQuery<User>(
    key: ['users', userId],
    fetcher: () => api.getUser(userId),
    options: QoraOptions(staleTime: Duration(minutes: 5)),
  ),
  builder: (context, snapshot) {
    return switch (snapshot.data) {
      // Affiche les données périmées instantanément pendant que Loading(previousData: user) est émis
      Loading(:final previousData) when previousData != null =>
        Stack(children: [
          UserCard(previousData),
          const LinearProgressIndicator(),
        ]),
      Success(:final data) => UserCard(data),
      Failure(:final error, :final previousData) => Column(children: [
          if (previousData != null) UserCard(previousData),
          ErrorBanner('$error'),
        ]),
      _ => const CircularProgressIndicator(),
    };
  },
)

Séquence d'états au montage avec cache périmé

Abonnement démarré
  ↓ émet immédiatement
Loading(previousData: Alice)    ← données périmées visibles, indicateur de chargement discret
  ↓ fetch terminé
Success(data: Alice Martin)     ← données fraîches, l'UI se met à jour sans rupture

Refetch au montage

Par défaut, watchQuery déclenche un fetch au premier abonnement quand les données sont périmées. Ce comportement est contrôlé par refetchOnMount :

main.dart
// Toujours refetcher au montage, même si les données en cache sont encore fraîches
// (défaut quand QoraClientConfig.refetchOnMount est true)
options: QoraOptions(refetchOnMount: true),

// Fetch uniquement si les données sont absentes ou périmées ; ignorer le refetch si fraîches
options: QoraOptions(refetchOnMount: false),

// Hériter du paramètre global QoraClientConfig.refetchOnMount (défaut)
options: QoraOptions(refetchOnMount: null),

Polling (revalidation active)

Pour les données en direct, combiner le SWR avec refetchInterval pour rafraîchir continuellement le cache :

dashboard_page.dart
client.watchQuery<Stats>(
  key: ['dashboard', 'stats'],
  fetcher: api.getDashboardStats,
  options: QoraOptions(
    staleTime: Duration(seconds: 10),
    refetchInterval: Duration(seconds: 30),
  ),
)

Le timer d'intervalle démarre quand le premier abonné s'attache, se déclenche toutes les refetchInterval, et s'arrête quand le dernier abonné annule. Le polling ne s'applique pas aux requêtes hors écran.


Forcer le rafraîchissement

Pour contourner la vérification de péremption et forcer une requête réseau immédiate, invalider d'abord la clé :

main.dart
// Forcer un fetch frais au prochain accès
client.invalidate(['users', userId]);

// Ou précharger immédiatement des données fraîches
await client.prefetch<User>(
  key: ['users', userId],
  fetcher: () => api.getUser(userId),
  options: QoraOptions(staleTime: Duration.zero),
);

Étapes suivantes

Copyright © 2026