颤振有状态的小部件在热重载和 pushNamed 之后丢失数据
Posted
技术标签:
【中文标题】颤振有状态的小部件在热重载和 pushNamed 之后丢失数据【英文标题】:flutter stateful widget lose data on hot reload and after pushNamed 【发布时间】:2019-12-17 00:51:58 【问题描述】:我不确定要查找什么,因此请随时给我指点,以了解在哪里查找。我正在学习,所以这绝对是值得赞赏的。
我刚刚发现我可以在颤振中使用命名路由,并且我正在尝试转移到事物以使我的应用程序的行为更可预测。但是,在某些时候,我的 UserDataContainer Stateful Widget 会丢失他的状态值。所以我有一些用户数据,它消失了。
这里是一些代码。
这是我的用户数据容器:
// app_state_container.dart
//import 'package:advanced_app/models/app_state.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:myapp/FoodLib/user.dart';
class UserDataContainer extends StatefulWidget
// Your apps state is managed by the container
final UserDataType userdata = UserDataType();
// This widget is simply the root of the tree,
// so it has to have a child!
final Widget child;
UserDataContainer(@required this.child
// @required this.userdata,
)
print("CREATED USERDATA CONTAINER");
print("The name: " + this.userdata.name);
// This creates a method on the AppState that's just like 'of'
// On MediaQueries, Theme, etc
// This is the secret to accessing your AppState all over your app
static _UserDataContainerState of(BuildContext context)
return (context.inheritFromWidgetOfExactType(_InheritedUserDataContainer)
as _InheritedUserDataContainer)
.data;
@override
_UserDataContainerState createState() => new _UserDataContainerState();
class _UserDataContainerState extends State<UserDataContainer>
// Just padding the state through so we don't have to
// manipulate it with widget.state.
// UserDataType userdata = UserDataType();
String name = "test";
@override
void initState()
// You'll almost certainly want to do some logic
// in InitState of your AppStateContainer. In this example, we'll eventually
// write the methods to check the local state
// for existing users and all that.
super.initState();
// So the WidgetTree is actually
// AppStateContainer --> InheritedStateContainer --> The rest of your app.
@override
Widget build(BuildContext context)
return new _InheritedUserDataContainer(
data: this,
child: widget.child,
);
// This is likely all your InheritedWidget will ever need.
class _InheritedUserDataContainer extends InheritedWidget
// The data is whatever this widget is passing down.
final _UserDataContainerState data;
// InheritedWidgets are always just wrappers.
// So there has to be a child,
// Although Flutter just knows to build the Widget thats passed to it
// So you don't have have a build method or anything.
_InheritedUserDataContainer(
Key key,
@required this.data,
@required Widget child,
) : super(key: key, child: child);
// This is a better way to do this, which you'll see later.
// But basically, Flutter automatically calls this method when any data
// in this widget is changed.
// You can use this method to make sure that flutter actually should
// repaint the tree, or do nothing.
// It helps with performance.
@override
bool updateShouldNotify(_InheritedUserDataContainer old)
if(
data.widget.userdata.fooditems.length !=
old.data.widget.userdata.fooditems.length)
return true;
// print("SAME");
return false;
其中UserDataType
是包含消失数据的类。
这是我使用 Material 类的主要应用程序:
class MyApp extends StatelessWidget
@override
Widget build(BuildContext context)
return new DynamicTheme(
defaultBrightness: Brightness.light,
data: (brightness) => new ThemeData(
primarySwatch: Colors.teal,
brightness: brightness,
),
themedWidgetBuilder: (context, theme)
return UserDataContainer(
child: MaterialApp(
title: 'Title',
home: UserLoader(),
theme: theme,
routes:
// When navigating to the "/" route, build the FirstScreen widget.
'/userpage': (context) => User(),
'/settings': (context) => SettingsPage(),
'/saved_stuff' : (context) => SavedRecipesPage(),
'/database' : (context) => Database(),
'/new_list' : (context) => ShoppingKartPage(),
'/stats' : (context) => StatPage(),
// When navigating to the "/second" route, build the SecondScreen widget.
// '/second': (context) => SecondScreen(),
,
),
);
);
class UserLoader extends StatelessWidget
@override
Widget build(BuildContext context)
print("Version 1");
final ThemeData theme = Theme.of(context);
return FutureBuilder<void>(
future: UserDataContainer.of(context).widget.userdata.init(),
builder: (BuildContext context, AsyncSnapshot<void> snapshot)
if (snapshot.connectionState == ConnectionState.done)
print("Build user");
return User();
else
return SplashScreen();
);
class SplashScreen extends StatefulWidget
@override
_SplashScreenState createState() => new _SplashScreenState();
class _SplashScreenState extends State<SplashScreen>
@override
void initState()
super.initState();
@override
Widget build(BuildContext context)
return new Scaffold(
body: new Center(
child: new Image.asset('images/icon.png'),
),
);
材料应用程序有我的路线,并且 UserLoader 在那里,所以我可以在启动时加载保存在手机上的一些用户数据。它会放置一个启动画面并等待数据加载完毕。
在我的抽屉里,我已经用指定的版本替换了移动到特定页面的代码:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SettingsPage()));
成为
Navigator.of(context).pop();
例如。
但是,现在当我使用该应用程序时,UserDataContainer 有时会包含一个全新的 User() 实例(它是一个 dart 类,而不是小部件),我不知道为什么该应用程序会突然出现这样的行为。
编辑:
我尝试通过使用它而不是我的 futureloader 来删除 FutureBuilder,但 UserContainer 的状态每次都会重新生成(虽然不会再次初始化):
class LoadingScreen extends StatefulWidget
@override
_LoadingScreenState createState() => new _LoadingScreenState();
class _LoadingScreenState extends State<LoadingScreen>
@override
void initState()
super.initState();
SchedulerBinding.instance.addPostFrameCallback((_)
runInitTasks();
);
@protected
Future runInitTasks() async
await UserDataContainer.of(context).widget.userdata.init();
Navigator.of(context).pushReplacementNamed('/fridge');
@override
Widget build(BuildContext context)
return Scaffold(
body: new Center(
child: new Image.asset('images/icon.png'),
),
);
【问题讨论】:
我想,但我不确定。问题出在FutureBuilder<void>
及其构建器中,因为在第一次显示User()
页面后,您在其他页面中创建的任何setState
以重建小部件,此UserLoader
页面也将被重建为树.所以我的建议是找到新的方法,不要一次又一次地重建FutureBuilder<void>
。
有什么建议吗?计时器似乎很随意,但我只发现了这个
您可以使用Navigator.pushReplacement
或Navigator.pushReplacementNamed
代替return User();
。以防止不再重建此页面。代码将是if (snapshot.connectionState == ConnectionState.done) print("Build user"); // Navigator.pushReplacement method return SplashScreen();
这会触发此错误:在构建期间调用了 setState() 或 markNeedsBuild()。
不是相关问题。数据丢失的第一个问题解决了吗?
【参考方案1】:
Navigation 上数据丢失的原因是因为一旦导航到屏幕正在重建。使用shared_preferences 可以轻松跟踪简单的数据,但如果您想获得更大的灵活性,可以使用状态管理实现,例如使用provider 插件。这是文档中提供的关于如何使用提供程序的sample。
【讨论】:
以上是关于颤振有状态的小部件在热重载和 pushNamed 之后丢失数据的主要内容,如果未能解决你的问题,请参考以下文章