当我呈现我的子 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,
),
基本上有两种使用提供者的方法
- one it the current one you're using which is the consumer type,
- 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,
...
);
}
}
我有一个应用程序 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,
),
基本上有两种使用提供者的方法
- one it the current one you're using which is the consumer type,
- 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,
...
);
}
}