Flutter 如何处理 Stream 和 Future 返回错误

Posted

技术标签:

【中文标题】Flutter 如何处理 Stream 和 Future 返回错误【英文标题】:Flutter How to handle Stream and Future return error 【发布时间】:2022-01-19 13:42:36 【问题描述】:

我正在尝试流式传输我的 firebase firestore 字段。这是我的数据库中的代码。它在按钮中工作,我可以打印我想要的。但实际上我想使用 StreamBuilder 在我的主页中显示带有小部件的数据。

  getYukListFromDB() async 
    _firebaseFirestore
        .collection('customer')
        .get()
        .then((QuerySnapshot querySnapshot) 
      for (var docA in querySnapshot.docs) 
        debugPrint("shipment altındaki docs idsi = " + docA.id);
        _firebaseFirestore
            .collection('customer')
            .doc(docA.id)
            .collection('myYuks')
            .get()
            .then((QuerySnapshot querySnapshot) 
          for (var docB in querySnapshot.docs) 
            debugPrint("myYuk altındaki docs idsi = " + docB.id);
            _firebaseFirestore
                .collection('customer')
                .doc(docA.id)
                .collection('myYuks')
                .doc(docB.id)
                .get()
                .then((DocumentSnapshot documentSnapshot) 
              Map<String, dynamic> mapData =
                  documentSnapshot.data()! as Map<String, dynamic>;
              if (documentSnapshot.exists) 
                debugPrint("icerik =  $mapData['icerik']");
                debugPrint('doc var');
               else 
                debugPrint('doc yok');
              
            );
          
        );
      
    );

  
  

当我在流中尝试这个时,它给了我一个错误。

Stream<List<YukModel>> yeniYukStream(String uid)  // ***error here***
 _firebaseFirestore
        .collection('customer')
        .get()
        .then((QuerySnapshot querySnapshot) 
      for (var doc1 in querySnapshot.docs) 
        debugPrint("shipment altındaki docs idsi = " + doc1.id);
        return _firebaseFirestore
            .collection("customer")
            .doc(doc1.id)
            .collection('myYuks')
            .get()
            .then((QuerySnapshot querySnapshot) 
          for (var doc2 in querySnapshot.docs) 
            debugPrint("myYuk altındaki docs idsi = " + doc2.id);
            _firebaseFirestore
                .collection('customer')
                .doc(doc2.id)
                .collection('myYuks')
                .orderBy('createTime', descending: true)
                .snapshots()
                .map((QuerySnapshot querySnapshot) 
              List<YukModel> retVal = <YukModel>[];
              for (var lastData in querySnapshot.docs) 
                retVal.add(YukModel.fromDocumentSnapshot(lastData));
              
              return retVal;
            );
          
        );
      
    );
  

主体可能正常完成,导致返回“null”,但返回类型可能是不可为空的类型。 尝试在末尾添加 return 或 throw 语句。

而且我知道它应该是未来。让我们试试吧。

Future<Stream<List<YukModel>>> yeniYukStream(String uid) async
 return _firebaseFirestore
        .collection('customer')
        .get()
        .then((QuerySnapshot querySnapshot)  // ****error here****
      for (var doc1 in querySnapshot.docs) 
        debugPrint("shipment altındaki docs idsi = " + doc1.id);
        return _firebaseFirestore
            .collection("customer")
            .doc(doc1.id)
            .collection('myYuks')
            .get()
            .then((QuerySnapshot querySnapshot)  // ****error here****
          for (var doc2 in querySnapshot.docs) 
            debugPrint("myYuk altındaki docs idsi = " + doc2.id);
            _firebaseFirestore
                .collection('customer')
                .doc(doc2.id)
                .collection('myYuks')
                .orderBy('createTime', descending: true)
                .snapshots()
                .map((QuerySnapshot querySnapshot)  
              List<YukModel> retVal = <YukModel>[];
              for (var lastData in querySnapshot.docs) 
                retVal.add(YukModel.fromDocumentSnapshot(lastData));
              
              return retVal;
            );
          
        );
      
    );
  

****错误****行是这样的:

主体可能正常完成,导致返回“null”,但返回类型可能是不可为空的类型。 尝试在末尾添加 return 或 throw 语句。

这是我的 YukModel.dart 文件;

