Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cache Firebase data in Flutter?

In my app I build a list of objects using data from Firebase. Inside a StreamBuilder, I check if the snapshot has data. If it doesen't, I am returning a simple Text widget with "Loading...". My problem is that if I go to another page in the app, and then come back, you can see for a split second that it says 'Loading...' in the middle of the screen, and it is a bit irritating. I am pretty sure it is downloading the data from Firebase, and building the widget every time I come back to that page. And if I don't do the check for data, it gives me a data that I am trying to access data from null.

Is there a way to cache the data that was already downloaded, and if there has been no change in the data from Firebase, then just use the cached data?

Heres a redacted version of my code:

class Schedule extends StatefulWidget implements AppPage {
  final Color color = Colors.green;
  @override
  _ScheduleState createState() => _ScheduleState();
}

class _ScheduleState extends State<Schedule> {
  List<Event> events;
  List<Event> dayEvents;
  int currentDay;
  Widget itemBuilder(BuildContext context, int index) {
    // Some Code
  }
  @override
  Widget build(BuildContext context) {
    return Center(
      child: StreamBuilder(
        stream: Firestore.instance.collection('events').snapshots(),
        builder: (context, snapshot) {
          if (!snapshot.hasData) {
            return Text("Loading...");
          }
          events = new List(snapshot.data.documents.length);
          for (int i = 0; i < snapshot.data.documents.length; i++) {
            DocumentSnapshot doc = snapshot.data.documents.elementAt(i);

            events[i] = Event(
              name: doc["name"],
              start: DateTime(
                doc["startTime"].year,
                doc["startTime"].month,
                doc["startTime"].day,
                doc["startTime"].hour,
                doc["startTime"].minute,
              ),
              end: DateTime(
                doc["endTime"].year,
                doc["endTime"].month,
                doc["endTime"].day,
                doc["endTime"].hour,
                doc["endTime"].minute,
              ),
              buildingDoc: doc["location"],
              type: doc["type"],
            );
          }
          events.sort((a, b) => a.start.compareTo(b.start));
          dayEvents = events.where((Event e) {
            return e.start.day == currentDay;
          }).toList();
          return ListView.builder(
            itemBuilder: itemBuilder,
            itemCount: dayEvents.length,
          );
        },
      ),
    );
  }
}
like image 551
Marco Avatar asked Aug 07 '19 01:08

Marco


People also ask

Does Firebase cache data?

Firebase Hosting uses a powerful global CDN to make your site as fast as possible. Any requested static content is automatically cached on the CDN. If you redeploy your site's content, Firebase Hosting automatically clears all your cached static content across the CDN until the next request.

How does firestore cache work?

Cloud Firestore supports offline data persistence. This feature caches a copy of the Cloud Firestore data that your app is actively using, so your app can access the data when the device is offline. You can write, read, listen to, and query the cached data.


1 Answers

You can use the the following code to define the source you want to retrieve data from. This will search either in local cache or on the server, not both. It works for all get() parameters, no matter if it is a search or document retrieval.

import 'package:cloud_firestore/cloud_firestore.dart';

FirebaseFirestore.instance.collection("collection").doc("doc").get(GetOptions(source: Source.cache))

To check if the search has data in cache, you need to first run the search against cache and if there is no result, run it against the server. I found project firestore_collection to use a neat extension that can greatly simplify this process.

import 'package:cloud_firestore/cloud_firestore.dart';

// https://github.com/furkansarihan/firestore_collection/blob/master/lib/firestore_document.dart
extension FirestoreDocumentExtension on DocumentReference {
  Future<DocumentSnapshot> getSavy() async {
    try {
      DocumentSnapshot ds = await this.get(GetOptions(source: Source.cache));
      if (ds == null) return this.get(GetOptions(source: Source.server));
      return ds;
    } catch (_) {
      return this.get(GetOptions(source: Source.server));
    }
  }
}

// https://github.com/furkansarihan/firestore_collection/blob/master/lib/firestore_query.dart
extension FirestoreQueryExtension on Query {
  Future<QuerySnapshot> getSavy() async {
    try {
      QuerySnapshot qs = await this.get(GetOptions(source: Source.cache));
      if (qs.docs.isEmpty) return this.get(GetOptions(source: Source.server));
      return qs;
    } catch (_) {
      return this.get(GetOptions(source: Source.server));
    }
  }

If you add this code, you can simply change the .get() command for both documents and queries to .getSavy() and it will automatically try the cache first and only contact the server if no data can be locally found.

FirebaseFirestore.instance.collection("collection").doc("doc").getSavy();
like image 92
James Cameron Avatar answered Oct 02 '22 03:10

James Cameron