Исправлена последовательность виджетов Flutter для извлечения данных при загрузке приложения
Я сталкиваюсь с проблемой трепетания при попытке прочитать данные из локального хранилища при загрузке приложения.
У меня есть унаследованный виджет, который содержит информацию для аутентификации текущего пользователя. Когда приложение загружается, я хочу заглянуть в локальное хранилище для токенов сессии. Если токены сессии существуют, я бы хотел обновить унаследованный виджет этой информацией.
Мои экраны динамичны. Если он знает, что пользователь аутентифицирован, он выводит их на запрошенный экран, в противном случае он выводит их на экран регистрации.
Проблема, с которой я сталкиваюсь, заключается в том, что я не могу обновить состояние унаследованного виджета из initState()
метод из виджета, который зависит от унаследованного виджета (виджет My router)
Как я могу читать из локального хранилища, когда приложение загружает и обновляет унаследованный виджет?
Ошибка при запуске приложения:
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,
);
});
}
}
Унаследованный метод виджета, который проверяет подлинность и обновляет себя, что вызывает повторную визуализацию моего виджета маршрутизатора
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()
)
)
);
Как правильно проверить локальное хранилище при загрузке приложения и обновить унаследованный виджет, содержащий эту информацию?
3 ответа
На самом деле вы не можете получить доступ 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
изменения. Пожалуйста, посмотрите на документы здесь.
Надеюсь это поможет!
Хотя ответ @hemanth-raj правильный, я бы на самом деле рекомендовал немного другой способ сделать это. Вместо того чтобы строить AuthContainer
без данных вы могли бы на самом деле выполнить загрузку пользовательского сеанса, прежде чем создавать свои виджеты и передавать данные напрямую. В этом примере используется плагин scoped_model для абстрагирования шаблона унаследованных виджетов (который я настоятельно рекомендую переписывать унаследованные виджеты вручную!), Но в остальном он очень похож на то, что вы сделали.
Future startUp() async {
UserModel userModel = await loadUser();
runApp(
ScopedModel<UserModel>(
model: userModel,
child: ....
),
);
}
void main() {
startup();
}
Это более или менее то, что я делаю в своем приложении, и у меня не было никаких проблем с ним (хотя вы, вероятно, захотите добавить некоторую обработку ошибок, если есть вероятность сбоя loadUser)!
Это должно сделать ваш пользовательский код намного чище =).
И к вашему сведению, то, что я сделал в моей UserModel, это bool get loggedIn => ...
это знает, какая информация должна быть там, чтобы сказать, вошел ли пользователь в систему или нет. Таким образом, мне не нужно отслеживать это отдельно, но я все еще получаю хороший простой способ сказать извне модели.
Сделайте это как пример, приведенный в этом:
void addPostFrameCallback(FrameCallback callback) {
// Login logic code
}
Запланируйте обратный вызов для конца этого кадра.
Не запрашивает новый кадр.
Этот обратный вызов запускается во время кадра, сразу после постоянных обратных вызовов кадра (когда происходит сброс основного конвейера рендеринга). Если кадр выполняется, а обратные вызовы после кадра еще не были выполнены, то зарегистрированный обратный вызов все еще выполняется в течение кадра. В противном случае зарегистрированный обратный вызов выполняется в течение следующего кадра.
Обратные вызовы выполняются в том порядке, в котором они были добавлены.
Посткадровые обратные вызовы не могут быть незарегистрированными. Они называются ровно один раз.