StreamBuilder ListView在首次加载时从Firestore返回空列表

时间:2020-02-26 14:24:38

标签: flutter dart google-cloud-firestore stream-builder

在注册过程中,我正在构建的应用程序中为每个“用户”文档创建了一个子集合,最多包含100个文档。

我正试图在StreamBuilder中显示这些子集合文档。

我有一个无法解决的奇怪错误。用户首次查看时,StreamBuilder不会显示数据。而是返回一个空列表。

我可以看到文档已在子集合中正确生成。正在使用StreamBuilder在页面之前的页面上设置数据。即使存在延迟,我也会以为新文档会刚开始出现在StreamBuilder中。 Firebase console view

如果应用重新启动,或者用户注销并再次登录,StreamBuilder 显示预期的数据。

以下是我正在使用的代码:

Stream<QuerySnapshot> provideActivityStream() {
    return Firestore.instance
        .collection("users")
        .document(widget.userId)
        .collection('activities')
        .orderBy('startDate', descending: true)     
        .snapshots();
  }
...
Widget activityStream() {
  return Container(
      padding: const EdgeInsets.all(20.0),
      child: StreamBuilder<QuerySnapshot>(
      stream: provideActivityStream(),
      builder: (BuildContext context,
          AsyncSnapshot<QuerySnapshot> snapshot) {
        if (snapshot.hasError)
          return new Text('Error: ${snapshot.error}');
        if(snapshot.data == null) {
          return CircularProgressIndicator();
        }
        if(snapshot.data.documents.length < 1) {
          return new Text(
            snapshot.data.documents.toString()
            );
        }
        if (snapshot != null) {
          print('$currentUser.userId');
        }
        if (
          snapshot.hasData && snapshot.data.documents.length > 0
          ) {
          print("I have documents");
          return new ListView(
              children: snapshot.data.documents.map((
                  DocumentSnapshot document) {
                  return new PointCard(
                    title: document['title'],
                    type: document['type'],
                  );
                }).toList(),
            );
        }
      } 
    )
  );
}

编辑:根据评论请求添加主版本

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: Text("Home"),
          actions: <Widget>[
          ],
          bottom: TabBar(
            tabs: [
              Text("Account"),
              Text("Activity"),
              Text("Links"),
            ],
          ),
        ),
        body: TabBarView(
          children: [
            accountStream(),
            activityStream(),
            linksStream()
            ]
          )
        ),
      );
    }
  }

我尝试解决的问题

我最初以为是连接错误,因此基于switch (snapshot.connectionState)创建了一系列案例。我可以看到ConnectionState.active = true,因此认为在Firestore中添加新文档可能会产生效果,但是什么也没做。

我尝试了以下操作以使初始流构造函数异步。无法加载任何数据。

Stream<QuerySnapshot> provideActivityStream() async* {
    await Firestore.instance
        .collection("users")
        .document(widget.userId)
        .collection('activities')
        .orderBy('startDate', descending: true)     
        .snapshots();
  }

我尝试删除tabcontroller元素-例如仅有一个页面-但这也无济于事。

我尝试使用DocumentSnapshotQuerySnapshot来访问数据。我两个都有问题。

我敢肯定这很简单,但是坚持下去。任何帮助,不胜感激。谢谢!

3 个答案:

答案 0 :(得分:0)

不能通过使用任何查询快照和文档快照来获取

您应该首先使用Querysnapshot进行查询,然后将信息检索到Documentsnapshot 是的,可能需要花费几秒钟的时间来加载文档,因此您应该使用异步和等待功能的解决方案是正确的

建议您使用直接快照而不是streamBuilder

我们可以在statefullWidget的initstate中加载文档快照 当您的课程是一个statefullWidget且问题也与状态有关时,

...

 bool isLoading;
 List<DocumentSnapshot> activity =[];
 QuerySnapshot user;
 @override
 void initState() {
  print("in init state");
  super.initState();
  getDocument();
 ``    } 
  getDocument() async{

 setState(() {
   isLoading = true;
 });
 user= await Firestore.instance
    .collection("users")
    .document(widget.userId)
    .collection('activities')
    .orderBy('startDate', descending: true)     
    .getDocuments();

  activity.isEmpty ? activity.addAll(user.documents) : null;

    setState(() {
  isLoading = false;
       });

     }

