Gestion des erreurs
Qora distingue les erreurs transitoires (retry automatique) des erreurs terminales (remontées à l'UI). Tous les échecs de fetch passent par le même pipeline : retry avec backoff exponentiel → transition vers Failure → notification des abonnés.
L'état Failure
Quand tous les retries sont épuisés, QoraState transite vers Failure :
sealed class QoraState<T> { ... }
final class Failure<T> extends QoraState<T> {
final Object error;
final StackTrace? stackTrace;
final T? previousData; // non-null quand des données étaient en cache avant l'échec
}
previousData porte la dernière valeur récupérée avec succès. Cela permet la dégradation gracieuse : afficher des données périmées à côté d'un indicateur d'erreur plutôt que de remplacer toute la vue.
QoraBuilder<User>(
queryKey: ['users', userId],
fetcher: () => api.getUser(userId),
builder: (context, state, fetchStatus) => switch (state) {
Initial() => const SizedBox.shrink(),
Loading(:final previousData) => previousData != null
? Stack(children: [UserCard(previousData), const LoadingOverlay()])
: const CircularProgressIndicator(),
Success(:final data) => UserCard(data),
Failure(:final error, :final previousData) => previousData != null
? Column(children: [
UserCard(previousData),
ErrorBanner('Échec du rafraîchissement : $error'),
])
: ErrorScreen('$error'),
},
)
Configuration du retry
Qora relance automatiquement les fetchs échoués avec un backoff exponentiel. Contrôler cela par requête via QoraOptions :
QoraOptions(
retryCount: 3, // Défaut : 3 ; mettre 0 pour désactiver
retryDelay: const Duration(seconds: 1), // Délai de base ; double à chaque tentative
)
Calendrier de backoff par défaut :
| Tentative | Délai |
|---|---|
| 1 | 1 s |
| 2 | 2 s |
| 3 | 4 s |
Délai personnalisé
QoraOptions(
retryDelayCalculator: (attempt) => Duration(
milliseconds: (500 * pow(2, attempt)).toInt(),
),
)
Désactiver les retries
// Échec immédiat : ex. pour les mutations ou les requêtes temps réel
QoraOptions(retryCount: 0)
Ignorer sélectivement les retries
Pour ignorer les retries pour des types d'erreurs spécifiques (ex. 401 Unauthorized ne doit jamais être retenté) :
QoraOptions(
retryCount: 3,
retryDelayCalculator: (attempt) {
// Ne sera pas appelé si retryCount est effectivement 0,
// mais la logique de retry peut être conditionnée dans le fetcher lui-même :
return Duration(seconds: 1 << attempt);
},
)
Le pattern recommandé est de lever une exception spécifique et de la capturer dans le fetcher :
Future<User> getUser(int id) async {
final response = await http.get(Uri.parse('/users/$id'));
if (response.statusCode == 401) throw const UnauthorizedException();
if (response.statusCode == 404) throw const NotFoundException();
if (response.statusCode != 200) throw HttpException(response.statusCode);
return User.fromJson(jsonDecode(response.body));
}
Puis dans QoraClientConfig.errorMapper, normaliser les erreurs avant qu'elles atteignent le moteur de retry :
QoraClientConfig(
errorMapper: (error, stack) {
if (error is UnauthorizedException) return error; // Ne pas envelopper : laisser remonter
return QoraException('$error', originalError: error, stackTrace: stack);
},
)
Gestion globale des erreurs
Utiliser QoraClientConfig.errorMapper pour normaliser toutes les erreurs en un seul type avant qu'elles remontent dans Failure.error :
final client = QoraClient(
config: QoraClientConfig(
errorMapper: (error, stackTrace) {
if (error is DioException) {
return switch (error.type) {
DioExceptionType.connectionTimeout => const NetworkTimeoutError(),
DioExceptionType.receiveTimeout => const NetworkTimeoutError(),
_ => ApiError(error.response?.statusCode),
};
}
return error;
},
),
);
Erreurs de fetch en arrière-plan
Quand vous utilisez watchQuery, les fetchs en arrière-plan s'exécutent dans des futures unawaited. Si le fetch échoue, l'erreur est stockée dans l'état Failure, mais l'exception relancée ferait crasher la Zone asynchrone courante. Utilisez QoraClientConfig.onBackgroundFetchError pour intercepter ces erreurs en toute sécurité pour la journalisation ou le rapport de crash :
final client = QoraClient(
config: QoraClientConfig(
onBackgroundFetchError: (error, stackTrace, key) {
// L'erreur est déjà stockée dans l'état Failure, utiliser pour le reporting
crashReporter.record(error, stackTrace);
},
),
);
Le callback reçoit l'erreur, la stack trace et la clé de requête normalisée. L'erreur a déjà été stockée dans l'état Failure de la requête, ce callback est uniquement pour l'observabilité.
Observation des erreurs par tracker
Implémenter QoraTracker.onQueryFetchFailed pour observer toutes les erreurs de requête globalement : utile pour le rapport de crash ou l'analytics :
class AnalyticsTracker extends NoOpTracker {
@override
void onQueryFetchFailed(String key, Object error, StackTrace stackTrace) {
Sentry.captureException(error, stackTrace: stackTrace);
analytics.track('query_error', {'key': key, 'error': '$error'});
}
}
Erreurs de mutation
Les erreurs de mutation sont exposées sur MutationFailure :
QoraMutationBuilder<User, UpdatePayload, User?>(
mutator: (payload) => api.updateUser(payload),
options: MutationOptions(
onMutate: (payload) async =>
QoraScope.of(context).getQueryData<User>(['users', userId]),
onError: (error, payload, snapshot) async {
if (snapshot != null) {
QoraScope.of(context).restoreQueryData(['users', userId], snapshot);
}
},
onSettled: (_, _, _, _) async =>
QoraScope.of(context).invalidate(['users', userId]),
),
builder: (context, state, mutate) => switch (state) {
MutationFailure(:final error) => ErrorBanner('$error'),
_ => UpdateButton(onTap: () => mutate(payload)),
},
)
Erreurs hors-ligne
Quand networkMode est NetworkMode.online (défaut) et que l'appareil est hors-ligne :
fetchQuerylèveQoraOfflineExceptionsi aucune donnée en cache n'existe.watchQueryémetFetchStatus.paused: l'état reste àLoading(previousData: ...)si des données ont été précédemment mises en cache.- Le fetch est rejoué automatiquement quand la connectivité est restaurée.
try {
final data = await client.fetchQuery<User>(
key: ['users', userId],
fetcher: () => api.getUser(userId),
);
} on QoraOfflineException {
showSnackBar('Pas de connexion internet');
}
Voir aussi
- QoraOptions :
retryCount,retryDelay,retryDelayCalculator,networkMode - Requêtes réseau adaptatives : comportement hors-ligne,
NetworkMode,FetchStatus - Guide des mutations :
onError, rollback