【问题标题】:StreamBuilder ListView returns empty list from Firestore on first loadStreamBuilder ListView 在首次加载时从 Firestore 返回空列表
【发布时间】:2020-06-10 10:52:14
【问题描述】:

在我作为注册过程的一部分构建的应用程序中,为每个“用户”文档创建了一个子集合,其中包含多达 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 访问数据。我两个都有问题。

我确信这很简单,但坚持下去。非常感谢任何帮助。谢谢!

【问题讨论】:

  • 尝试传递 provideStravaActivityStream 作为对流的引用。
  • 您介意发布整个小部件树的代码吗?
  • @Neeraj 我已经编辑了这个问题。希望能给你更多的背景。它在 tabcontroller 中,但我不认为这是错误所在(因为我尝试将它作为独立页面加载并遇到同样的问题)。
  • @AbdelbakiBoukerche 感谢您发现错字。我已编辑问题以显示名称始终为provideActivityStream。如果我引用了错误的流,我将永远不会获得数据,但我会在重新加载事件中获得数据。
  • @heymonkeyriot 我的意思是使用 stream: provideActivityStream 而不是 stream: provideActivityStream()

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


【解决方案1】:

不是通过使用任何一个查询快照和文档快照来获取的

您应该首先使用 Querysnapshot 进行查询,然后将信息检索到 Documentsnapshot 是的,加载文档可能需要几秒钟,解决方案是正确的,您应该使用 async 和 await 函数

建议你使用 Direct snapshot 而不是 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

【讨论】:

  • 非常感谢分享这个。我会试试这个。也许我对 StreamBuilder 缺乏了解,但我认为使用 Streams 的目的是让我们不必担心 initState 和 setState。还是我误解了什么?
  • 你的概念是绝对正确的,但在我们的例子中,我们应该使用 async 和 await 因为必须从 firestore 查询数据,这些是强制性的
【解决方案2】:

我们也可以试试下面的

 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

【讨论】:

    【解决方案3】:

    tl;博士

    • 需要使用 setState 才能让 Firebase currentUser uid 可用于小部件
    • 需要使用AutomaticKeepAliveClientMixin 才能正确使用TabBar
    • 我认为使用 Provider 包可能是保持用户状态的更好方法,但不能解决此问题

    说明

    我的代码通过 Future 获取 currentUser uid。根据the SO answer here,这是一个问题,因为在FirebaseAuth 可以返回uid 之前,所有小部件都将被构建。我最初尝试使用initState 来获取uid,但这有完全相同的同步问题。从函数调用setState 以调用FirebaseAuth.instance 允许更新小部件树。

    我将此小部件放置在 TabBar 小部件中。我的理解是,每次从视图中删除选项卡时,它都会在返回时重新构建。这导致了进一步的状态问题。 AutomaticKeepAlive mixin 的 API 文档是 here

    解决方案代码

    添加了cmets,希望对其他人的理解有所帮助(或者有人可以纠正我的误解)

    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()
                ]
              )
            ),
          );
        }
      }
    

    【讨论】:

      猜你喜欢
      • 2019-10-01
      • 2022-07-04
      • 1970-01-01
      • 2021-08-12
      • 2021-09-23
      • 2019-06-28
      • 2019-12-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多