web-dev-qa-db-fra.com

Corriger la séquence du widget Flutter pour extraire les données lors du chargement de l'application

Je rencontre un problème de flottement lorsque j'essaie de lire les données du stockage local lors du chargement de l'application.

J'ai un widget hérité qui contient des informations d'authentification pour l'utilisateur actuel. Lorsque l'application se charge, je souhaite examiner le stockage local pour les jetons de session. Si les jetons de session existent, je voudrais mettre à jour le widget hérité avec ces informations.

Mes écrans sont dynamiques. S'il sait que l'utilisateur est authentifié, il les amène à l'écran demandé, sinon il les amène à l'écran d'enregistrement.

Le problème que je rencontre est que je ne peux pas mettre à jour l'état du widget hérité à partir d'une méthode initState() à partir d'un widget qui dépend du widget hérité (widget My router)

Comment puis-je lire à partir du stockage local lorsque l'application se charge et met à jour le widget hérité?

Erreur lors de l'exécution de l'application:

flutter: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
flutter: The following assertion was thrown building _InheritedAuthContainer:
flutter: inheritFromWidgetOfExactType(_InheritedAuthContainer) or inheritFromElement() was called before
flutter: RootState.initState() completed.
flutter: When an inherited widget changes, for example if the value of Theme.of() changes, its dependent
flutter: widgets are rebuilt. If the dependent widget's reference to the inherited widget is in a constructor
flutter: or an initState() method, then the rebuilt dependent widget will not reflect the changes in the
flutter: inherited widget.
flutter: Typically references to inherited widgets should occur in widget build() methods. Alternatively,
flutter: initialization based on inherited widgets can be placed in the didChangeDependencies method, which
flutter: is called after initState and whenever the dependencies change thereafter.

Widget de routeur (racine)

class Root extends StatefulWidget {
  @override
  State createState() => RootState();
}

class RootState extends State<Root> {
  static Map<String, Widget> routeTable = {Constants.HOME: Home()};
  bool loaded = false;
  bool authenticated = false;

  @override
  void initState() {
    super.initState();
    if (!loaded) {
      AuthContainerState data = AuthContainer.of(context);
      data.isAuthenticated().then((authenticated) {
        setState(() {
          authenticated = authenticated;
          loaded = true;
        });
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        initialRoute: '/',
        onGenerateRoute: (routeSettings) {
          WidgetBuilder screen;
          if (loaded) {
            if (authenticated) {
              screen = (context) => SafeArea(
                  child: Material(
                      type: MaterialType.transparency,
                      child: routeTable[routeSettings.name]));
            } else {
              screen = (conext) => SafeArea(
                  child: Material(
                      type: MaterialType.transparency, child: Register()));
            }
          } else {
            screen = (context) => new Container();
          }
          return new MaterialPageRoute(
            builder: screen,
            settings: routeSettings,
          );
        });
  }
}

Méthode du widget hérité qui vérifie l'authentification et se met à jour elle-même, ce qui déclenche un nouveau rendu du widget de mon routeur

Future<bool> isAuthenticated() async {
    if (user == null) {
      final storage = new FlutterSecureStorage();
      List results = await Future.wait([
        storage.read(key: 'idToken'),
        storage.read(key: 'accessToken'), 
        storage.read(key: 'refreshToken'),
        storage.read(key: 'firstName'),
        storage.read(key: 'lastName'),
        storage.read(key: 'email')
      ]);
      if (results != null && results[0] != null && results[1] != null && results[2] != null) {
        //triggers a set state on this widget
        updateUserInfo(
          identityToken: results[0], 
          accessToken: results[1], 
          refreshToken: results[2],
          firstName: results[3],
          lastName: results[4],
          email: results[5]
        );
      }
    }
    return user != null && (JWT.isActive(user.identityToken) || JWT.isActive(user.refreshToken));
  }

Principale

void main() => runApp(
  EnvironmentContainer(
    baseUrl: DEV_API_BASE_URL,
    child: AuthContainer(
      child: Root()
    )
  )
);

Quelle est la bonne façon de vérifier le stockage local lors du chargement de l'application et de mettre à jour le widget hérité qui contient ces informations?

10
TemporaryFix

En fait, vous ne pouvez pas accéder à InheritedWidget à partir d'une méthode initState. Essayez plutôt d'y accéder à partir de didChangeDependencies.

Exemple:

@override
void didChangeDependencies() {
  super.didChangeDependencies();
  if (!loaded) {
    AuthContainerState data = AuthContainer.of(context);
    data.isAuthenticated().then((authenticated) {
      setState(() {
        authenticated = authenticated;
        loaded = true;
      });
    });
  }
}

Une autre façon serait de planifier la récupération des données dans initState avec SchedulerBinding. Vous pouvez trouver les documents ici

SchedulerBinding.instance.addPostFrameCallback((_) {
  // your login goes here
});

Remarque: rappelez-vous que didChangeDependencies sera appelé chaque fois que l'état ou les dépendances de tout parent InheritedWidget changeront. Veuillez consulter les documents ici .

J'espère que cela t'aides!

9
Hemanth Raj

Bien que la réponse de @ hemanth-raj soit correcte, je préconiserais en fait une manière légèrement différente de procéder. Au lieu de construire le AuthContainer sans données, vous pouvez réellement faire le chargement de la session utilisateur avant de construire vos widgets et de passer les données directement. Cet exemple utilise le plugin scoped_model pour résumer le passe-partout des widgets hérités (que je recommande fortement plutôt que d'écrire manuellement les widgets hérités!) Mais est par ailleurs assez similaire à ce que vous avez fait.

Future startUp() async {
  UserModel userModel = await loadUser();
  runApp(
    ScopedModel<UserModel>(
      model: userModel,
      child: ....
    ),
  );
}

void main() {
  startup();
}

C'est plus ou moins ce que je fais dans mon application et je n'ai eu aucun problème avec cela (bien que vous souhaitiez probablement mettre en place une gestion des erreurs s'il y a une chance d'échec de loadUser)!

Cela devrait rendre votre code userState beaucoup plus propre =).

Et pour info, ce que j'ai fait dans mon UserModel c'est avoir un bool get loggedIn => ... qui sait quelles informations doivent y figurer pour savoir si l'utilisateur est connecté ou non. De cette façon, je n'ai pas besoin de le suivre séparément, mais j'ai toujours un moyen simple et agréable de le dire de l'extérieur du modèle.

3
rmtmckenzie

Faites comme ceci comme exemple donné dans this :

void addPostFrameCallback(FrameCallback callback) {
  // Login logic code 
}

Planifiez un rappel pour la fin de cette trame.

Ne demande pas de nouveau cadre.

Ce rappel est exécuté pendant une trame, juste après les rappels de trame persistants (c'est-à-dire lorsque le pipeline de rendu principal a été vidé). Si une trame est en cours et que les rappels post-trame n'ont pas encore été exécutés, le rappel enregistré est toujours exécuté pendant la trame. Sinon, le rappel enregistré est exécuté pendant la trame suivante.

Les rappels sont exécutés dans l'ordre dans lequel ils ont été ajoutés.

Les rappels post-trame ne peuvent pas être désenregistrés. Ils sont appelés exactement une fois.

0
Hank Moody