当我渲染我的子材料应用程序时,如何阻止我的更改通知提供程序重建我的父材料应用程序?

Posted

技术标签:

【中文标题】当我渲染我的子材料应用程序时,如何阻止我的更改通知提供程序重建我的父材料应用程序?【英文标题】:How can I stop my change notifier provider from rebuilding my parent material app when I am rendering my child material app? 【发布时间】:2020-07-19 15:11:04 【问题描述】:

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

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

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

但我的问题如下。两个应用程序都收听ThePreferencesProvider(),因此当主题更改时,它们都会收到通知并重建。但这是一个问题,因为每当父材质应用程序重建时,它都会返回启动页面。所以现在每当我更改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】:

你爱上了 XY 问题

这里真正的问题不是“我的小部件重建过于频繁”,而是“当我的小部件重建时,我的应用返回到启动页面”。

解决方案不是防止重建,而是更改您的 build 方法以解决问题,这是我之前在此处详细介绍的内容:How to deal with unwanted widget 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,
      ...
    );
  

【讨论】:

【参考方案2】:

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

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

您的代码中使用 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,
            );
          ),
        );
    

希望对你有帮助!

【讨论】:

【参考方案3】:

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

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

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

【讨论】:

以上是关于当我渲染我的子材料应用程序时,如何阻止我的更改通知提供程序重建我的父材料应用程序?的主要内容,如果未能解决你的问题,请参考以下文章

在不阻止的情况下发送批量通知电子邮件

如何以编程方式更改UIButton标签

当我打开某个屏幕时如何收不到通知? - 安卓

访问:当我输入新记录时,更改不会出现在我的子表单中

Vue 组件 prop 更改不会触发重新渲染

iOS 开发:推送通知阻止我的本地通知触发