How to get Metadata for Asynchronous Snapshot in Flutter

Viewed 400

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.

1 Answers

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