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

У меня есть класс приложения, который возвращает 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 способа использования провайдера

  1. один из них - текущий, который вы используете, который является типом потребителя,
  2. использует экземпляр провайдера
 final _preferencesProvider= Provider.of<PreferencesProvider>(context, listen: false);

вы можете переключить "listen:true", если хотите, чтобы виджет перестраивался при вызове notifyListeners()... false, если в противном случае также просто используйте _preferencesProvider.someValue, как любой другой экземпляр

Другие вопросы по тегам