15 juin 2026 · Flutter · go_router

go_router : quand state.extra disparaît avec refreshListenable

Un bug subtil en Flutter : les données passées via extra sont perdues quand le routeur se reconstruit. Diagnostic et solution durable.

Sur un flux d'authentification par OTP, j'ai rencontré un bug déroutant : après validation du code, l'utilisateur était renvoyé vers l'écran de connexion au lieu de continuer vers son profil. Aucune erreur, aucun log suspect : juste une redirection fantôme.

Le symptôme

Le numéro de téléphone était transmis à l'écran de vérification OTP via le paramètre extra de go_router :

context.push('/verify-otp', extra: phoneNumber);

Dans la majorité des cas, tout fonctionnait. Mais dès que l'état d'authentification changeait pendant que l'écran OTP était affiché, le state.extra devenait null et la garde de route renvoyait l'utilisateur au point de départ.

La cause

Le routeur était configuré avec un refreshListenable branché sur le bloc d'authentification. C'est le pattern classique pour déclencher les redirections quand l'utilisateur se connecte ou se déconnecte :

GoRouter(
  refreshListenable: GoRouterRefreshStream(authBloc.stream),
  redirect: (context, state) { /* ... */ },
  // ...
)

Or, quand le refreshListenable notifie, go_router réévalue la route courante en reconstruisant un nouveau state, et extra n'est pas conservé dans cette reconstruction. extra est un objet Dart passé en mémoire au moment de la navigation ; il ne fait pas partie de l'URL, donc il ne survit pas à une réévaluation.

C'est un piège documenté mais facile à manquer : extra est fiable pour une navigation ponctuelle, pas pour un état qui doit survivre aux rebuilds du routeur.

La solution : faire porter l'état par l'URL

La correction consiste à passer les données indispensables dans les paramètres de requête, qui font partie de l'URL et survivent donc à toutes les réévaluations :

// Un helper centralisé plutôt que des URLs construites à la main
abstract class Routes {
  static String verifyOtpWith(String phone) =>
      '/verify-otp?phone=${Uri.encodeComponent(phone)}';
}

// À l'appel
context.push(Routes.verifyOtpWith(phoneNumber));

// À la lecture
final phone = state.uri.queryParameters['phone'];

Deux règles que j'en ai tirées :

  • extra uniquement pour du confort éphémère (un objet déjà chargé qu'on évite de recharger), jamais pour une donnée dont dépend la logique de navigation.
  • Centraliser la construction des routes dans une classe dédiée. Le jour où un paramètre change, il n'y a qu'un seul endroit à modifier, et l'encodage URL est garanti partout.

Ce genre de bug coûte des heures parce qu'il est intermittent : il ne se manifeste que si le refreshListenable notifie au mauvais moment. Si votre flux OTP ou deep-link « oublie » ses données par moments, vérifiez ce que vous transportez dans extra.

← Tous les articles