Flutter:StreamProvider 的奇怪行为,使用不完整数据重建的小部件

标签 flutter flutter-provider

我从 StreamProvider 获取的数据不完整。

下一个最小的小部件树重现了我的问题:选项卡式屏幕上的 SreamProvider。

我的用户对象包含多个值(以及其他属性)的映射,我通过调用 final user = Provider.of<User>(context); 将这些值用于选项卡式 View 的一个屏幕。在build()里面方法以便在应用程序启动或完全重建(即热重载)时在此屏幕中获取这些值。

问题:每当我切换标签并返回此标签时,build()使用 final user = Provider.of<User>(context); 仅调用一次方法返回用户的不完整副本:缺少某些数据(用户对象内 map 的某些属性)和 build()永远不会再次调用来完成数据(假设 StreamProvider 应该在某个时候返回完整的对象,可能会导致一些重建。结果:一些数据丢失并且无法构建 Screen 的一些小部件。

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}) 显示了不完整的 map (统计数据)和 build() 永远不会再次调用(使用剩余数据),因此 User 对象仍然不完整。它仅在重新加载或启动应用程序时被调用两次(数据与完整对象一起返回)。

欢迎任何解决方案!

PD:我找到了一种更简单的方法来重现这种情况。无需更改标签:

内部FirstScreen()我有一列 StatelessWidgets。如果我调用 Provider.of<User>(context)在其中一个中,我得到了该对象的不完整版本。 user.stats map 具有通过 Provider.of 访问它时所具有的键值对的一半。上面的一些小部件。

这是从 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 中的统计数据 Map: stats map

最佳答案

虽然 firebase 确实允许您使用 map 作为您使用的类型,但我认为在这种情况下这不是您的最佳选择,因为它看起来很困惑。

您的问题有多种解决方案。

我认为最好的方法是将您的用户集合并排创建一个名为“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 更改为此。

Map<String, dynamic> stats = Map<String, dynamic>();

删除 try 和 catch block ,然后您应该能够像这样访问您的数据 user.stats['average'];

希望这会有所帮助,当我实际可以在模拟器中进行测试时,我会更新我的答案,但如果您尝试了并且成功了,请告诉我。

关于Flutter:StreamProvider 的奇怪行为,使用不完整数据重建的小部件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63656437/

相关文章:

firebase - Flutter Firebase Cloud Messaging - 应用程序在后台时的通知

forms - Flutter GetX 表单验证

Flutter:Hero widget、Navigation 2.0 和 Provider 不能一起正常工作

flutter - 允许提供者之间的区别

android.content.res.Resources$NotFoundException : File res/drawable-v21/launch_background. xml

flutter - 无法加载 Assets

android - 当我将 MediaQuery.of(context).padding.top 与 appBar 一起使用时,它返回 0.0。这是为什么?

flutter - 难以将商品添加到购物车 - Flutter

flutter - 如何检查提供的类是否存在

Flutter Provider 不会重建小部件