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