QoraOptions
Vue d'ensemble
QoraOptions configure le comportement d'une requête individuelle. Tout champ laissé à sa valeur par défaut hérite de QoraClientConfig.defaultOptions. Les champs sont fusionnés, les valeurs par requête ayant la priorité.
await client.fetchQuery<User>(
key: ['users', userId],
fetcher: () => api.getUser(userId),
options: QoraOptions(
staleTime: Duration(minutes: 5),
retryCount: 2,
),
);
Constructeur
const QoraOptions({
Duration staleTime = Duration.zero,
Duration gcTime = const Duration(minutes: 5),
bool enabled = true,
Object? dependsOn,
int retryCount = 3,
Duration retryDelay = const Duration(seconds: 1),
Duration Function(int attemptIndex)? retryDelayCalculator,
bool Function(Object error, int attemptIndex)? retryCondition,
bool refetchOnWindowFocus = true,
bool refetchOnReconnect = true,
Duration? refetchInterval,
bool? refetchOnMount,
NetworkMode networkMode = NetworkMode.online,
Object? Function(Object? data)? toJson,
})
Champs
staleTime
final Duration staleTime
Défaut : Duration.zero
Durée pendant laquelle les données sont considérées fraîches après un fetch réussi. Pendant la fenêtre de fraîcheur, fetchQuery et watchQuery ignorent complètement le réseau et retournent les données en cache.
Une fois staleTime écoulé, les données sont considérées périmées : Qora les retourne immédiatement mais déclenche aussi une revalidation en arrière-plan.
// Toujours périmé (défaut) : servir le cache, toujours revalider
staleTime: Duration.zero,
// Frais pendant 5 minutes : pas de réseau dans la fenêtre
staleTime: Duration(minutes: 5),
// Jamais périmé en pratique
staleTime: Duration(days: 365),
| Type de données | Valeur recommandée |
|---|---|
| Prix en direct, feeds | Duration.zero |
| Profil utilisateur | Duration(minutes: 5) |
| Contenu article/post | Duration(minutes: 10) |
| Configuration applicative | Duration(hours: 1) |
| Données de référence statiques | Duration(days: 1) |
gcTime
final Duration gcTime
Défaut : Duration(minutes: 5)
Durée pendant laquelle une entrée inactive reste en cache après l'annulation de tous les abonnés watchQuery. Quand le timer GC se déclenche, l'entrée est supprimée ; le prochain accès repart de Initial.
// Garder en cache 10 minutes après avoir quitté l'écran
gcTime: Duration(minutes: 10),
// Supprimer immédiatement à la désabonnement (ex. données de session sensibles)
gcTime: Duration.zero,
staleTime et gcTime sont indépendants. Un pattern courant est staleTime < gcTime : les données deviennent périmées (déclenchant une revalidation) mais restent en cache pour que la navigation retour soit instantanée.enabled
final bool enabled
Défaut : true
Quand false :
fetchQuerylève unStateErrorimmédiatementwatchQueryémet l'état actuel sans déclencher de fetch
Utile pour les requêtes conditionnelles qui ne doivent s'exécuter que lorsqu'un prérequis est satisfait :
// Fetch uniquement quand userId est non-null
options: QoraOptions(enabled: userId != null),
dependsOn
final Object? dependsOn
Défaut : null
La clé d'une autre requête qui doit avoir des données Success avant que cette requête se déclenche. Fournit une dépendance réactive sans coordination manuelle :
| API | Comportement quand la dépendance n'est pas résolue |
|---|---|
watchQuery | S'abonne au stream de la dépendance ; se déclenche automatiquement à l'arrivée des données |
fetchQuery | Lève StateError immédiatement |
prefetch | Ignore silencieusement |
// Étape 1 : récupérer la session (pas de dépendance)
client.watchQuery<Session>(
key: ['session'],
fetcher: () => api.getSession(),
);
// Étape 2 : récupérer l'utilisateur une fois que la session a des données
client.watchQuery<User>(
key: ['user'],
fetcher: () => api.getUser(session!.userId),
options: QoraOptions(dependsOn: ['session']),
);
Voir le guide des requêtes dépendantes pour des exemples complets.
retryCount
final int retryCount
Défaut : 3
Nombre de tentatives de retry automatique après un échec de fetch. Mettre à 0 pour désactiver les retries.
retryCount: 0, // échec immédiat à la première erreur
retryCount: 5, // retry jusqu'à 5 fois
retryDelay
final Duration retryDelay
Défaut : Duration(seconds: 1)
Délai de base entre les tentatives de retry. Par défaut Qora utilise un backoff exponentiel :
- Tentative 0 :
1 s - Tentative 1 :
2 s - Tentative 2 :
4 s - Tentative 3 :
8 s
retryDelayCalculator
final Duration Function(int attemptIndex)? retryDelayCalculator
Défaut : null (utilise le backoff exponentiel)
Calculateur de délai personnalisé. Reçoit l'index de tentative base-zéro et retourne le délai avant cette tentative.
// Délai fixe de 500 ms
retryDelayCalculator: (_) => const Duration(milliseconds: 500),
// Backoff exponentiel avec jitter
retryDelayCalculator: (i) => Duration(
milliseconds: (500 * pow(2, i) + Random().nextInt(200)).toInt(),
),
retryCondition
final bool Function(Object error, int attemptIndex)? retryCondition
Défaut : null (retenter toutes les erreurs jusqu'à retryCount)
Prédicat qui détermine si un fetch échoué doit être réessayé. Reçoit l'erreur et l'index de tentative base-zéro qui vient d'échouer. Retourner false pour ignorer les tentatives suivantes.
Utile pour éviter de réessayer des erreurs non récupérables comme les réponses HTTP 4xx :
// Réessayer uniquement les erreurs réseau, pas les erreurs applicatives
retryCondition: (error, attempt) => error is SocketException,
// Ne jamais réessayer les réponses 4xx (si mappées vers une exception typée)
retryCondition: (error, attempt) => error is! HttpException,
// Réessayer uniquement la première tentative, échouer rapidement ensuite
retryCondition: (error, attempt) => attempt == 0,
Quand retryCondition retourne false, toutes les tentatives restantes pour cette erreur sont ignorées et l'erreur est levée immédiatement.
refetchOnWindowFocus
final bool refetchOnWindowFocus
Défaut : true
Indique si cette requête doit être rechargée quand l'application regagne le focus de fenêtre. Nécessite qu'un LifecycleManager soit enregistré sur le client.
refetchOnReconnect
final bool refetchOnReconnect
Défaut : true
Indique si cette requête doit être rechargée quand la connexion réseau est rétablie. Nécessite qu'un ConnectivityManager soit enregistré sur le client.
refetchInterval
final Duration? refetchInterval
Défaut : null (désactivé)
Quand défini, watchQuery recharge automatiquement à cette cadence tant qu'au moins un abonné est actif. Le timer s'arrête quand le dernier abonné annule.
// Polling toutes les 30 secondes pendant que le widget est monté
options: QoraOptions(refetchInterval: Duration(seconds: 30)),
refetchOnMount
final bool? refetchOnMount
Défaut : null (revient à QoraClientConfig.refetchOnMount)
Contrôle si watchQuery déclenche un fetch au premier abonnement :
| Valeur | Comportement |
|---|---|
true | Toujours recharger au montage, même si les données sont fraîches |
false | Fetch uniquement si les données sont absentes ; ignorer si en cache |
null | Utiliser QoraClientConfig.refetchOnMount (défaut : true) |
networkMode
final NetworkMode networkMode
Défaut : NetworkMode.online
Contrôle le comportement de cette requête quand l'appareil n'a pas de connectivité :
| Valeur | Comportement |
|---|---|
NetworkMode.online | Le fetch est mis en pause hors ligne (FetchStatus.paused). Rejoué automatiquement à la reconnexion. |
NetworkMode.always | Le fetch s'exécute toujours quelle que soit la connectivité. Utiliser pour les fetchers locaux (base de données, mémoire). |
NetworkMode.offlineFirst | Les données en cache sont retournées immédiatement. Une revalidation en arrière-plan est mise en file pour quand la connexion est rétablie. |
// Défaut : mise en pause hors ligne, rejeu à la reconnexion
options: const QoraOptions(networkMode: NetworkMode.online),
// Requête base de données locale : toujours exécuter, jamais mettre en pause
options: const QoraOptions(networkMode: NetworkMode.always),
// Servir le cache immédiatement, rafraîchir en arrière-plan quand en ligne
options: const QoraOptions(networkMode: NetworkMode.offlineFirst),
NetworkMode.always pour les requêtes sur SQLite, Hive ou tout store local : elles n'ont pas besoin de connectivité et ne doivent jamais être mises en pause.structuralSharing
final bool structuralSharing
Défaut : true
Préserve l'égalité référentielle pour les données imbriquées inchangées entre les fetches. Quand activé, chaque fetch réussi compare les nouvelles données avec le cache existant via une égalité profonde. Si les données sont structurellement inchangées, la référence précédente est conservée.
Cela évite les reconstructions inutiles de widgets : les widgets qui comparent avec identical() ou == peuvent ignorer le rendu quand les données n'ont pas réellement changé.
La comparaison est récursive pour Map, List et Set. Les classes de modèle personnalisées doivent surcharger == et hashCode pour une détection fiable.
Désactiver uniquement si la vérification d'égalité profonde devient un goulot d'étranglement mesuré avec de très grandes structures de données :
// Activé (défaut) : préserver les références quand les données n'ont pas changé
QoraOptions(structuralSharing: true),
// Désactivé : toujours créer de nouvelles références à chaque fetch
QoraOptions(structuralSharing: false),
keepPreviousData
final bool keepPreviousData
Défaut : false
Quand true, les données précédentes restent visibles pendant que le prochain fetch est en cours, supprimant la transition Success → Loading → Success. L'entrée reste dans l'état Success pendant le fetch et en cas d'échec, donc l'UI n'affiche jamais de spinner de chargement.
Utile pour la pagination basée sur un offset : garder la page actuelle visible pendant le chargement de la suivante.
// Changement de page sans flash de chargement
fetchQuery<List<Post>>(
key: ['posts', 'page', page],
fetcher: () => api.getPosts(page: page, limit: 20),
options: QoraOptions(
keepPreviousData: true,
staleTime: Duration.zero,
),
);
Sans effet sur le fetch initial (pas de données précédentes à conserver).
transform
final Object Function(Object? data)? transform
Défaut : null
Fonction de transformation appliquée aux données de fetch réussies avant leur entrée dans le cache. Utilisez-la pour découpler la transformation des données du fetcher : le fetcher retourne les données brutes (ex. maps JSON décodées) et transform les convertit en modèle final.
La transformation s'exécute avant le partage structurel, donc les modifications des données transformées sont correctement détectées par rapport à l'entrée en cache.
// Le fetcher retourne du JSON brut ; transform le convertit en classe de modèle.
fetchQuery<User>(
key: ['users', userId],
fetcher: () => api.getUser(userId), // retourne Map<String, dynamic>
options: QoraOptions(
transform: (raw) => User.fromJson(raw as Map<String, dynamic>),
),
);
Le fetcher et la transform sont tous deux testables indépendamment : le fetcher retourne des données brutes, la transform est une fonction pure.
transformError
final Object Function(Object error)? transformError
Défaut : null
Fonction de transformation appliquée aux erreurs de fetch avant leur entrée dans le cache. Reçoit l'erreur brute levée par le fetcher (après toutes les tentatives). Utilisez-la pour normaliser les erreurs de votre couche API ou pour les enrichir avec du contexte métier.
S'exécute avant le QoraClientConfig.errorMapper, donc la transformation par requête a priorité sur le mappeur global.
fetchQuery<Post>(
key: ['post', postId],
fetcher: () => api.getPost(postId),
options: QoraOptions(
transformError: (error) => switch (error) {
DioException(e: final err) => AppError.fromDio(err),
_ => AppError.unknown(error.toString()),
},
),
);
providesTags
final List<QueryTag>? providesTags
Défaut : null
Étiquettes que cette requête fournit pour l'invalidation de cache basée sur les tags. Lorsqu'une mutation invalide un tag correspondant (via MutationOptions.invalidatesTags), toutes les requêtes qui fournissent ce tag sont automatiquement re-récupérées, indépendamment de leur clé de requête.
Cela découple la logique d'invalidation de la structure des clés de requête, facilitant l'invalidation de requêtes apparentées à travers différents motifs de clés :
// La requête fournit des tags
watchQuery<Post>(
key: ['post', postId],
fetcher: () => api.getPost(postId),
options: QoraOptions(
providesTags: [
QueryTag('post', postId.toString()),
QueryTag('author', authorId.toString()),
],
),
);
Correspondance générique
Invalider QueryTag('post') (sans id) re-récupère TOUTES les requêtes qui fournissent un tag de type 'post', indépendamment de leurs ids individuels. Invalider QueryTag('post', '123') ne re-récupère que les requêtes qui fournissent le tag spécifique 'post:123'.
// Re-récupère TOUTES les requêtes taguées avec 'post' (n'importe quel id).
client.invalidateTags([QueryTag('post')]);
// Re-récupère seulement les requêtes taguées avec 'post:123'.
client.invalidateTags([QueryTag('post', '123')]);
toJson
final Object? Function(Object? data)? toJson
Défaut : null
Sérialiseur optionnel qui contrôle comment les données de la requête sont affichées dans l'overlay DevTools.
Quand omis, Qora applique l'ordre de résolution suivant automatiquement :
- Les données qui sont déjà
Map,List,String,num,boolounull: passées telles quelles. - Tout objet avec une méthode
toJson()(json_serializable,freezed, manuel): appelée dynamiquement. - Tout le reste: repli sur
.toString().
Fournir toJson explicitement uniquement quand la détection automatique est insuffisante, par exemple quand la méthode toJson() a une signature non standard ou retourne un type non encodable.
// Sérialiseur explicite (remplace la détection automatique)
fetchQuery<User>(
key: ['users', userId],
fetcher: () => api.getUser(userId),
options: QoraOptions(
toJson: (data) => (data as User).toJson(),
),
);
getRetryDelay(int attemptIndex)
Duration getRetryDelay(int attemptIndex)
Calcule le délai de retry pour l'index de tentative base-zéro donné. Délègue à retryDelayCalculator si défini, sinon applique le backoff exponentiel : retryDelay × 2^attemptIndex.
merge(QoraOptions? other)
QoraOptions merge(QoraOptions? other)
Retourne un nouveau QoraOptions fusionnant this avec other. Les champs de other ont la priorité. Les champs nullable (retryDelayCalculator, refetchInterval, refetchOnMount) reviennent à this quand non définis dans other.
Utilisé en interne par QoraClient pour combiner les défauts globaux avec les options par requête.
Recettes courantes
// Données en direct : toujours frais, polling toutes les 10 s
QoraOptions(
staleTime: Duration.zero,
refetchInterval: Duration(seconds: 10),
)
// Données de config : cache agressif
QoraOptions(
staleTime: Duration(hours: 1),
gcTime: Duration(hours: 2),
retryCount: 5,
)
// Requête conditionnelle
QoraOptions(enabled: currentUser != null)
// Désactiver les retries pour les mutations
QoraOptions(retryCount: 0)
QoraState
Hiérarchie de classes sealed représentant les quatre états possibles d'une requête. Le pattern matching exhaustif est imposé à la compilation.
QoraClientConfig
Configuration globale de QoraClient. Appliquée comme référence pour chaque requête. Les QoraOptions par requête sont fusionnées par-dessus.