Flutter Future:A build function returned null - firebase

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));
}

Related

Flutter Firestore: FirestoreBuilder with initial data

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.

How to get Metadata for Asynchronous Snapshot in Flutter

So I am pulling data from Cloud Firestore but a bit stuck on checking to see whether the data is being retrieved via the cache or server. So to do this here is how I am pulling the data from cloud firestore
marketplacedata() async {
try {
var snapshot = await FirebaseFirestore.instance
.collection('marketplaces')
.doc('All')
.collection('offers')
.get();
I'm pulling the data from the init
class _SearchMarketplaceState extends State<SearchMarketplace> {
void initState() {
widget.futuredata = getData();
super.initState();
}
getData() async {
return await FireStoreData().marketplacedata('All');
}
Then I am using future builder to retrieve the data as such
FutureBuilder(
future: widget.futuredata,
builder: (BuildContext context, AsyncSnapshot snapshot) {
var marketplacedata = snapshot.data;
if (snapshot.hasError) {
return Text('something went wrong');
}
**if (snapshot.hasData) {
HOW DO I CHECK WHETHER THE DATA IS COMING FROM CACHE?);
.metadata doesnt work on AsyncSnapShot
}**
if (searchController.text.isNotEmpty) {
marketplacedata = searchFilter(
searchController.text.toLowerCase(), marketplacedata);
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Loading();
} else {
return GridView.builder(
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: (4 / 4),
),
itemCount: marketplacedata.length ?? 1,
itemBuilder: (BuildContext context, int index) {
return buildMarketplace(context, index,
marketplaceID: marketplacedata[index].marketplaceID,
heading: marketplacedata[index].heading,
companyDesc: marketplacedata[index].companyDesc,
iconURL: marketplacedata[index].iconURL,
keywords: marketplacedata[index].keywords,
preferred: marketplacedata[index].preferred,
imgURL: marketplacedata[index].imgURL);
},
);
}
},
),
Any help is appreciated. In the end I am trying to minimize the number of reads I am getting and hoping to get most of the data read from the cache. However I cant seem to access any data. Thanks in advance.
You can use the data property of the snapshot in your FutureBuilder to access the metadata property:
snapshot.data?.metadata.isFromCache
However, since your overall goal is to reduce backend calls, your get() call to Firestore will always fetch from the server first. See the default value of source in the GetOptions argument to get():
Source.serverAndCache (default value), causes Firestore to try to retrieve an up-to-date (server-retrieved) snapshot, but fall back to returning cached data if the server can't be reached.
You could conditionally use your Future to fetch from the Firestore cache, and subsequently from the server if there is no cached data:
Future<DocumentSnapshot> getData(Source dataSource) {
return FirebaseFirestore.instance
.collection("users")
.doc("testUser1")
.get(GetOptions(source: dataSource)); //calls using GetOptions with Source parameter
}
//using a nested FutureBuilder
return FutureBuilder<DocumentSnapshot>(
future: getData(Source.cache), //first restricts to local cache
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> cacheSnap) {
if (cacheSnap.hasError ||
!cacheSnap.hasData ||
!cacheSnap.data!.exists) {
return FutureBuilder(
future: getData(Source.server), //nested Future uses the server
builder: (BuildContext context,
AsyncSnapshot<DocumentSnapshot> serverSnap) {
if (serverSnap.connectionState == ConnectionState.done) {
return Text(
"Server Future: ${serverSnap.data?.metadata.isFromCache}");
}
return Text("Loading");
});
}
if (cacheSnap.connectionState == ConnectionState.done) {
return Text("Cache Future: ${cacheSnap.data?.metadata.isFromCache}");
}
return Text("loading");
},
);
If you want to listen to real time updates, then the initial state can come from the cache:
If there is a state available in a local cache, the query snapshot will be initially populated with the cached data, then updated with the server's data when the client has caught up with the server's state.
In Flutter, you would use a StreamBuilder instead of a FutureBuilder if you want to go this route. I confirmed this behavior in my project.

StreamBuilder Flutter Firebase

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.

Get user name from a document in Firestore flutter

I need to display the user name on the profile page. I have tried multiple things but every time it shows an error.
This is what I have done so far:
Widget userProfile(BuildContext context) {
Stream<DocumentSnapshot> provideDocumentUserName() {
return Firestore.instance.collection('user').document('name').snapshots();
}
return Container(
child: StreamBuilder<DocumentSnapshot>(
stream: provideDocumentUserName(),
builder: (BuildContext context,
AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return CircularProgressIndicator();
} else {
Map<String, dynamic> documentFields = snapshot.data.data;
return Text(documentFields["name"]);
}
}));
The error I'm getting is:
The getter 'data' was called on null. Receiver: null Tried calling:
data
Also:
Another exception was thrown: NoSuchMethodError: The method '[]' was called on null.
I have been working on this for hours.
It looks like snapshot.data is null and that's why you might be seeing this error. you might want to check the connection state. one way to tackle is as follows:
switch (snapshot.connectionState) {
case ConnectionState.done:
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
Map<String, dynamic> documentFields = snapshot.data;
return Text(documentFields["name"]);
}
return CircularProgressIndicator(); // unreachable
Courtesy to #cipli-onat post here
You should get documentFields as follow:
Map<String, dynamic> documentFields = snapshot.data.data();

Firebase in flutter getCurrentUser never unwraps the Future and I can't get at the Data

So I have a Flutter app with Firebase authentication. I have a dart file for sign in and registration and then a separate dart file with different Widgets for a list of data. I want to get the current user so I can get that users personalized data from the database. But for some reason I can never get that user. Here is the code:
Future getUser() async {
final FirebaseUser user = await FirebaseAuth.instance.currentUser();
return user.getIdToken();
}
return FutureBuilder(
future: getUser(),
builder: (context, snapshot) {
if (snapshot.hasData) return Text(snapshot.data);
else if (snapshot.hasError) return Text("data error (see futurebuilder)");
return Text("Await for data");
},
);
But on my debugging device the Text output just says data error (see future builder) which is the error message I wrote. However I can't figure out what I'm doing wrong. I don't see why this isn't just giving me the user ID token. Thanks.
Try changing your code from snapshot.hasData to snapshot.connectionState == ConnectionState.done and see if this helps you. I have the exact same setup in my app and this works for me.
something like this should work
FutureBuilder(
future: getUser(),
builder: (context, snapshot) {
if(snapshot.connectionState == ConnectionState.done){
return
//Execute code after future is returned
} else {
return CircularProgressIndicator();
}
},
);

Resources