Flutter StreamBuilder returns null from Firestore - firebase

The idea is to display a string from a random document within a collection in Firebase. A simple function getRandom() retrieves the total number of documents and generates a random integer r that is fed into the Firebase instance.
The output in the app is always null.
StreamBuilder(
initialData: Words(),
stream: getWords(),
builder: (context, snapshot){
if(!snapshot.hasData){
return Center(child: Text("NO DATA"));
}else {
var r = snapshot.data;
return Center(child: Text("${r.english}"));
}
})
Stream<Words> getWords() async* {
int r = await getRandom();
print("RANDOM NO: " + "$r");
Firestore.instance.document("vocabs/foods/words/$r")
.get()
.then((snapshot){
try {
return Words().english;
} catch(e){
print("ERROR");
return null;
}
});
}
class Words{
Words(): super();
String english;
Words.fromSnapshot(DocumentSnapshot snapshot)
: english = snapshot.data["english"];
}

I've constructed a this piece of sample code for you to give you some options to achieve what you'd like to do:
import 'dart:async';
class Word {
final String english;
const Word(this.english);
}
Future<Iterable<Word>> get firebaseSnapshot async => [ Word('aWord'), Word('bWord'), Word('cWord') ];
Stream<String> getEnglishWords() async* {
yield* await firebaseSnapshot.then((words) => Stream.fromIterable(words.map((w) => w.english)));
}
Stream<String> getEnglishWords2() async* {
final words = await firebaseSnapshot.then((words) => words.map((w) => w.english));
yield* Stream.fromIterable(words);
}
Stream<String> getEnglishWords3() async* {
final snapshot = await firebaseSnapshot;
for(final word in snapshot) {
yield word.english;
}
}
main() async {
await for(final englishWord in getEnglishWords()) {
print(englishWord);
}
await for(final englishWord in getEnglishWords2()) {
print(englishWord);
}
await for(final englishWord in getEnglishWords3()) {
print(englishWord);
}
}
Option No. 2 is the one I'd use. There is some significant performance consideration around it. I am scraping the back of my mind for the lecture around it... Nope, can't recall... If I find it, I'll update ya.

Related

Firebase RealTime database Flutter stream never returns problem

I am trying to read a list of objects from Realtime database in Firebase with no success so far.
I am following the official tutorial from Firecasts on the following link:
https://www.youtube.com/watch?v=sXBJZD0fBa4
Here is how the database looks like:
Here is the code I have written:
The following is the class I have created for the objects I will be reading from database:
class TestData {
final String name;
final String surname;
final String age;
TestData({
required this.name,
required this.surname,
required this.age,
});
factory TestData.fromRTDB(Map<dynamic, dynamic> data) {
return TestData(
name: data["Name"],
surname: data["Surname"],
age: data["Age"],
);
}
}
Here is how I try to read it from the database:
class TestDataGetter {
final _db = FirebaseDatabase.instance.ref();
Stream<List<TestData>> getTestDataStream() {
final csPostprocessedStream = _db.child("test_data/").onValue;
var streamToPublish = csPostprocessedStream.map((event) {
final testDataMap = Map<dynamic, dynamic>.from(
event.snapshot.value as Map<dynamic, dynamic>);
final testDataList = testDataMap.entries.map((element) {
return TestData.fromRTDB(Map<dynamic, dynamic>.from(element.value));
}).toList();
return testDataList;
});
return streamToPublish;
}
}
And here is the screen where I would like to show the data:
class TestDataScreen extends StatelessWidget {
const TestDataScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
StreamBuilder(
stream: TestDataGetter().getTestDataStream(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
print("Waiting");
return const CircularProgressIndicator(color: Colors.white);
} else if (snapshot.hasError) {
print("Error occured...");
return const CircularProgressIndicator(color: Colors.white);
} else {
final testDataList = snapshot.data as List<TestData>;
return Text(
testDataList[0].name + " / " + testDataList[1].name,
style: Theme.of(context).textTheme.headline3,
textAlign: TextAlign.center);
}
}),
],
),
);
}
}
But I can never see the data on the screen. The spinning wheel is the only thing I see and on the console I see the print out as "Waiting" (as I print out this text in the code above).
It gets stuck in if (!snapshot.hasData).
I am clueless after spending hours on this.
Try the following
!snapshot.hasData || snapshot.data.documents.isEmpty
The problem here is that snapshots() will also return a QuerySnapshot when the query returns no documents. Thus, you could expand your condition like this:
Here is what I have should help you
if (snapshot.hasError) {
//TODO: we need to make sure we caputer this error message snapshot.error
return const PlaceHolder(
url: 'assets/animations/404-error.json',
message: ErrorMessages.getDogsError,
);
} else if (snapshot.data == null || snapshot.data!.isEmpty) {
return const PlaceHolder(
url: 'assets/animations/no-dog-animation.json',
message: AppMessages.noDogMessage,
);
} else if (!snapshot.hasData) {
}
Your problem is that you most likely have an error.
The issue is, your error handling is actually never reached, because in case of an error, !hasData will still be true
You may want to execute the hasError condition before !hasData:
if (snapshot.hasError) {
return Text('error');
} else if (!snapshot.hasData) {
return Text('loading');
} else {
return Text('data');
}

