I am trying to implement a stream builder to display a String from a firebase document.
However I get the following error "The body might complete normally, causing 'null' to be returned, but the return type is a potentially non-nullable type." on the 3rd line of the code. Not sure what I am doing wrong. Any ideas?
StreamBuilder<QuerySnapshot>(
stream: _firestore.collection('data').snapshots(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const ErrorWidget();
}
if (snapshot.hasData) {
final messages = snapshot.data!.docs;
List<Text> messageWidget = [];
for (var message in messages) {
final messageText = message.get('email');
final messageWidget = Text('$messageText');
}
return Column(
children: messageWidget,
);
}
},
)
Add another return after and outside your second if statement. Because currently your streambuilder only returns on two conditions, either error or hasData. But what about everything else? For example waiting?
You can add return CircularProgressindicator() at the end, and it should work.
Related
I'm making my first Flutter app and I encounter a problem and doesn't found any solution for it.
I have a view where I render a Firestore document, and there is two ways of getting there:
From a list where I already loaded my documents
From Dynamic Links with uid attached as arguments (args)
So in order to listen document changes and loading the data when arriving from the link I used FirestoreBuilder like this:
return FirestoreBuilder<EventDocumentSnapshot>(
ref: eventsRef.doc(args.uid),
builder: (context, AsyncSnapshot<EventDocumentSnapshot> snapshot, Widget? child) {
if (!snapshot.hasData) {
return Container();
}
Event? event = snapshot.requireData.data;
return Scafold(); //Rest of my rendering code
}
);
How I could avoid first call to Firebase when I already have the data but still listen to changes? The main problem is that my hero animation doesn't work because of this.
I tried with a StreamBuilder and initialDataparam but since it's expecting stream I didn't know how to cast my data.
Okay, so I found the solution myself after many tries, so I added my Model object that can be null as initialData, but the thing that makes me struggle with is how you get the data in the builder. You have to call different methods depending on where the data is coming from.
return StreamBuilder(
initialData: args.event
ref: eventsRef.doc(args.uid),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
// Here is the trick, when data is coming from initialData you only
// need to call requireData to get your Model
Event event = snapshot.requireData is EventDocumentSnapshot ? snapshot.requireData.data : snapshot.requireData;
return Scafold(); //Rest of my rendering code
}
);
Reading through cloud_firestore's documentation you can see that a Stream from a Query can be obtained via snapshots()
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('books').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return new Text('Loading...');
return new ListView(
children: snapshot.data.documents.map((DocumentSnapshot document) {
return new ListTile(
title: new Text(document['title']),
subtitle: new Text(document['author']),
);
}).toList(),
);
},
);
This won't help you, but with GetX it's simple to implement like this: You don't need StreamBuilder anymore.
//GetXcontroller
class pageController extends GetXcontroller {
...
RxList<EventModel> events = RxList<EventModel>([]);
Stream<List<EventModel>> eventStream(User? firebaseUser) =>
FirebaseFirestore.instance
.collection('events')
.snapshots()
.map((query) =>
query.docs.map((item) => UserModel.fromMap(item)).toList());
#override
void onReady() async {
super.onReady();
events.bindStream(
eventStream(controller.firebaseUser)); // subscribe any change of events collection
}
#override
onClose() {
super.onClose();
events.close(); //close rxObject to stop stream
}
...
}
You can use document snapshots on StreamBuilder.stream. You might want to abstract the call to firebase and map it to an entity you defined.
MyEntity fromSnapshot(DocumentSnapshot<Map<String, dynamic>> snap) {
final data = snap.data()!;
return MyEntity (
id: snap.id,
name: data['name'],
);
}
Stream<MyEntity> streamEntity(String uid) {
return firebaseCollection
.doc(uid)
.snapshots()
.map((snapshot) => fromSnapshot(snapshot));
}
return StreamBuilder<MyEntity>(
// you can use firebaseCollection.doc(uid).snapshots() directly
stream: streamEntity(args.uid),
builder: (context, snapshot) {
if (snapshot.hasData) {
// do something with snapshot.data
return Scaffold(...);
} else {
// e.g. return progress indicator if there is no data
return Center(child: CircularProgressIndicator());
}
},
);
For more complex data models you might want to look at simple state management or patterns such as BLoC.
I encountered this problem while working with Flutter & Firebase (firestore), I did my research and couldn`t find anything helpful.
I want to turn my streams I get from firestore into a stream of widgets StreamBuilder().
Here is Firestore object initialization final FirebaseFirestore _firestore = FirebaseFirestore.instance;
Here is the method where I try to get my data from firestore as a stream
void messagesStream() async {
await for (var snapshot in _firestore.collection('message').snapshots()) {
for (var message in snapshot.docs) {
print(message.data());
}
}
}
Here is StreamBuilder widget insdie my widget tree =>
StreamBuilder<QuerySnapshot>(
stream: _firestore.collection('message').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
final messages = snapshot.data!;
List<Text> messageWidgets = [];
for (var message in messages) // <== I get My error here
*// The type 'QuerySnapshot<Object?>' used in the 'for' loop must
// implement Iterable.*
{
final messageText = message.data['text'];
final messageSender = message.data['sender'];
final messageWidget =
Text('$messageText from #$messageSender');
messageWidgets.add(messageWidget);
}
return Column(
children: messageWidgets,
);
},
),
If I removed the null check operator from final messages = snapshot.data;
I get a different error which is
A nullable expression can't be used as an iterator in a for-in loop.
Try checking that the value isn't 'null' before using it as an iterator.
One way to easily deal with nullable iterables in a for loop is to use ?? to provide a non-nullable fallback value. For example:
List<int>? nullableList;
for (var element in nullableList ?? <int>[]) {
...
}
Alternatively you can use the conditional-member-access operator with forEach:
List<int>? nullableList;
nullableList?.forEach((element) {
...
});
although generally I recommend normal for loops over forEach.
I think you may check if "snapshot.data" is not null before anything else.
// This basically means that if you have data, you show the widget CircularProgressIndicator()
if (snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
You may try like this :
// Means that if the snapshot hasn't data show the widget
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
I tried some things but I cant read the data. I'm a beginner in flutter and I have to do a project that it shows the data in firebase. Can you show a beginner project that it only shows the datas in direbase?
I tried this too but this code have some errors on await (how it's possible I copied this from flutter official site)
import 'package:firebase_database/firebase_database.dart';
DatabaseReference ref = FirebaseDatabase.instance.ref("users/123");
// Get the data once
DatabaseEvent event = await ref.once();
// Print the data of the snapshot
print(event.snapshot.value);
Thanks for replys I checked the await keyword and correct them (I think)
But I dont know how to print this
Can you show me that too
And I edited code to this =>
Future<Object?> GetData() async {
FirebaseDatabase database = FirebaseDatabase.instance;
DatabaseReference ref = FirebaseDatabase.instance.ref("litre");
// Get the data once
DatabaseEvent event = await ref.once();
// Print the data of the snapshot
print(event.snapshot.value); // { "name": "John" }
return event.snapshot.value;
}
Wherever you're calling the GetData method you need to await it...or if it is a widget which needs to display data after fetching it from firebase you can wrap it with a future builder.
I hope this solves your problem, if it still doesn't, post with the specific error you're getting.
FutureBuilder(
future: GetData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else {
if (snapshot.error != null) {
return Center(
child: Text('An error occured'),
);
} else {
return Text(snapshot.data); // Or whatever widget you want to return or display
}
}
},
),
I am new to firebase/firestore and wanted to add firestore to my app. My app currently has a login and adds data to the database with the UID set as the document name. Console Image
I want to display the name in my apps profile page. How would I achieve this?
Called it with this
Center(child:building(context),),
Widget building(BuildContext context) {
return new StreamBuilder(
stream: Firestore.instance
.collection('UserData')
.document(getUID())
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return new Text("Loading");
} else {
return new Text(snapshot.data.toString());
}
});
}
Current Error
Error Image
Previous Error
Error Message
Thanks in advance!
Try this
Widget building(BuildContext context) {
return new StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance
.collection('UserData')
.document('TjMJDFd940UtLORgdYND771GYwG2')
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return new Text("Loading");
} else {
Map<String, dynamic> documentFields = snapshot.data.data;
return Text(documentFields["First Name"] +
" " +
documentFields["Last Name"]);
}
});
}
Note that TjMJDFd940UtLORgdYND771GYwG2 refer to documentID.
The docs for the Flutter Firebase API are stashed away and are admittedly hard to find. Here's the documentation on the QuerySnapshot class.
You are trying to look at the .document property of a QuerySnapshot object, so it's throwing an error because that property does not exist. Try using snapshot.documents to get a list of documents to iterate over (or just use snapshot.documents[0] if there will always only be one), and then read the data from that, i.e.: snapshot.documents[0].data[documentId]['First Name']. I removed quotes from documentId reference, since we want to index the variable value and not just the string "documentId".
I want to retrieve data from firebase to future builder.I use this function for that.
Future getAllData() async {
ReseviorDataModel resModel;
DatabaseReference ref = FirebaseDatabase.instance.reference();
DataSnapshot childed =await
ref.child("Reservoir/${widget.placeName}").once();
Map<dynamic, dynamic> values;
values = childed.value;
resModel = ReseviorDataModel.customConstrcutor(values["sensor1"],
values["sensor2"], values["sensor3"]);
return resModel;
}
I invoke this function inside my future builder.
child: FutureBuilder(
future: getAllData(),
builder: (context, snapshot) {
Center(child: Text(snapshot.data));
}
but it keeps throwing this "A build function returned null." error .i cant figure out what is the problem here
To clear things up here, your builder always has to return a widget to display. You can never return null, as the error message describes.
The problem in this case was that the OP didn't return anything from the builder, so just adding a return worked out fine.
Some things to keep in mind when using FutureBuilder. Always check the snapshot.hasData property and return a UI accordingly. This will prevent cases where the snapshot.data is null causing a new error to be thrown in your Widget taking in the null.
Example:
child: FutureBuilder(
future: getAllData(),
builder: (context, snapshot) {
if(!snapshot.hasData) {
// show loading while waiting for real data
return CircularProgressIndicator();
}
return Center(child: Text(snapshot.data));
}