3

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

然后在TheSplashPage()我等待一些条件为真,如果它们是我向他们展示我的嵌套材料应用程序。

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

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

所以我的问题是如何阻止我的应用程序TheSplashPage()在我更改设置时返回?

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

设置页面.dart

Switch(
  value: preferences.isDarkMode,
  onChanged: (isDarkmode) => preferences.isDarkMode = isDarkmode,
),
4

3 回答 3

7

你爱上了 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,
      ...
    );
  }
}
于 2020-04-14T20:29:26.793 回答
4

使用 Consumer 时,每次通知侦听器时都会强制小部件重新构建。

为避免此类行为,您可以按照 ian villamia 的回答中所述使用 Provider.of,因为它可以在您需要的任何地方使用,并且仅在您需要的地方使用。

使用 Provider.of 的代码中的更改将删除消费者并在解决主题时添加 Provider.of,如下所示:

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

但是,如果您想继续使用 Consumer,您可以执行其他操作:

Consumer 小部件上的子属性是未重建的子属性。您可以使用它在那里设置 TheSpashScreen,并通过构建器将其传递给 materialApp。

TL:博士

如果您为了简单起见只需要利用一个变量,请使用 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,
            );
          }),
        );
    }
}

我希望这对你有帮助!

于 2020-04-13T18:29:55.663 回答
3

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

  1. 一个是您当前正在使用的消费者类型,
  2. 正在使用提供者的实例
 final _preferencesProvider= Provider.of<PreferencesProvider>(context, listen: false);

如果您希望在调用 notifyListeners() 时重建小部件,则可以切换“listen:true”...如果不是,则返回 false 也可以像任何其他实例一样使用 _preferencesProvider.someValue

于 2020-04-11T13:37:41.457 回答