When I call setValue while my user is offline in my Android app, Firebase provides a snapshot of the change to my listener from the local store,even though the change hasn't been committed to the server. I understand that Firebase will make an effort to sync with the server when connectivity is restored, but if the app restarts that change is lost forever.
I've already got a custom local store for all my data, and I was hoping I could keep track of uncommitted changes myself, clearing a dirty bit when I hear back from the server of a successful setValue. But that doesn't seem possible if the local store is pretending the date is committed.
I don't think I want to use disk persistence (since I already have my own). Is there a way to tell when the update is from the local store vs a real server commit? Or maybe I should use a completion listener to clear the dirty bit?
I understand that Firebase will make an effort to sync with the server when connectivity is restored, but if the app restarts that change is lost forever.
The default behavior is indeed that the pending writes are kept in memory. You can however easily change that by enabling disk persistence.
Firebase.getDefaultConfig().setPersistenceEnabled(true);
But since you indicate you don't want to use that, the alternative is to use a CompletionListener to detect when the changes have been committed to the database on Firebase's servers:
ref.setValue("I'm writing data", new Firebase.CompletionListener() {
#Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
if (firebaseError != null) {
System.out.println("Data could not be saved. " + firebaseError.getMessage());
} else {
System.out.println("Data saved successfully.");
}
}
});
Note that completion listeners are not persisted to disk. So even if you enable dis persistence, the completion callbacks will not fire when pending writes are fulfilled after an app restart. So don't try to mix disk persistence or completion listeners.
Related
So I am attempting to use the Cloud Firestore offline cache ONLY as an API for my instrumentation tests, to avoid having to read and write from the server database during my integration tests.
First, in my test setup method, I call this method
protected fun setFirestoreToOfflineMode() {
Tasks.await(FirebaseFirestore.getInstance().disableNetwork())
}
Then, at the beginning of each relevant test, I use
fun givenHasTrips(vararg trips: Trip) {
GlobalScope.launch(Dispatchers.Default) {
trips.forEach {
firestoreTripApi.put(it)
}
}
}
In that put method, I have the following code:
try {
Tasks.await(tripCollection().document(tripData.id).set(tripData)),
firestoreApiTimeoutSeconds, TimeUnit.SECONDS)
Either.Right(Unit)
} catch (e: Throwable) {
Either.Left(Failure.ServerError)
}
I am calling the set() method and am waiting for a successful result in order to be able to return that the operation was a success, to update my UI afterward.
What happens is the cache DB is written correctly BUT the "set()" function times out because the database is in offline mode. I have read that Firestore only confirms a success if the Server DB was correctly written. If that is the case, I do not know if it is possible to have this call not time-out when operating strictly in offline-cache mode.
Is there a solution to have Firestore act as if the local cache database was the source of truth and return successes if placed in offline mode, just for tests?
The Task returned by the methods that modify the database (set, update, delete) only issues a callback when the data is fully committed to the cloud. There is no way to change this behavior.
What you can do instead is set up a listener to the document(s) that are expected to change, and wait for the listener to trigger. The listener will trigger even while offline.
I came across unique use case where following feature will come in very useful.
In essence I have components that are listening for specific changes in a document, my security rules are set up in a way where reads are allowed, but all writes are disabled as database updates are only possible through cloud function, hence I was researching the docs to find if it is possible to do something like this.
/**
* This update should only happen locally / offline in order to update state of components
* that are listening to changes in this document
*/
myDocumentRef.update({ name: 'newname' });
/**
* Then cloud function is called that performs validation and if correct
* updates document with new data (same as one we set offline above).
* Once that is done listening components will receive new data. If unsuccessful
* we would set document to old data (again offline) and show error.
*/
await myCloudFunction();
Hence my question: is it possible to perform that update (and only update) locally / offline?
This is basically "optimistic update" flow utalising firebase as global store of sorts.
Firestore does not provide a concept of "local only" updates. All writes will be synchronized with the server at the earliest convenience. If a write eventually fails, the promise returned by the API call (if still in memory) will be rejected. If the sync happens after the app restarts, the promise is lost, and you have no way of knowing if the write succeeded. Local writes that eventually fail will be reverted in the local cache.
What you will have to do instead is write your data in such a way that the local listener can tell the stage of the write, possibly by writing metadata into the document to indicate that stage, cooperating with the Cloud Function on keeping that up to date.
We are building a real-time chat app using Firestore. We need to handle a situation when Internet connection is absent. Basic message sending code looks like this
let newMsgRef = database.document(“/users/\(userId)/messages/\(docId)“)
newMsgRef.setData(payload) { err in
if let error = err {
// handle error
} else {
// handle OK
}
}
When device is connected, everything is working OK. When device is not connected, the callback is not called, and we don't get the error status.
When device goes back online, the record appears in the database and callback triggers, however this solution is not acceptable for us, because in the meantime application could have been terminated and then we will never get the callback and be able to set the status of the message as sent.
We thought that disabling offline persistence (which is on by default) would make it trigger the failure callback immediately, but unexpectedly - it does not.
We also tried to add a timeout after which the send operation would be considered failed, but there is no way to cancel message delivery when the device is back online, as Firestore uses its queue, and that causes more confusion because message is delivered on receiver’s side, while I can’t handle that on sender’s side.
If we could decrease the timeout - it could be a good solution - we would quickly get a success/failure state, but Firebase doesn’t provide such a setting.
A built-in offline cache could be another option, I could treat all writes as successful and rely on Firestore sync mechanism, but if the application was terminated during the offline, message is not delivered.
Ultimately we need a consistent feedback mechanism which would trigger a callback, or provide a way to monitor the message in the queue etc. - so we know for sure that the message has or has not been sent, and when that happened.
The completion callbacks for Firestore are only called when the data has been written (or rejected) on the server. There is no callback for when there is no network connection, as this is considered a normal condition for the Firestore SDK.
Your best option is to detect whether there is a network connection in another way, and then update your UI accordingly. Some relevant search results:
Check for internet connection with Swift
How to check for an active Internet connection on iOS or macOS?
Check for internet connection availability in Swift
As an alternatively, you can check use Firestore's built-in metadata to determine whether messages have been delivered. As shown in the documentation on events for local changes:
Retrieved documents have a metadata.hasPendingWrites property that indicates whether the document has local changes that haven't been written to the backend yet. You can use this property to determine the source of events received by your snapshot listener:
db.collection("cities").document("SF")
.addSnapshotListener { documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching document: \(error!)")
return
}
let source = document.metadata.hasPendingWrites ? "Local" : "Server"
print("\(source) data: \(document.data() ?? [:])")
}
With this you can also show the message correctly in the UI
Currently developing a hybrid mobile app using ionic. When the app starts up, and a user writes to the Realtime Database for the first time, it's always delayed by around 10 or more seconds. But any subsequent writes are almost instantaneous (less than 1 second).
My calculation of delay is based on watching the database in the Firebase console.
Is this a known issue, or maybe I am doing something wrong. Please share your views.
EDIT:
The write is happening via Firebase Cloud Function.
This is the call to the Firebase Cloud function
this.http.post(url+"/favouritesAndNotes", obj, this.httpOptions)
.subscribe((data) => {
console.log(data);
},(error)=>{
console.log(error);
});
This is the actual function
app.post('/favouritesAndNotes', (request, response) => {
var db = admin.database().ref("users/" + request.body.uid);
var favourites = request.body.favourites;
var notes = request.body.notes;
if(favourites!==undefined){
db.child("favourites/").set(favourites);
}
if(notes!==undefined){
db.child("notes/").set(notes);
}
console.log("Write successfull");
response.status(200).end();
});
The first time you interact with the Firebase Database in a client instance, the client/SDK has to do quite some things:
If you're using authentication, it needs to check if the token that it has is still valid, and if not refresh it.
It needs to find the server that the database is currently hosted on.
It needs to establish a web socket connection.
Each of these may take multiple round trips, so even if you're a few hundred ms from the servers, it adds up.
Subsequent operations from the same client don't have to perform these steps, so are going to be much faster.
If you want to see what's actually happening, I recommend checking the Network tab of your browser. For the realtime database specifically, I recommend checking the WS/Web Socket panel of the Network tab, where you can see the actual data frames.
Made the mistake of performing a destructive migration on a synchronized realm, which I just now learned I shouldn’t have done according to the docs' statement “However, if the migration makes a destructive change, the Realm will stop syncing with ROS, producing a Bad changeset received error”. The server won't restart our Realm Object Server and the logs say realm-object-server dead but pid file exists. We cannot even access ROS on web at this point.
Is there a way around this without re-installing our realm instance? Also, if the magnitude of this migration is so severe, is there not a way to give a warning to the developer?
Code Sample:
let config = Realm.Configuration(
syncConfiguration: SyncConfiguration(user: curUser, realmURL: RealmURL.userObjects), migrationBlock: { (migration, schema) in
// todo
})
When you perform a schema change, this results in an operation that is appended to the operation log maintained by a Realm. This first occurs on the client copy of a synchronized Realm and is then synced to Realm Object Server. If the operation is a destructive change, the server should simply reject the operation and return an error. The result is that the server's operation log is not affected but the client is now in a state where it can't continue to sync with the server. In this situation, the solution is to reset the client, which is easiest in development by deleting and reinstalling the app.
Your situation, however, sounds like a different problem. The fact that the server is unresponsive implies something else went wrong. You could try removing and reinstalling the server since this does not delete the data or configuration.