如何解决Flutter:StreamProvider的奇怪行为,使用不完整的数据重建了小部件
我从StreamProvider获取的数据不完整。
下一个最小的小部件树重现了我的问题:选项卡式屏幕上的SreamProvider。
“我的用户”对象包含多个值的映射(以及其他属性),我通过在final user = Provider.of<User>(context);
方法内调用build()
将这些值用于选项卡式视图的一个屏幕,以获取这些值在该屏幕上,只要应用程序启动或完全重建(即热重载)即可。
问题:每当我切换选项卡并返回此选项卡时,build()
方法仅被调用一次,而final user = Provider.of<User>(context);
返回的是用户的不完整副本:一些数据丢失(用户对象内映射的某些属性),并且再也不会调用build()
来完成数据(假设StreamProvider应该在某个时候返回完整的对象,这可能会导致某些重建。)数据丢失,并且无法构建“屏幕”的某些小部件。
class Wrapper extends StatelessWidget {
@override
Widget build(BuildContext context) {
final firebaseUser = Provider.of<FirebaseUser>(context);
// return either Home or Authenticate widget:
if (firebaseUser == null) {
return WelcomeScreen();
} else {
return StreamProvider<User>.value(
value: FirestoreService(uid: firebaseUser.uid).user,child: TabsWrapper(),);
}
}
}
class TabsWrapper extends StatefulWidget {
final int initialIndex;
TabsWrapper({this.initialIndex: 0});
@override
_TabsWrapperState createState() => _TabsWrapperState();
}
class _TabsWrapperState extends State<TabsWrapper> with TickerProviderStateMixin {
TabController tabController;
@override
void initState() {
super.initState();
tabController = TabController(vsync: this,length: choices.length);
}
@override
Widget build(BuildContext context) {
return DefaultTabController(
initialIndex: widget.initialIndex,length: 3,child: Scaffold(
backgroundColor: lightBlue,bottomNavigationBar: TabBar(
controller: tabController,labelStyle: navi.copyWith(color: darkBlue),unselectedLabelColor: darkBlue,labelColor: darkBlue,indicatorColor: darkBlue,tabs: choices.map((TabScreen choice) {
return Tab(
text: choice.title,icon: Icon(choice.icon,color: darkBlue),);
}).toList(),),body: TabBarView(
controller: tabController,children: <Widget>[
FirstScreen(),SecondScreen(),ThirdScreen(),],);
}
}
有问题的屏幕(FirstScreen):
class FirstScreen extends StatelessWidget {
final TabController tabController;
final User user;
FirstScreen ();
@override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
print('**************************************** ${user.stats}'); //Here I can see the problem
return SomeBigWidget(user: user);
}
}
print($ {user.stats})显示不完整的地图(统计信息),build()
再也不会调用(使用剩余数据),因此User对象保持不完整。重新加载或启动应用程序时,只会调用两次(并且数据会与完整对象一起返回)。
欢迎任何解决方案!
PD:我找到了一种更简单的方法来重现这种情况。无需更改标签:
在FirstScreen()
内部,有一列StatelessWidgets。如果我在其中一个中调用Provider.of<User>(context)
,则会得到该对象的不完整版本。通过上面的某些小部件的Provider。访问user.stats
映射时,它具有键/值对的一半。
这是从Firebase更新流的方式。 (对象每次创建):
Stream<User> get user {
Stream<User> userStream = usersCollection.document(uid).snapshots().map((s) => User.fromSnapshot(s));
return userStream;
}
我也有updateShouldNotify = (_,__) => true;
用户模型:
class User {
String uid;
String name;
String country;
Map stats;
User.fromUID({@required this.uid});
User.fromSnapshot(DocumentSnapshot snapshot)
: uid = snapshot.documentID,name = snapshot.data['name'] ?? '',country = snapshot.data['country'] {
try {
stats = snapshot.data['stats'] ?? {};
} catch (e) {
print('[User.fromSnapshot] Exception building user from snapshot');
print(e);
}
}
}
这是Firestore中的统计数据映射:
解决方法
尽管firebase确实允许您使用地图作为一种类型供您使用,但在这种情况下,我认为这不是您的最佳选择,因为这似乎令人困惑。
您的问题有多种解决方案。
我认为最好的方法是并排创建一个名为“ stats”的用户集合,并使每个stat的docid =具有该统计信息的用户的userid,这将使您将来的查询更加轻松。
在此之后,您可以像获取用户一样使用已用于获取统计信息的方法。
class Stat {
String statid;
int avg;
int avg_score;
List<int> last_day;
int n_samples;
int total_score;
int words;
Stat.fromSnapshot(DocumentSnapshot snapshot){
statid = snapshot.documentID,avg = snapshot.data['average'] ?? '',//rest of data
}
}
也许您需要的简单解决方案是将User类中的地图更改为此。
Map<String,dynamic> stats = Map<String,dynamic>();
删除try and catch块,那么您应该能够像这样user.stats['average'];
希望这会有所帮助,当我可以将其实际在模拟器中进行测试时,我会更新我的答案,但是如果您尝试使用它并且可以让我知道。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。