riverpod-ref-read-in-dispose

Par divinevideo · divine-mobile

Corriger l'erreur Riverpod « Using ref when widget is unmounted is unsafe » dans un `ConsumerStatefulWidget`. À utiliser quand : (1) une erreur est levée dans `dispose()` lors d'un appel à `ref.read()`, (2) une méthode de service doit être appelée lors du nettoyage du widget, (3) un `ConsumerStatefulWidget` doit effectuer des actions sur un provider quand l'utilisateur quitte l'écran. Solution : mettre en cache le service/notifier dans `initState` avec `late final`.

npx skills add https://github.com/divinevideo/divine-mobile --skill riverpod-ref-read-in-dispose

Erreur Riverpod ref.read() dans dispose()

Problème

Quand un ConsumerStatefulWidget doit appeler une méthode de service dans dispose() (par exemple pour annuler une opération quand l'utilisateur quitte un écran), utiliser ref.read() lève une erreur car le widget est déjà démonté.

Contexte / Conditions de déclenchement

  • Message d'erreur : Using ref when widget is unmounted is unsafe
  • Utilisation de ConsumerStatefulWidget avec ConsumerState
  • Appel de ref.read(someProvider) à l'intérieur de dispose()
  • Le widget se désaffiche (pop, push replacement, etc.)
  • La stack trace montre l'erreur provenant de dispose()

Exemple de code problématique :

@override
void dispose() {
  ref.read(authServiceProvider).cancelOperation();  // ERREUR!
  super.dispose();
}

Solution

Étape 1 : Ajouter un champ late final pour le service

class _MyScreenState extends ConsumerState<MyScreen> {
  late final MyService _myService;  // Mise en cache ici

Étape 2 : Initialiser dans initState

@override
void initState() {
  super.initState();
  _myService = ref.read(myServiceProvider);  // Sûr ici
}

Étape 3 : Utiliser la référence mise en cache dans dispose

@override
void dispose() {
  _myService.cancelOperation();  // Utiliser la référence mise en cache
  super.dispose();
}

Vérification

  1. Accéder à l'écran
  2. Se désafficher (bouton retour, push replacement, etc.)
  3. Aucune erreur "Using ref when widget is unmounted" dans la console
  4. L'action de nettoyage (cancel, close, etc.) s'exécute toujours

Exemple

Avant (cause une erreur) :

class _NostrConnectScreenState extends ConsumerState<NostrConnectScreen> {
  @override
  void dispose() {
    // ERREUR : ref n'est pas sûr à utiliser ici
    ref.read(authServiceProvider).cancelNostrConnect();
    super.dispose();
  }
}

Après (fonctionne correctement) :

class _NostrConnectScreenState extends ConsumerState<NostrConnectScreen> {
  // Mise en cache d'AuthService pour utilisation dans dispose (impossible d'utiliser ref.read dans dispose)
  late final AuthService _authService;

  @override
  void initState() {
    super.initState();
    _authService = ref.read(authServiceProvider);
  }

  @override
  void dispose() {
    // Annuler la session si l'utilisateur quitte l'écran
    _authService.cancelNostrConnect();
    super.dispose();
  }
}

Notes

  • Ce pattern est nécessaire car ref de Riverpod est lié au cycle de vie du widget
  • ref.read() est sûr dans initState() car le widget est en cours de montage
  • ref.read() n'est PAS sûr dans dispose() car le widget est en cours de démontage
  • Le pattern late final garantit que le service est initialisé exactement une fois
  • Ceci s'applique à tout nettoyage nécessitant l'appel de méthodes sur des providers
  • Pour les simples changements d'état de provider (sans appels de méthode), envisagez plutôt d'utiliser deactivate()
  • Si le service lui-même peut être disposé, ajoutez des vérifications de null ou try-catch selon les besoins

Skills connexes

  • flutter-dispose-timer-test-failure : Pour les problèmes de dispose liés aux timers
  • flutter-deactivate-setstate-during-build : Pour les problèmes de setState/build cycle dans deactivate

Références

Skills similaires