class YukModel 
  String? yukID;
  String? yukBaslik;
  String? icerik;
  int? agirlik;
  Timestamp? createTime;
  String? aracTipi;
  bool? onayDurumu;

  YukModel(
      this.yukID,
      this.yukBaslik,
      this.icerik,
      this.agirlik,
      this.createTime,
      this.aracTipi,
      this.onayDurumu);


  YukModel.fromDocumentSnapshot(DocumentSnapshot documentSnapshot) 
    yukID = documentSnapshot.id;
    yukBaslik = documentSnapshot.get('yukBaslik');
    icerik = documentSnapshot.get('icerik');
    agirlik = documentSnapshot.get('agirlik');
    createTime = documentSnapshot.get('createTime');
    aracTipi = documentSnapshot.get('aracTipi');
    onayDurumu = documentSnapshot.get('onayDurumu');
  

我该怎么办?另外,我正在使用 GetX 包进行状态管理。

我现在的解决方案

这是我的解决方案。它对我有用。此外,您可以使用此代码访问您的子集合。

在 HomePage 中,我添加了一个 StreamBuilder:

StreamBuilder(
        stream: FirebaseFirestore.instance
            .collection("customer")
            .doc(authController.doneUser!.uid) // you can use your uid
            .collection("myYuks")
            .snapshots(),
        builder: (BuildContext context,
            AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) 
          if (snapshot.hasData && snapshot.data != null) 
            if (snapshot.data!.docs.isNotEmpty) 
              return ListView.builder( 
                itemBuilder: (context, int index) 
                  Map<String, dynamic> docData =
                      snapshot.data!.docs[index].data();

                  if (docData.isEmpty) 
                    return const Center(child: Text("Data empty"));
                  

//these are my fields in subcollections.
// you can use like docData["yourfieldnameinsubcollection"];

                  String yukID = docData[FirestoreFields.yukID]; 
                  String yukBaslik = docData[FirestoreFields.yukBaslik];
                  String icerik = docData[FirestoreFields.icerik];
                  String agirlik = docData[FirestoreFields.agirlik];
                  return Card(
                    child: Container(
                      color: ColorConstants.birincilRenk,
                      height: 210,
                      child: Padding(
                        padding: const EdgeInsets.all(8.0),
                        child: Wrap(
                          children: [
                            Text(yukID, style: const TextStyle(fontSize: 16)),
                            const Divider(thickness: 1),
                            Text(yukBaslik,
                                style: const TextStyle(fontSize: 20)),
                            const Divider(thickness: 1),
                            Text(icerik, style: const TextStyle(fontSize: 16)),
                            const Divider(thickness: 1),
                            Text(agirlik, style: const TextStyle(fontSize: 16)),
                          ],
                        ),
                      ),
                    ),
                  );
                ,
                itemCount: snapshot.data!.docs.length,
              );
             else 
              return const Text("no data ");
            
           else 
            return const Text("loading");
          
        ,
      ),

【问题讨论】:

我在这里用 // **** 错误标记它****。查看第二个和第三个代码块。 看起来 firebase 承诺失败了。用 streambuilder 包装你的 firebase 调用。在您的异步函数 getData 中调用 firebase 端点,然后将数据返回到流构建器。尝试捕获 getData。 @GoldenLion 的最后一条评论对您有帮助吗?如果是,您能否请金狮将其添加为帮助社区的答案?谢谢。 我自己想出来的。 @TolgaYılmaz 您能否将解决方案添加为帮助社区解决相同问题的答案?谢谢。 【参考方案1】:

我没有仔细阅读你的整个代码,因为它太多了,但从错误消息中我可以帮助你理解问题。

她是您可能想要达到的目标的简化版本:

// This method returns the data from Firestore collection
Stream<T> getData()
  return collection.snapshots(); // This returns a Stream
  // get() returns a Future
  // snapshots() returns a Stream


// As the name says, it build from a STREAM
StreamBuilder(
  future: getData(), // the source of stream
  builder: (context,snapshot)
    if(snapshot.hasData)  // checking if the stream's snapshot has data
      return Text(snapshot.data!);   // If there is data, we display widgets accordingly
    else
      return const CircularProgressIndicator();    // If there isn't data yet, we display a CircularProgressIndicator
    
  
)

现在解决您的问题,如错误消息所述:

The body might complete normally, causing 'null' to be returned, but the return type is a potentially non-nullable type. Try adding either a return or a throw statement at the end.

下面是解释的例子:

StreamBuilder(
  future: getData(), // the source of stream
  builder: (context,snapshot)
    if(snapshot.hasData)  // checking if the stream's snapshot has data
      return Text(snapshot.data!);   // If there is data, we display widgets accordingly
    
  
)

如果你注意到了,我在这里没有使用 else 语句,所以这意味着: 如果快照有数据然后显示一个小部件,但是如果快照还没有数据,那么将显示什么。我会收到和你一样的错误。为了解决这个问题,我使用了else 语句。这样我的

我的身体不返回 null

【讨论】:

感谢您的回复。但我知道如何流式传输 'collection.snapshot()' 。但这并不能满足我的需求。请你分析一下我的代码好吗?【参考方案2】:

订阅者可以收听 Streambuilder。 MyClassBloc 包含一个名为 MyClassListStream 的 get 方法。 MyClassBloc 包含列表类型的数据流。 MyClassBlock 可以从 web api 提供者分配数据到 listMyClass 变量。在 _loadMyClassItems 方法中,调用了对 initializeStream 的调用,通知所有订阅者流上有数据。 _buildList 方法有一个流构建器,流指向 MyClassBloc 流控制器获取 MyClassListStream 评估 _MyClassListSubject.stream 和 initialData 引用 widget.blocMyClass.listMyClass。当数据到达流时,Streambuilder 映射流中的数据以创建 CardWidget 列表。 snapshot.data 是 MyClass 类型。因此 _buildList 返回从流创建的卡片小部件列表。一切从 initState() 调用 _loadMyClassItems 开始,web api 在 Promise 完成后返回 MyClass 对象列表并将数据分配给 MyClassBloc 变量并分配数据并调用 initializeTheStream。从 MyClassBloc _MyClassListSubject.add(listMyClass) 事件通知 Streambuilder 数据在流上。 Streambuilder 然后返回一个 CardWidgets 列表。

class MyClassBloc 
  List<MyClass> listMyClass;

  Stream<List<MyClass>> get MyClassListStream =>
      _MyClassListSubject.stream;
  final _MyClassListSubject =
      BehaviorSubject<List<MyClass>>();

  initializeTheStream() 
    if (listMyClass != null) 
      _MyClassListSubject.add(listMyClass);
    
  

  dispose() 
    _MyClassListSubject.close();
  



class MyClass
 
        int field1;
        String field2;

        MyClass(
        this.field1,
        this.field2,
        );

        Map<String,dynamic> toJson()
   var map=
       'field1': field1,
        'field2': field2,
      ;
      return map;
      

        factory MyClass.fromJson(Map<String,dynamic> json)
        if(json==null)throw FormatException("null json");
        return MyClass(
        json['field1'] as int,
        json['field2'] as String,
        );
  

    

 _loadMyClassItems(BuildContext context) 
    try 

      Provider.of<ApiWidget>(context, listen: false)
          .getMyClass()
          .then((value) 
        setState(() 
          loadSummary = true;
          if (value != null) 
            widget.blocSummary.listMyClass = value;
            widget.blocSummary.initializeTheStream();
           else 
            widget.blocSummary.listMyClass =
               [];
            widget.blocSummary.initializeTheStream();
          
        );
      ).catchError((error) 
        DialogCaller.showErrorDialog(context, error);
      );
     catch (e) 
      DialogCaller.showErrorDialog(context, e.toString());
    
  




Widget _buildList(BuildContext context) 
    List<Widget> cardWidgets;
    return StreamBuilder<List<MyClass>>(
        stream: widget.blocMyClass.MyClassListStream,
        initialData: widget.blocMyClass.listMyClass,
        builder: (context, snapshot) 
          //debugPrint(snapshot.connectionState.toString());
          if (!snapshot.hasData) 
            return PleaseWaitWidget();
           else if (snapshot.hasError) 
            DialogCaller.showErrorDialog(
                    context, "future builder in main has an error")
                .then((value) );
           else if (snapshot.hasData) 
            //debugPrint("reached processing");
            cardWidgets =
                snapshot.data.map((MyClass MyClass) 
              return CardWidget(MyClass);
            ).toList();
            return CardWidgets.length == 0
                ? Container(
                    padding: EdgeInsets.all(20),
                    child: Text("No Records found", style: NoRecordFoundStyle))
                : ListView(children: CardWidgets);
          
          return (Container(child: Text("Failed to build list")));
        );
  


myWidget.dart

void initState()
  super.initState();
 _loadMyClassItems();

Widget build(BuildContext context) 
    if (loading == false) 
      return PleaseWaitWidget();
     else 
      return new Scaffold(
          key: _scaffoldKey,
     
            ....

     body: Column(children: [
            Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Expanded(child: _buildList(context)),
     ]

【讨论】:

以上是关于Flutter 如何处理 Stream 和 Future 返回错误的主要内容,如果未能解决你的问题,请参考以下文章

Flutter:如何处理使用 StreamProvider、FireStore 和使用 Drawer

我应该如何处理:Wordpress 社交登录(网络)和 Flutter(移动)

Flutter如何处理框内固定大小的图像?

如何处理 Flutter 谷歌地图控制器

Flutter 如何处理异步/等待中的错误

如果使用Arrays Stream的整数数组的总和超过int max value(2147483647),如何处理?