Reputation: 518
Im trying to stream my firebase firestore fields. This is my code from my Database. It works in button and I can print what I want. But actually I want to show data with Widgets in my HomePage with 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');
}
});
}
});
}
});
}
When I try this in stream it gives me an error.
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;
});
}
});
}
});
}
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.
And I know it should be Future. Let's try it.
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;
});
}
});
}
});
}
**** error **** lines is this:
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.
And this is my YukModel.dart file;
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');
}
}
What should I do? Also, I am using GetX package for state management.
MY SOLUTION FOR NOW
Here is my solution. It worked for me. Also, you can access your subcollections with this code.
In HomePage, I added a 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");
}
},
),
Upvotes: 1
Views: 1354
Reputation: 4253
The Streambuilder can be listened to by subscribers. The MyClassBloc contains a get method called MyClassListStream. The MyClassBloc contains a datastream of list type. MyClassBlock can be assigned data from an web api provider to the listMyClass variable. in the _loadMyClassItems method a call to initializeStream is called notifying all subscribers there is data on the stream. The _buildList method has a streambuilder which stream points to the MyClassBloc stream controller get MyClassListStream assessing _MyClassListSubject.stream and the initialData references widget.blocMyClass.listMyClass. When data arrives on the stream, the Streambuilder maps data from the stream to create a list of CardWidgets. The snapshot.data is of MyClass Type. Therefore _buildList returns a list of cardwidgets created from the stream. Everything starts when initState() calls the _loadMyClassItems and the web api returns a list of MyClass objects after the promise is completed and assigns the data to the MyClassBloc variable and assigns data and invokes the initializeTheStream. The Streambuilder is notified that data is on the stream from the MyClassBloc _MyClassListSubject.add(listMyClass) event. The Streambuilder then returns a list of 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)),
]
Upvotes: 0
Reputation: 2013
I didn't go through your entire code, because it was too much, but from the error message I can help you understand the issue.
Hers's a simplied version of what you might be trying to achive:
// 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
}
}
)
Now for your issue, as the error message says:
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.
Here's the example to explain that:
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
}
}
)
If you noticed, I don't use the else statement here, so what that means is that:
If snapshot has data then display a widget, but what if the snapshot doesn't hava data yet, then what will be displayed. I will receive the same error as you. Soo to solve that, I use an else
statement. This way my
my body DOESN'T return null
Upvotes: 1