//inside  Widget build(BuildContext context) { return  Scaffold( in your body 
//section of scaffold in the cointainer
Container(
padding: const EdgeInsets.all(20.0),
child: isLoading ?
         CircularProgressIndicator(),
        :ListView.builder(

                          itemCount: global.category.length,
                          itemBuilder: (context, index) {
                            return  PointCard(
                                    title: activity[index].data['title'],
                                      type: activity[index].data['type'],
                                    );//pointcard
                             }
                            ),//builder
                     ),//container

答案 1 :(得分:0)

我们也可以尝试以下方法

 QuerySnapshot qs;
 Stream<QuerySnapshot> provideActivityStream() async{
    qs= await Firestore.instance
             .collection("users")
             .document(widget.userId)
             .collection('activities')
            .orderBy('startDate', descending: true)     
            .snapshots();

      return qs;
  }//this should work

但是如果以上内容不起作用,请按照streambuilder的基础知识进行操作 然后还有另一个

     QuerySnapshot qs;
 Stream<QuerySnapshot> provideActivityStream() async* {
    qs= await Firestore.instance
             .collection("users")
             .document(widget.userId)
             .collection('activities')
            .orderBy('startDate', descending: true)     
            .snapshots();

      yield qs;
  }//give this a try

答案 2 :(得分:0)

tl; dr

  • 需要使用setState来使Firebase currentUser uid可用于小部件
  • 需要使用AutomaticKeepAliveClientMixinTabBar一起正常工作
  • 我认为使用Provider软件包可能是保持用户状态的更好方法,但不能解决此问题

说明

我的代码获取带有Future的currentUser uid。根据{{​​3}},这是一个问题,因为所有小部件都将在FirebaseAuth可以退还uid之前构建。最初,我尝试使用initState来获取uid,但这具有完全相同的同步问题。从函数中调用setState来调用FirebaseAuth.instance使得小部件树得以更新。

我将这个小部件放置在TabBar小部件中。我的理解是,每次从视图中删除选项卡时,都会对其进行处理,以便在返回时进行重新构建。这引起了进一步的州问题。用于AutomaticKeepAlive混合的API文档为the SO answer here

解决方案代码

添加了一些注释,希望它们对其他人的理解有所帮助(或某些人可以纠正我的误解)

activitylist.dart

class ActivityList extends StatefulWidget {
// Need a stateful widget to use initState and setState later
  @override
  _ActivityListState createState() => _ActivityListState();
}

class _ActivityListState extends State<ActivityList> 
  with AutomaticKeepAliveClientMixin<ActivityList>{
  // `with AutomaticKeepAliveClientMixin` added for TabBar state issues

  @override
  bool get wantKeepAlive => true;
  // above override required for mixin

  final databaseReference = Firestore.instance;

  @override
    initState() {
      this.getCurrentUser(); // call the void getCurrentUser function
      super.initState();
  }

  FirebaseUser currentUser;

  void getCurrentUser() async {
    currentUser = await FirebaseAuth.instance.currentUser();
    setState(() {
      currentUser.uid;
    });
    // calling setState allows widgets to access uid and access stream
  }

  Stream<QuerySnapshot> provideActivityStream() async* {
    yield* Firestore.instance
        .collection("users")
        .document(currentUser.uid)
        .collection('activities')
        .orderBy('startDate', descending: true)     
        .snapshots();
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Container(
                  padding: const EdgeInsets.all(20.0),
                  child: StreamBuilder<QuerySnapshot>(
                  stream: provideActivityStream(),
                  builder: (BuildContext context,
                  AsyncSnapshot<QuerySnapshot> snapshot) {
                    if(snapshot.hasError) return CircularProgressIndicator();
                    if(snapshot.data == null) return CircularProgressIndicator();
                    else if(snapshot.data !=null) {
                      return new ListView(
                          children: snapshot.data.documents.map((
                              DocumentSnapshot document) {
                            return new ActivityCard(
                              title: document['title'],
                              type: document['type'],
                              startDateLocal: document['startDateLocal'],
                            );
                          }).toList(),
                        );
                    }
                  },
                )
            );
  }
}

home.dart

...
@override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: Text("Home"),
          actions: <Widget>[
          ],
          bottom: TabBar(
            tabs: [
              Text("Account"),
              Text("Activity"),
              Text("Links"),
            ],
          ),
        ),
        body: TabBarView(
          children: [
            accountStream(),
            ActivityList(), // now calling a stateful widget in an external file
            linksStream()
            ]
          )
        ),
      );
    }
  }