Flutter How to handle Stream and Future return error

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

Flutter StreamBuilder for multiple firebase documents

I am trying to make my Flutter app update when a change is made to the usersCollection.document(user.uid) firebase document.
When the user document is updated I want to retrieve the data from this document but also from another firebase document, facilitiesCollection.document(...).
My current code
Future<Map> _getCheckedInFacilityData() async {
Map<String, dynamic> result = {};
try {
DocumentSnapshot userDoc =
await _db.usersCollection.document(user.uid).get();
if (userDoc.data['checkedIn']) {
// User is checked in
DocumentSnapshot facDoc = await _db.facilitiesCollection
.document(userDoc.data['activeFacilityID'].toString())
.get();
result['facilityID'] = userDoc.data['activeFacilityID'];
result['sessionID'] = userDoc.data['activeSessionID'];
result['facilityActiveUsers'] = facDoc.data['activeUsers'].length;
result['facilityName'] = facDoc.data['name'];
return result;
}
} catch (er) {
debugPrint(er.toString());
}
return null;
}
FutureBuilder<Map>(
future: _getCheckedInFacilityData(),
builder: (context, map) {
switch (map.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
...
This is currently working but the page is not updated when a change is made to the user document.
I haven't been using Flutter/Dart for long so any ideas are welcome.
Is it possible to return a custom object/map which is comprised of 2 separate documents from a StreamBuilder, or is there another method that will work in my situation.
Surely you can do it with Streams asyncMap() and then listen in StreamBuilder
Basic algoritm
Get stream of you first data type and then asyncMap to wait second data type and return them both
stream.asyncMap(
(v1) async {
final v2 = await Future.delayed(Duration(seconds: 1), () => 4);
return v1 * v2;
},
);
Closer to your code
Stream<Map<String, dynamic>> _getCheckedInFacilityData() {
return _db.usersCollection.document(user.uid).snapshots()
.asyncMap(
(userDoc) async {
final DocumentSnapshot facDoc =
await _db.facilitiesCollection
.document(userDoc.data['activeFacilityID'].toString())
.get();
final Map<String, dynamic> userMap = userDoc.data;
final Map<String, dynamic> facMap = facDoc.data;
return userMap..addAll(facMap);
},
);
}
In this function I merge two maps - be carefull if both maps have identical keys map will keep only last was added key in our case from addAll(facMap)
Last step is to show you streamed data on screen - use StreamBuilder
StreamBuilder<Map>(
stream: _getCheckedInFacilityData(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('${snapshot.error}');
} else if (snapshot.connectionState == ConnectionState.waiting) {
return LinearProgressIndicator();
}
return /* some widget that shows your data*/;
},
),

flutter: check if the document exist

I want to check whether the document exist or not without creating the document if it does not exits
Checked() {
Future<DocumentSnapshot> check = linkref.
document(user.UID).
collection("Requests").
document(uuid).get();
return FutureBuilder(
future: check,
builder: (context, ccheck) {
if (check != null ) {
return Text("Available");
}
return Text("not available);
});
}
i tried this code but even if the document does not exists it says that it exists
You should use; if (ccheck.data.exists) instead of if (check != null ). Here is the code;
Checked() {
Future<DocumentSnapshot> check =
linkref.document(user.UID).collection("Requests").document(uuid).get();
return FutureBuilder<DocumentSnapshot>(
future: check,
builder: (context, ccheck) {
if (ccheck.data.exists) {
return Text("Available");
}
return Text("not available");
});
}
You can use the .where( field, isEqualTo: query). This might be useful to you.
final userRef = FirebaseFirestore.instance.collection('users');
checkExists(String query) async {
QuerySnapshot checker = await userRef
.where('uid', isEqualTo: query)
.get();
chkr.docs.forEach((doc) {
if (doc.exists) {
print('Exists');
print(doc.get('uid'));
}else {
print('false');
}
});
}
Then, if you are using a button, you can use onPressed: () => check(yourQuery).

Async await flutter firestore

I would like to ask whats going on with my code.
Assuming the 'Counter' field is 179 in this instance, how do I make my outside myData update before printing?
class Test {
Firestore _firestore = Firestore.instance;
var myData;
void getData() async {
DocumentSnapshot snapshot =
await _firestore.collection('Counter').document('Counter').get();
myData = await snapshot.data['Counter'];
print('inside $myData');
}
void checkMyData() {
myData = 5;
getData();
print('outside $myData');
}
}
Console:
flutter: outside 5
flutter: inside 179
You have to make getData() return a Future like this:
Future getData() async {
So you can do this:
getData().then((value) {
print('value: $value');
}).catchError((error) {
print('error: $error');
});
But you probably want to use a FutureBuilder to show the information when arrives, like this:
FutureBuilder(
future: getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('value: ${snapshot.data}');
} else if (snapshot.hasError){
return Text('error: ${snapshot.error}');
}
return Text('loading...');
},
)

Resources