Mutations et invalidation
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égie | Ce que vous faites | À utiliser quand |
|---|---|---|
| Invalider | Marquer les requêtes concernées comme périmées ; elles se relancent | Par défaut. Le serveur fait foi et une requête de plus est acceptable |
| Écrire la réponse | Mettre la réponse du serveur en cache avec setQueryData | Le serveur renvoie l'entité mise à jour, et vous voulez éviter le refetch |
| Mise à jour optimiste | Modifier le cache dans onMutate, annuler dans onError | Le 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
- Hors-ligne et réseau : requêtes en pause, file de mutations, reconnexion
- Recette Mutations :
QoraMutationBuilder, retries, tags, contrôleurs autonomes - Mises à jour optimistes : schémas pour les listes et les clés multiples
- QoraMutationBuilder : l'API du widget