Concepts clés

Mutations et invalidation

Comment s'exécutent les écritures, dans quel ordre leurs callbacks sont appelés, et les trois façons de réaligner le cache sur le serveur ensuite.

Une requête lit des données serveur et les garde en cache. Une mutation modifie des données serveur : création, mise à jour, suppression. Les mutations ne sont ni mises en cache ni dédupliquées ; elles s'exécutent quand vous les appelez, une fois par appel.

Le plus difficile dans une écriture n'est pas de l'envoyer. C'est ce que deviennent les données en cache ensuite : la liste qui devrait contenir le nouvel élément, l'écran de détail qui affiche l'ancien titre. Cette page traite de cette partie.

Le cycle de vie

Une mutation passe par quatre états :

MutationIdle ──mutate()──▶ MutationPending ──▶ MutationSuccess
                                         └──▶ MutationFailure

Ses callbacks s'exécutent dans un ordre fixe. Le connaître indique où placer chaque partie de la logique :

mutate(variables)
  1. onMutate(variables)            → renvoie un contexte (souvent un snapshot)
  2. état : MutationPending
  3. mutator(variables)             → retenté retryCount fois (0 par défaut)
  succès :
  4. état : MutationSuccess
  5. onSuccess(data, variables, context)
  6. invalidates, puis invalidatesTags
  7. onSettled(data, null, variables, context)
  échec :
  4. état : MutationFailure
  5. onError(error, variables, context)
  6. onSettled(null, error, variables, context)

Le context renvoyé par onMutate est transmis à tous les callbacks suivants. C'est ainsi qu'un rollback dans onError sait quoi restaurer. Si onMutate lève lui-même une exception, la mutation passe directement à MutationFailure : le mutator n'est pas appelé, et onError non plus.

Trois façons de mettre à jour le cache après une écriture

StratégieCe que vous faitesÀ utiliser quand
InvaliderMarquer les requêtes concernées comme périmées ; elles se relancentPar défaut. Le serveur fait foi et une requête de plus est acceptable
Écrire la réponseMettre la réponse du serveur en cache avec setQueryDataLe serveur renvoie l'entité mise à jour, et vous voulez éviter le refetch
Mise à jour optimisteModifier le cache dans onMutate, annuler dans onErrorLe résultat est prévisible et l'UI doit réagir instantanément, ou fonctionner hors-ligne

Elles se combinent. Le schéma le plus robuste est une mise à jour optimiste suivie d'une invalidation : l'UI change immédiatement, et le refetch remplace votre supposition par ce que le serveur a réellement enregistré.

Invalider

Déclarez les clés qu'une mutation affecte ; Qora les invalide après onSuccess :

QoraMutationBuilder<Todo, String, void>(
  mutator: (title) => api.createTodo(title),
  options: const MutationOptions(invalidates: [['todos']]),
  builder: (context, state, mutate) => AddTodoField(
    enabled: !state.isPending,
    onSubmit: mutate,
  ),
)

Invalider une clé marque sa donnée comme périmée. Une requête affichée à l'écran se relance immédiatement ; en attendant, son état est Loading avec l'ancienne donnée dans previousData, que l'écran peut continuer d'afficher. Une requête qui n'est pas à l'écran se relance la prochaine fois qu'un widget l'utilise.

invalidates compare des clés exactes. [['todos']] invalide ['todos'], pas ['todos', 42]. Pour invalider toute une famille, appelez client.invalidatePrefix(['todos']) dans onSuccess, ou utilisez les tags (providesTags sur les requêtes, invalidatesTags sur la mutation). Voir Clés de requête.

Écrire la réponse

Quand le serveur renvoie l'entité mise à jour, mettez-la en cache au lieu de la recharger :

options: MutationOptions(
  onSuccess: (updated, _, __) async {
    client.setQueryData<Todo>(['todos', updated.id], updated);
  },
),

Chaque widget qui affiche ['todos', updated.id] se reconstruit avec la nouvelle valeur, sans requête. Les listes qui contiennent l'élément sont une autre clé : invalidez-les, ou mettez-les à jour de la même façon.

Mise à jour optimiste

Modifiez le cache avant la réponse du serveur, gardez un snapshot, et restaurez-le en cas d'échec :

options: MutationOptions<Todo, Todo, List<Todo>?>(
  onMutate: (todo) async {
    final previous = client.getQueryData<List<Todo>>(['todos']);
    client.setQueryData<List<Todo>>(
      ['todos'],
      [for (final t in previous ?? <Todo>[]) t.id == todo.id ? todo : t],
    );
    return previous; // devient `context` dans les autres callbacks
  },
  onError: (_, __, previous) async {
    client.restoreQueryData<List<Todo>>(['todos'], previous);
  },
  invalidates: const [['todos']], // réaligner sur le serveur dans tous les cas
),

restoreQueryData remet le snapshot en place, ou supprime l'entrée si le snapshot vaut null. La recette Mises à jour optimistes couvre les listes, les clés multiples et les modifications concurrentes.

Mutations et réseau

Par défaut, une mutation envoie sa requête immédiatement. Quand l'appareil est hors-ligne, cette requête échoue comme n'importe quelle erreur réseau, et le chemin d'échec ci-dessus s'exécute.

Avec offlineQueue: true, une mutation appelée hors-ligne est mise en file à la place : onMutate s'exécute tout de suite, donc les mises à jour optimistes fonctionnent hors-ligne, et la requête est envoyée à la reconnexion. Hors-ligne et réseau explique la file, son ordre et ses limites.

Étapes suivantes

Copyright © 2026