Исправлена ​​последовательность виджетов Flutter для извлечения данных при загрузке приложения - PullRequest
8 голосов
/ 09 марта 2019

У меня проблема с трепетом при попытке прочитать данные из локального хранилища при загрузке приложения.

У меня есть унаследованный виджет, который содержит информацию для аутентификации текущего пользователя.Когда приложение загружается, я хочу заглянуть в локальное хранилище для токенов сессии.Если токены сессии существуют, я бы хотел обновить унаследованный виджет этой информацией.

Мои экраны динамические.Если он знает, что пользователь прошел проверку подлинности, он выводит их на запрошенный экран, в противном случае он выводит их на экран регистрации.

Проблема, с которой я сталкиваюсь, заключается в том, что я не могу обновить состояние унаследованного виджета из 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.

Виджет маршрутизатора (Root)

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

Унаследованный метод виджета, который проверяет подлинность и обновляет себя, который запускает повторное отображение моего виджета маршрутизатора

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

Main

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

Как правильно проверить локальное хранилище при загрузке приложения и обновить унаследованный виджет, содержащий эту информацию?

Ответы [ 3 ]

5 голосов
/ 18 марта 2019

На самом деле вы не можете получить доступ к InheritedWidget из метода initState.Вместо этого попробуйте получить к нему доступ из 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.Пожалуйста, посмотрите документы здесь .

Надеюсь, это поможет!

3 голосов
/ 21 марта 2019

Хотя ответ @ 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 => ..., который знает, какая информация должна быть там, чтобы сказать, является ли пользовательавторизован или нет.Таким образом, мне не нужно отслеживать это отдельно, но я все еще получаю хороший простой способ сказать извне модели.

0 голосов
/ 24 марта 2019

Сделайте так, как показано в примере this :

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

Запланировать обратный вызов для конца этого кадра.

Не запрашивает новый кадр.

Этот обратный вызов запускается во время кадра, сразу после постоянных обратных вызовов кадра (то есть, когда основной конвейер рендеринга был очищен). Если кадр выполняется, а обратные вызовы после кадра еще не были выполнены, то зарегистрированный обратный вызов все еще выполняется в течение кадра. В противном случае зарегистрированный обратный вызов выполняется в течение следующего кадра.

Обратные вызовы выполняются в том порядке, в котором они были добавлены.

Обратные обратные вызовы не могут быть незарегистрированными. Они называются ровно один раз.

...