更正 Flutter 小部件序列以在应用程序加载时提取数据

Correct Flutter widget sequence to pull data on app load

当我尝试在加载应用程序时从本地存储中读取数据时,我 运行 遇到了抖动问题。

我有一个继承的小部件,它保存当前用户的身份验证信息。当应用程序加载时,我想查看会话令牌的本地存储。如果会话令牌存在,我想用此信息更新继承的小部件。

我的屏幕是动态的。如果它知道用户已通过身份验证,则会将他们带到请求的屏幕,否则会将他们带到注册屏幕。

我 运行 遇到的问题是我无法通过 initState() 方法更新继承的小部件的状态,该方法来自依赖于继承的小部件(我的路由器小部件)

当应用程序加载和更新继承的小部件时,如何从本地存储中读取?

运行 应用时出错:

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.

路由器小部件(根)

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,
          );
        });
  }
}

检查身份验证并更新自身的继承 Widget 方法会触发我的路由器小部件的重新渲染

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));
  }

主要

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

在应用加载时检查本地存储并更新保存此信息的继承小部件的正确方法是什么?

实际上您无法从 initState 方法访问 InheritedWidget。而是尝试从 didChangeDependencies.

访问它

示例:

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

另一种方法是使用 SchedulerBindinginitState 中安排数据获取。您可以找到文档 here

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

注意:请记住,只要任何父 InheritedWidget 的状态或依赖项发生变化,就会调用 didChangeDependencies。请查看文档 here.

希望对您有所帮助!

虽然@hemanth-raj 的回答是正确的,但我实际上提倡一种稍微不同的方法。您实际上可以在构建小部件并直接传递数据之前加载用户会话,而不是构建没有数据的 AuthContainer 。此示例使用 scoped_model plugin 抽象出继承的小部件样板(我强烈建议手动编写继承的小部件!)但在其他方面与您所做的非常相似。

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

void main() {
  startup();
}

这或多或少是我在我的应用程序中所做的,我没有遇到任何问题(尽管如果 loadUser 有任何可能失败,您可能想要进行一些错误处理)!

这应该会使您的 userState 代码更清晰 =)。

仅供参考,我在我的 UserModel 中所做的是有一个 bool get loggedIn => ... 知道需要在其中包含哪些信息来判断用户是否已登录。这样我就不需要单独跟踪它,但我仍然可以通过一种很好的简单方法从模型外部判断。

this 中给出的示例那样做:

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

安排此帧结束时的回调。

不请求新帧。

此回调是 运行 在帧期间,就在持久帧回调之后(即主渲染管道已被刷新时)。如果帧正在进行中并且尚未执行 post 帧回调,则注册的回调仍会在帧期间执行。否则,注册的回调将在下一帧执行。

回调按照添加的顺序执行。

Post帧回调无法注销。它们只被调用一次。