16

当我在应用程序加载时尝试从本地存储读取数据时遇到了颤振问题。

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

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

我遇到的问题是我无法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()
    )
  )
);

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

4

3 回答 3

20

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

例子:

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

另一种方法是initState使用SchedulerBinding. 你可以在这里找到文档

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

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

希望这可以帮助!

于 2019-03-18T07:03:59.330 回答
4

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

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

void main() {
  startup();
}

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

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

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

于 2019-03-21T17:52:28.750 回答
0

像下面给出的例子那样

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

在此帧结束时安排回调。

不请求新帧。

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

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

后帧回调不能取消注册。它们只被调用一次。

于 2019-03-24T19:36:24.013 回答