当我呈现我的子 material 应用程序时,如何阻止我的更改通知程序提供者重建我的父 material 应用程序?

How can I stop my change notifier provider from rebuilding my parent material app when I am rendering my child material app?

我有一个应用程序 class,returns 一个 MaterialApp() 的主页设置为 TheSplashPage()。如果任何首选项发生更改,此应用会监听首选项通知程序。

然后在 TheSplashPage() 中,我等待某些条件为真,如果它们为真,我会向他们展示我的嵌套 material 应用程序。

旁注:我在这里使用 material 应用程序,因为它看起来更合乎逻辑,因为它有父 material 应用程序不应该有的路由.而且一旦用户未经身份验证或断开连接,我希望整个嵌套应用程序关闭并显示另一个页面。这很好用!

但我的问题如下。两个应用程序都监听 ThePreferencesProvider(),因此当主题更改时,它们都会收到通知并重新构建。但这是一个问题,因为每当父 material 应用程序重建时,它都会 returns 初始页面。所以现在每当我更改 TheSettingsPage().

上的设置时,我都会回到 TheSplashPage()

所以我的问题是如何阻止我的应用程序在我更改设置时返回到 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,
),

基本上有两种使用提供者的方法

  1. one it the current one you're using which is the consumer type,
  2. is using the instance of a provider
 final _preferencesProvider= Provider.of<PreferencesProvider>(context, listen: false);

you can toggle the "listen:true" if you want the widget to rebuild when notifyListeners() are called... false if otherwise also just use _preferencesProvider.someValue like any other instance

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

使用消费者

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

希望对您有所帮助!

你爱上了 XY 问题

这里真正的问题不是"my widget rebuilds too often",而是"when my widget rebuild, my app returns to the splash page"。

解决方案 不是 来防止重建,而是更改您的 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,
      ...
    );
  }
}