Как я могу запретить моему поставщику уведомлений об изменениях перестроить приложение родительского материала, когда я визуализирую приложение дочернего материала?
У меня есть класс приложения, который возвращает MaterialApp()
где его дом установлен на TheSplashPage()
. Это приложение слушает уведомление о предпочтениях, если какие-либо предпочтения изменены.
Затем в TheSplashPage()
Я жду, пока некоторые условные выражения станут истинными, и если они верны, я показываю им свое вложенное приложение материала.
Боковое примечание: здесь я использую приложение с материалами, потому что оно кажется более логичным, поскольку у него есть маршруты, которых не должно быть у родительского приложения с материалами. А также, как только пользователь не аутентифицируется или отключается, я хочу, чтобы все вложенное приложение закрылось и показало другую страницу. Это прекрасно работает!
Но моя проблема в следующем. Оба приложения слушаютThePreferencesProvider()
поэтому при изменении темы они оба получают уведомление и перестраиваются. Но это проблема, потому что всякий раз, когда приложение родительского материала перестраивается, оно возвращает страницу-заставку. Итак, теперь я снова наTheSplashPage()
всякий раз, когда я меняю настройку на TheSettingsPage()
.
Итак, у меня вопрос: как я могу остановить возвращение моего приложения к TheSplashPage()
всякий раз, когда я меняю настройку?
Main.dart
void main() {
runApp(App());
}
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIOverlays([]);
return MultiProvider(
providers: [
ChangeNotifierProvider<PreferencesProvider>(create: (_) => PreferencesProvider()),
ChangeNotifierProvider<ConnectionProvider>(
create: (_) => ConnectionProvider(),
),
ChangeNotifierProvider<AuthenticationProvider>(create: (_) => AuthenticationProvider()),
],
child: Consumer<PreferencesProvider>(builder: (context, preferences, _) {
return MaterialApp(
home: TheSplashPage(),
theme: preferences.isDarkMode ? DarkTheme.themeData : LightTheme.themeData,
debugShowCheckedModeBanner: false,
);
}),
);
}
}
TheSplashPage.dart
class TheSplashPage extends StatelessWidget {
static const int fakeDelayInSeconds = 2;
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.delayed(new Duration(seconds: fakeDelayInSeconds)),
builder: (context, delaySnapshot) {
return Consumer<ConnectionProvider>(
builder: (BuildContext context, ConnectionProvider connectionProvider, _) {
if (delaySnapshot.connectionState != ConnectionState.done ||
connectionProvider.state == ConnectionStatus.uninitialized) return _buildTheSplashPage(context);
if (connectionProvider.state == ConnectionStatus.none) return TheDisconnectedPage();
return Consumer<AuthenticationProvider>(
builder: (BuildContext context, AuthenticationProvider authenticationProvider, _) {
switch (authenticationProvider.status) {
case AuthenticationStatus.unauthenticated:
return TheRegisterPage();
case AuthenticationStatus.authenticating:
return TheLoadingPage();
case AuthenticationStatus.authenticated:
return MultiProvider(
providers: [
Provider<DatabaseProvider>(create: (_) => DatabaseProvider()),
],
child: Consumer<PreferencesProvider>(
builder: (context, preferences, _) => MaterialApp(
home: TheGroupManagementPage(),
routes: <String, WidgetBuilder>{
TheGroupManagementPage.routeName: (BuildContext context) => TheGroupManagementPage(),
TheGroupCreationPage.routeName: (BuildContext context) => TheGroupCreationPage(),
TheGroupPage.routeName: (BuildContext context) => TheGroupPage(),
TheSettingsPage.routeName: (BuildContext context) => TheSettingsPage(),
TheProfilePage.routeName: (BuildContext context) => TheProfilePage(),
TheContactsPage.routeName: (BuildContext context) => TheContactsPage(),
},
theme: preferences.isDarkMode ? DarkTheme.themeData : LightTheme.themeData,
debugShowCheckedModeBanner: false,
)),
);
}
});
});
});
}
TheSettingsPage.dart
Switch(
value: preferences.isDarkMode,
onChanged: (isDarkmode) => preferences.isDarkMode = isDarkmode,
),
3 ответа
Вы попались на проблему XY
Настоящая проблема здесь не в том, что "мой виджет слишком часто перестраивается", а в том, что "когда мой виджет перестраивается, мое приложение возвращается на страницу-заставку".
Решение состоит не в том, чтобы предотвратить перестройку, а вместо этого изменить вашbuild
Таким образом, он устраняет проблему, о чем я подробно рассказывал здесь ранее: Как бороться с нежелательной сборкой виджета?
Вы столкнулись с той же проблемой, что и в вопросе с перекрестными ссылками: вы неправильно использовали FutureBuilder
.
НЕ:
@override
Widget build(BuildContext context) {
return FutureBuilder(
// BAD: will recreate the future when the widget rebuild
future: Future.delayed(new Duration(seconds: fakeDelayInSeconds)),
...
);
}
ДЕЛАТЬ:
class Example extends StatefulWidget {
@override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
// Cache the future in a StatefulWidget so that it is created only once
final fakeDelayInSeconds = Future<void>.delayed(const Duration(seconds: 2));
@override
Widget build(BuildContext context) {
return FutureBuilder(
// Rebuilding the widget no longer recreates the future
future: fakeDelayInSeconds,
...
);
}
}
При использовании Consumer вы заставляете виджет перестраиваться каждый раз, когда уведомляете слушателей.
Чтобы избежать такого поведения, вы можете использовать Provider.of, как указано в ответе ian villamia, поскольку его можно использовать везде, где вам это нужно, и только там, где вам это нужно.
Изменения в вашем коде для использования Provider.of будут удалять потребителя и добавлять Provider.of при разрешении темы следующим образом:
theme: Provider.of<PreferencesProvider>(context).isDarkMode ? DarkTheme.themeData : LightTheme.themeData,
ОДНАКО, если вы хотите продолжать использовать Consumer, вы можете сделать что-нибудь еще:
Дочернее свойство в виджете Consumer - это дочерний элемент, который не перестраивается. Вы можете использовать это, чтобы установить там TheSpashScreen и передать его в materialApp через конструктор.
TL:DR
Используйте Provider.of, если для простоты вам нужно использовать только одну переменную.
Используйте Consumer с его дочерним свойством, поскольку дочерний элемент не перестраивается. <= Лучшая производительность
Использование Provider.of
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIOverlays([]);
return MultiProvider(
providers: [
ChangeNotifierProvider<PreferencesProvider>(create: (_) => PreferencesProvider()),
ChangeNotifierProvider<ConnectionProvider>(
create: (_) => ConnectionProvider(),
),
ChangeNotifierProvider<AuthenticationProvider>(create: (_) => AuthenticationProvider()),
],
child: Builder(
builder: (ctx) {
return MaterialApp(
home: TheSpashPage(),
theme: Provider.of<PreferencesProvider>(ctx).isDarkMode ? DarkTheme.themeData : LightTheme.themeData,
);
}),
);
}
}
Использование Consumer
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIOverlays([]);
return MultiProvider(
providers: [
ChangeNotifierProvider<PreferencesProvider>(create: (_) => PreferencesProvider()),
ChangeNotifierProvider<ConnectionProvider>(
create: (_) => ConnectionProvider(),
),
ChangeNotifierProvider<AuthenticationProvider>(create: (_) => AuthenticationProvider()),
],
child: Consumer<PreferencesProvider>(
child: TheSpashPage(),
builder: (context, preferences, child) {
return MaterialApp(
home: child,
theme: preferences.isDarkMode ? DarkTheme.themeData : LightTheme.themeData,
debugShowCheckedModeBanner: false,
);
}),
);
}
}
Надеюсь, это будет вам полезно!
В основном есть 2 способа использования провайдера
- один из них - текущий, который вы используете, который является типом потребителя,
- использует экземпляр провайдера
final _preferencesProvider= Provider.of<PreferencesProvider>(context, listen: false);
вы можете переключить "listen:true", если хотите, чтобы виджет перестраивался при вызове notifyListeners()... false, если в противном случае также просто используйте _preferencesProvider.someValue, как любой другой экземпляр