Как я могу помешать моему провайдеру уведомлений об изменениях перестроить мое родительское приложение для материала при рендеринге моего дочернего приложения для материала? - PullRequest
3 голосов
/ 07 апреля 2020

У меня есть класс приложения, который возвращает MaterialApp(), в котором его дом установлен на TheSplashPage(). Это приложение слушает оповещение о предпочтениях, если какие-либо предпочтения были изменены.

Затем в TheSplashPage() я жду, пока некоторые условия будут верны, и если они будут, я покажу им свое приложение с вложенными материалами.

Примечание : здесь я использую приложение материала, потому что оно кажется более логичным, поскольку у него есть маршруты, которых не должно быть в приложении родительского материала. А также, когда пользователь не прошел проверку подлинности или отключился, я хочу, чтобы все вложенное приложение закрылось и показывало другую страницу. Это прекрасно работает!

Но моя проблема в следующем. Оба приложения слушают ThePreferencesProvider(), поэтому при изменении темы они оба получают уведомление и перестраивают. Но это проблема, потому что всякий раз, когда приложение родительского материала перестраивается, оно возвращает страницу spla sh. Так что теперь я возвращаюсь на 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 ]

3 голосов
/ 13 апреля 2020

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

Чтобы избежать такого поведения, вы можете использовать Provider.of, как указано в ответе Иана Вильямия, так как его можно использовать везде, где это необходимо. он вам нужен, и только там, где он вам нужен.

Изменения в вашем коде для использования Provider.of приведут к удалению потребителя и добавлению Provider.of при разрешении темы следующим образом:

theme: Provider.of<PreferencesProvider>(context).isDarkMode ? DarkTheme.themeData : LightTheme.themeData,        

ОДНАКО если вы хотите продолжать использовать Consumer, вы можете сделать что-то еще:

Дочернее свойство в виджете Consumer является дочерним, который не перестраивается, Вы можете использовать это, чтобы установить там экран TheSpashScreen и передать его материалу приложения через компоновщик.

TL: DR

Используйте Provider.of, если для простоты вам нужно только нажать на одну переменную .

Используйте Consumer со своим дочерним свойством, поскольку дочерний объект не перестраивается. <= Лучшая производительность </strong>

Использование 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,
            );
          }),
        );
    }
}

Надеюсь, это полезно для вас!

3 голосов
/ 14 апреля 2020

Вы попали на проблему XY

Реальная проблема здесь не в том, что "мой виджет перестраивается слишком часто", а в том, что "когда мой виджет перестраивается, мое приложение возвращается на страницу spla sh".

Решением является не для предотвращения перестроений, а вместо этого изменение вашего 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)),
    ...
  );
}

DO :

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,
      ...
    );
  }
}
2 голосов
/ 11 апреля 2020

в основном есть 2 способа использования провайдера

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

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

...