How to get Metadata for Asynchronous Snapshot in Flutter - firebase

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.

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 show Flutter firebase realtime database on widgets with data updates using Stream builder?

I am using Firebase for first time. My app is connected to firebase successfully and i am able access data on screen by following code. My problem is that while Values changes in database it should be changed on the screen without any button press or something.
stream: FirebaseDatabase.instance.ref("app3-e0228-default-rtdb").onvalue,
while i use above line the data on the screen will not accessible. what should I do?
StreamBuilder(
stream: FirebaseDatabase.instance.ref("app3-e0228-default-rtdb").once().asStream(),
builder: (context,AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
DatabaseEvent databaseEvent = snapshot.data!; // 👈 Get the DatabaseEvent from the AsyncSnapshot
var databaseSnapshot = databaseEvent.snapshot; // 👈 Get the DataSnapshot from the DatabaseEvent
print('Snapshot: ${databaseSnapshot.value}');
return Text("${databaseSnapshot.value}");
} else {
return CircularProgressIndicator();
}
}),
DataBase
The first problem I spot is here:
stream: FirebaseDatabase.instance
.ref("app3-e0228-default-rtdb")
.once()
.asStream(),
You're calling once(), which means you read the value from the database once (hence its name), and then turn that into a stream. So your stream will just have one element.
If you want to listen to the database and receive updates too, use onValue:
stream: FirebaseDatabase.instance
.ref("app3-e0228-default-rtdb")
.onValue
Inside your builder you'll then have a DataSnapshot that contains the data from your database, and you can simply do:
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
var databaseEvent = snapshot.data!; // 👈 Get the DatabaseEvent from the AsyncSnapshot
var databaseSnapshot = databaseEvent.snapshot; // 👈 Get the DataSnapshot from the DatabaseEvent
print('Snapshot: ${databaseSnapshot.value}');
return LiveMatch();
} else {
return CircularProgressIndicator();
}
Have you implemented streamSubscription and tried listening to it?
StreamBuilder will automatically update data whenever there are changes made.

flutter firebase get string from database using future

I want to get a string from my DB in Firebase, I'm very confused and I don't know how to do that!
I made a big search in the few past days about this idea but unf I don't get any useful result
what do I want? I want to make a Method that returns the 'Question' string.
DB:Collection / History/question
thank you for your time
the incorrect code :
Future loadData() async {
await Firebase.initializeApp();
if (snapshot.hasError) {
return Scaffold(
body: Center(
child: Text("Error: ${snapshot.error}"),
),
);
}
// Collection Data ready to display
if (snapshot.connectionState == ConnectionState.done) {
// Display the data inside a list view
return snapshot.data.docs.map(
(document) {
return method(
document.data()['question'].toString().toString(),
); //Center(
},
);
}
}
Here is the official documentation from Flutter Fire - https://firebase.flutter.dev/docs/firestore/usage/
Read data from Cloud firestore
Cloud Firestore gives you the ability to read the value of a collection or a document. This can be a one-time read or provided by real-time updates when the data within a query changes.
One-time Read
To read a collection or document once, call the Query.get or DocumentReference.get methods. In the below example a FutureBuilder is used to help manage the state of the request:
class GetUserName extends StatelessWidget {
final String documentId;
GetUserName(this.documentId);
#override
Widget build(BuildContext context) {
CollectionReference users = FirebaseFirestore.instance.collection('users');
return FutureBuilder<DocumentSnapshot>(
future: users.doc(documentId).get(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.hasData && !snapshot.data.exists) {
return Text("Document does not exist");
}
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data = snapshot.data.data();
return Text("Full Name: ${data['full_name']} ${data['last_name']}");
}
return Text("loading");
},
);
}
}
To learn more about reading data whilst offline, view the Access Data Offline documentation.
Realtime changes
FlutterFire provides support for dealing with real-time changes to collections and documents. A new event is provided on the initial request, and any subsequent changes to collection/document whenever a change occurs (modification, deleted, or added).
Both the CollectionReference & DocumentReference provide a snapshots() method which returns a Stream:
Stream collectionStream = FirebaseFirestore.instance.collection('users').snapshots();
Stream documentStream = FirebaseFirestore.instance.collection('users').doc('ABC123').snapshots();
Once returned, you can subscribe to updates via the listen() method. The below example uses a StreamBuilder which helps automatically manage the streams state and disposal of the stream when it's no longer used within your app:
class UserInformation extends StatefulWidget {
#override
_UserInformationState createState() => _UserInformationState();
}
class _UserInformationState extends State<UserInformation> {
final Stream<QuerySnapshot> _usersStream = FirebaseFirestore.instance.collection('users').snapshots();
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _usersStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return new ListView(
children: snapshot.data.docs.map((DocumentSnapshot document) {
return new ListTile(
title: new Text(document.data()['full_name']),
subtitle: new Text(document.data()['company']),
);
}).toList(),
);
},
);
}
}
By default, listeners do not update if there is a change that only affects the metadata. If you want to receive events when the document or query metadata changes, you can pass includeMetadataChanges to the snapshots method:
FirebaseFirestore.instance
.collection('users')
.snapshots(includeMetadataChanges: true)

Flutter :Future builder doesn't gets the data

I wrote this code which queries Firebase database and returns some data. This thing was working totally fine till few days back but i don't know why now it returns "no data" even if i have data in the database
Is there a error in the code or Something else cause this ?
Fetching the data from Firebase
getsearchfood(String query) {
Future<QuerySnapshot> search =
Firestore.instance.collection("data").getDocuments();
setState(() {
searched = search;
});
}
Operating the data
buildsearchfood() {
return FutureBuilder(
future: searched,
builder: (context, snapshot) {
if (snapshot.hasData) {
print("no data");
}
else{
print("present data");
}
return Container(
);
},
);
}
Add await, as it is an asynchronous operation
Future<QuerySnapshot> search =
await Firestore.instance.collection("data").getDocuments();
You don't need to setState like you did.
Try this:
buildsearchfood() {
return FutureBuilder(
future: Firestore.instance.collection("data").getDocuments(),
builder: (context, snapshot) {
if (snapshot.hasData) {
// This means you have data. You can reference it in `snapshot.data`
}
else{
// This means NO data
}
},
);
}

Flutter Future:A build function returned null

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

Resources