首页 文章

将来自Firestore的流组合起来

提问于
浏览
2

我一直试图使用 StreamBuilder 或类似的东西从Firestone收听多个集合 . 当我只使用一个Stream时,我的原始代码是:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

class List extends StatefulWidget{

  ///The reference to the collection is like
  ///Firestore.instance.collection("users").document(firebaseUser.uid).collection("list1").reference()
  final CollectionReference listReference;

  List(this.listReference);

  @override
  State createState() => new ListState();
}

class ListState extends State<List> {

  @override
  Widget build(BuildContext context){

    return new StreamBuilder(
        stream: widget.listReference.snapshots(),
        builder: (context, snapshot) {
          return new ListView.builder(
              itemCount: snapshot.data.documents.length,
              padding: const EdgeInsets.only(top: 2.0),
              itemExtent: 130.0,
              itemBuilder: (context, index) {
                DocumentSnapshot ds = snapshot.data.documents[index];
                return new Data(ds);
              }
          );
        });
  }
}

这段代码工作正常,但现在我想收听多个集合 . 我遇到了一个solution,它不涉及StreamBuilder并且使用动态列表 . 我的代码现在看起来像这样:

import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'main.dart';
import 'package:async/async.dart';

class ListHandler extends StatefulWidget{

  final CollectionReference listReference;

  ListHandler(this.listReference);

  @override
  State createState() => new ListHandlerState();
}

class ListHandlerState extends State<ListHandler> {

  StreamController streamController;
  List<dynamic> dataList = [];

  @override
  void initState() {
    streamController = StreamController.broadcast();
    setupData();
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
    streamController?.close();
    streamController = null;
  }

  Future<Stream> getData() async{
      Stream stream1 = Firestore.instance.collection("users").document(firebaseUser.uid).collection("list1").snapshots();
      Stream stream2 = Firestore.instance.collection("users").document(firebaseUser.uid).collection("list2").snapshots();

      return StreamZip(([stream1, stream2])).asBroadcastStream();
  }

  setupData() async {
    Stream stream = await getData()..asBroadcastStream();
    stream.listen((snapshot) {
      setState(() {
        //Empty the list to avoid repetitions when the users updates the 
        //data in the snapshot
        dataList =[];
        List<DocumentSnapshot> list;
        for(int i=0; i < snapshot.length; i++){
          list = snapshot[i].documents;
          for (var item in list){
            dataList.add(item);
          }
        }
      });
    });
  }

  @override
  Widget build(BuildContext context){
    if(dataList.length == 0){
      return new Text("No data found");
    }

    return new ListView.builder(
        itemCount: dataList.length,
        padding: const EdgeInsets.only(top: 2.0),
        itemBuilder: (context, index) {
          DocumentSnapshot ds = dataList[index];
          return new Data(ds['title']);
        }
    );
  }
}

问题是ListView返回的 Data 是一个 StatefulWidget ,用户可以与它进行交互,使Firestore中的数据发生变化,从而出现下一个错误:

[VERBOSE-2:dart_error.cc(16)] Unhandled exception:
setState() called after dispose(): ListHandlerState#81967(lifecycle state: defunct, not mounted)
This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback. The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().

该应用程序不会崩溃,它会执行预期但它始终显示此错误 .

有些人使用库 rxdart 来处理流,我尝试过像下面的代码那样的东西,但是当我把它放在StreamBuilder中时,只有以下代码:

import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'main.dart';
import 'showInfo.dart';
import 'package:rxdart/rxdart.dart';

class ListHandler extends StatefulWidget{

  @override
  State createState() => new ListHandlerState();
}

class ListHandlerState extends State<ListHandler> {

  Stream getData() {
    Stream stream1 = Firestore.instance.collection("users").document(firebaseUser.uid).collection("list1").snapshots();
    Stream stream2 = Firestore.instance.collection("users").document(firebaseUser.uid).collection("list2").snapshots();

    return Observable.merge(([stream2, stream1]));
  }

  @override
  Widget build(BuildContext context){
    return new StreamBuilder(
        stream: getData(),
        builder: (context, snapshot) {
          if(!snapshot.hasData){
            print(snapshot);
            return new Text("loading");
          }
          return new ListView.builder(
              itemCount: snapshot.data.documents.length,
              padding: const EdgeInsets.only(top: 2.0),
              itemBuilder: (context, index) {
                DocumentSnapshot ds = snapshot.data.documents[index];
                return new Data(ds);
              }
          );
        });
  }
}

这是我第一次使用Streams,我不太了解它们,我想你想做什么 .

2 回答

  • 1

    问题不在于合并,而是在StreamBuilder中基于LATEST快照更新UI,换句话说,它不会堆叠快照,它只是拾取最后发出的事件,换句话说,流被合并并合并stream确实包含所有合并流的数据,但是streamBuilder只显示最后一个流发出的事件,解决方法是:

    StreamBuilder<List<QuerySnapshot>>(stream: streamGroup, builder: (BuildContext context, 
        AsyncSnapshot<List<QuerySnapshot>> snapshotList){
                      if(!snapshotList.hasData){
                        return MyLoadingWidget();
                      }
                      // note that snapshotList.data is the actual list of querysnapshots, snapshotList alone is just an AsyncSnapshot
    
                      int lengthOfDocs=0;
                      int querySnapShotCounter = 0;
                      snapshotList.data.forEach((snap){lengthOfDocs = lengthOfDocs + snap.documents.length;});
                      int counter = 0;
                      return ListView.builder(
                        itemCount: lengthOfDocs,
                        itemBuilder: (_,int index){
                          try{DocumentSnapshot doc = snapshotList.data[querySnapShotCounter].documents[counter];
                          counter = counter + 1 ;
                           return new Container(child: Text(doc.data["name"]));
                          }
                          catch(RangeError){
                            querySnapShotCounter = querySnapShotCounter+1;
                            counter = 0;
                            DocumentSnapshot doc = snapshotList.data[querySnapShotCounter].documents[counter];
                            counter = counter + 1 ;
                             return new Container(child: Text(doc.data["name"]));
                          }
    
                        },
                      );
                    },
    
  • 2

    最后一个例子应该有效 . 也许第二个流在您观察流时没有发出任何值 .

    来自 async 包的 StreamGroup.merge() 也应该有效 .

    StreamZip 为每个流创建一对值 . 当它们以不同的速率发出值时,则一个流等待发射,直到另一个发出值 . 这可能不是你想要的 .

相关问题