3
votes

I am not referring to firestore offline persistence, but a way to permanently cache documents that will survive closing the database connection and app. I want to cache entire documents.

For example, in a simple chat app. Say there are 100 messages in the conversation, and the user has already read them all. A new message gets sent, so the user opens the app to read the new message. To re-download all 100 messages from firestore will have you charged for 100 document reads. But since the user has already read and retrieved those, I want them cached locally and not read again from the database (since a chat message will never change once created). I understand pagination could help, but I'd rather not read the same static document more than once.

Is SQFlite the best option for this cross-platform, or is there something even better?

2
"a way to permanently cache documents that will survive closing the database connection and app" That's precisely what Firestore's offline persistence (which is enabled by default) does. If you want to prevent getting charged, I would ask about that in the question title and not about caching.Frank van Puffelen
"Is SQFlite the best option for this cross-platform, or is there something even better?" That's a request to recommend a certain technology, which is off-topic on Stack Overflow. We have no preference on whether you use SQLite directly or use Room, or anything else that works for you.Frank van Puffelen
I had thought I read in the docs or a video somewhere that the offline persistence is terminated after the app closes or after a period of inactivity, so that when the app is reopened all of those documents would be read again (and billed)?user11015833
The offline cache remains populated upon app restarts. But its purpose is primarily to allow the app to function while the user has no network connection, not to reduce billing. As said: I highly recommend removing that focus from your question, as it complicates answering right now.Frank van Puffelen
Also: if you read that somewhere, please provide a link to the source. If that wasn't clear enough, I'd like to get it updated.Frank van Puffelen

2 Answers

1
votes

For a chat application I'd typically keep track of what the last message is that the user has already seen. If you show the messages ordered by their timestamp, that means you'll need to keep the timestamp of the latest message they've seen, and its document ID (just in case there are multiple documents with the same timestamp). With those two pieces of information, you can request only new documents from Firestore with collection.startAfter(...).

0
votes

I recommend to save the last time the user made login in the device locally, an then use it to only get the messages them didn't receive. Here is an oversimplified example:

import 'package:cloud_firestore/cloud_firestore.dart';

/// This class represents your method to acess local data,
/// substitute this class for your method to get messages saved in the device
/// I highly recommend sembast (https://pub.dev/packages/sembast)
class LocalStorage {
  static Map<String, Message> get savedMessages => {};

  static DateTime get lastLogin => DateTime.now();
  static void saveAllMessages(Map<String, Message> messages) {}
  static void saveLastLogin(DateTime lastLogin) {}
}

class Message {
  String text;
  String senderId;
  DateTime timestamp;
  Message.fromMap(Map<String, dynamic> map) {
    text = map['text'] ?? 'Error: message has no text';
    senderId = map['senderId'];
    timestamp = map['timestamp'];
  }
  Map<String, dynamic> toMap() {
    return {
      'text': text,
      'senderId': senderId,
      'timestamp': timestamp,
    };
  }
}

class User {
  DateTime lastLogin;
  String uid;
  Map<String, Message> messages;

  void updateMessages() {
    this.messages = LocalStorage.savedMessages;
    this.lastLogin = LocalStorage.lastLogin;

    /// Listening to changes in the firestore collection
    final firestore = Firestore.instance;
    final ref = firestore.collection('users/$uid');
    final query = ref.where('timestamp', isGreaterThan: this.lastLogin);
    query.snapshots().listen((querySnapshot) {
      /// Updating messages in the user data
      querySnapshot.documents.forEach((doc) {
        messages[doc.documentID] = Message.fromMap(doc.data);
      });

      /// Updating user last login
      this.lastLogin = DateTime.now();

      /// Saving changes
      LocalStorage.saveAllMessages(this.messages);
      LocalStorage.saveLastLogin(this.lastLogin);
    });
  }
}