Lets suppose:
I have an app installed in two devices, A and B.
This app listen to a person collection changes, as you can see:
FirebaseFirestore.getInstance().collection("people").addSnapshotListener((snapshots, e) -> {
if (e != null || snapshots == null) {
return;
}
for (DocumentChange dc : snapshots.getDocumentChanges()) {
if(dc == null){
continue;
}
switch (dc.getType()) {
case ADDED:
onDocumentAdded(dc.getDocument());
break;
case MODIFIED:
onDocumentModified(dc.getDocument());
break;
case REMOVED:
onDocumentRemoved(dc.getDocument());
break;
}
}
});
When device A adds a new person to the people collection, device B will be notified about it, but device A as well.
In my case, I am implementing Firestore in a existing app and it already have a persistence logic.
In fact, whenever a new person is added by device A, I already have it stored in the app of device A, but I want to save it in device B as well.
However, as device A is notified too and I would save this person twice.
Some solutions I've been thinking:
Storing an unique ID (UUID) on my local database and check if exists (but on Modified event it would not work);
Defining a client ID (UUID) and send it. When I get the notification by the listener, I check if the client ID is the same I have defined locally.
I asking it because I do not know if already exists a way to handle with it.
If device A creates the document, and you have an active listener on the document created (or in this case, it's collection), you should find that snapshots.getMetadata().isFromCache() == true and the document added should also have a similar trait - dc.getDocument().getMetadata().isFromCache() == true.
However, this is not entirely fool-proof, as documents that have Field Transforms such as serverTimestamp() may only fire the listener once they been accepted and resolved by Firestore.
An alternative is to simply add the new person to Firestore (without saving it locally first) and let the snapshot listeners handle persisting the data. As mentioned above, the listener will normally be fired locally while the data is being sent off to your actual Firestore database.
Whenever I needed this, I've kept a list of the document IDs that the local client has written in local storage, and then check the snapshots in the listener against that list.
It's a bit of a brute force approach, but pretty simple to implement. And if you prune the IDs from the local list in the listener once you've gotten the update, the memory overhead is pretty minimal.
Related
I have a fragment in android where I read some data from firestore and than present it in the recycler view. the thing is it still working slow the same when device is go offline. I see in documentation that data persistence is on by default in firestore. here is the code:
firestore.collection(Constants.FireCollections.USERS)
.document(FirebaseAuthRepository().getCurrentUserId())
.collection("categories")
.get().addOnSuccessListener {
for (c in it) {
defaultCategories.add(c.toObject(Category::class.java))
}
Log.i(TAG, "Category: $defaultCategories")
binding.recyclerViewIcons.adapter = CategoryIconAdapter(defaultCategories, this#OutflowTransactionFragment)
}
I need this to work fast at least fast than reading from firestore online mode. and I want the online mode also to fetch the data locally from cached data instead of online for speed so when the device comes online it just update the catched data.
overall the desired result I want the speed.
Thanks for reading this.
The answer is very simple just by redirecting the call from the server to cache will do the magic.
If you are using Firestore in your app then you may notice a tiny delay in when you are getting data from Firestore in offline mode. and this because due to Firebase Firestore checks first in the server the data when it fails to load then again it takes the data from cache.
The problem and the tiny delay is because of two calls. so to overcome the delay we need get data from cache and this very simple by passing Source.CACHE to get() method. The default call is always from server so we need to add cache:
val source = Source.CACHE
firestore.collection(Constants.FireCollections.USERS)
.document(FirebaseAuthRepository().getCurrentUserId())
.collection("categories")
.get(source).addOnSuccessListener {
for (c in it) {
defaultCategories.add(c.toObject(Category::class.java))
}
Log.i(TAG, "Category: $defaultCategories")
binding.recyclerViewIcons.adapter = CategoryIconAdapter(defaultCategories, this#OutflowTransactionFragment)
}
Hope this help, let me know in the comment section if need for more clearification.
I have a node in Firebase Realtime Database which saves a boolean value true or false along with some more attributes. If and only if this key is false, then I allow the user to perform write operations on the same node in database.
When user has did the task, this value is again set back to false. This is like the critical section concept of the operating systems i.e., only one user can perform a task at a time.
It works as I intended, but the issue I am facing is if user has changed this value to true, and but due to bad network or something he/she is not writing on the database. Now, no one can write on that node on database.
I would like to add a time-interval functionality, if user has not performed any write operation for some interval, say 10 minutes and the boolean is true. Then, I would like to set it to false.
I know that Firebase Cloud Function triggers only on database write, update and create operations. Please suggest me something to handle this issue or some other ways by which I can perform it. There is no code snippet regarding this functionality anywhere. I have looked up various resources on internet, brainstormed myself, I could get nothing.
This might not be perfect solution, but it is a workaround.
At the place, where you are allowing data write, probably through a conditional check; you can simply add another condition by using OR operator.
Simply allow data write when (that Boolean value is false) or (boolean value is true but the node's last updation timestamp is more than current timestamp by your time of interval).
It should me something like this :
if(!dataSnapshot.getBoolean || (dataSnapshot.getBoolean && currentTimestamp - dataSnapshot.getTimestamp >= YOUR_TIME_INTERVAL)){
//Your Logic Here
}
I am new to couchbase and I am trying to implement couchbase lite in one of my Android applications. What i am struggling with in particular is the concept of views and the rules for the map function as stated in the docs.
In the database the app stores documents with various doc types. In one query i need to get the entire document by document type ("payments")
and by value of an attribute of the document (doc["approved"] = true)
Hence I would create a view like so:
com.couchbase.lite.View view = database.getView("payments");
if (view.getMap() == null) {
Mapper map = new Mapper() {
#Override
public void map(Map<String, Object> doc, Emitter emitter) {
if (doc.get("type").equals("payments") && doc.get("approved") == true) {
emitter.emit(doc.get("name"), doc);
}
}
};
view.setMap(map, "1");
}
Note that the doc["approved"] value can be updated over time. In one of the rules about map functions in the docs it says:
It must be a "pure" function: ... That means any time it's called with the
same input, it must produce exactly the same output.
Would the implementation of the map function as show above violate that rule?
In the docs it further says :
In particular, avoid these common mistakes: ... Don't make any assumptions
about when the map function is called. That's an implementation detail
of the indexer. (For example, it's not called every time a document
changes.).
Does that mean when the approved status of one of the documents is updated from false to true that the following query not nessesarily contains the updated document? If so what would I need to do to achieve this? I am quite uncertain about what rule that exacly means? Could anyone try to open my eyes please?
What "pure" means is that you cannot use outside state in your map function. All your determinations must be based solely on the parameters that were passed into it. Your map function does not violate this.
I think the missing piece in your understanding is the difference between storage and indexing. You can store revisions of a document to the database, right? That in and of itself will not cause the view's index to be updated. That's what the documentation means by "not called every time a document changes." The index will be updated by default when the next query is run, so the newest state of the document will be output. It could realistically have been changed many times since the last query was run.
I'm building an Android app in which several users can access, modify and delete the same item and I'm using Firebase to sync all the devices.
In order to keep track of the updates the item has a timestamp.
I wrote a transaction so that when I try to delete the item it checks if the timestamp of my copy is older than the remote copy: in that case the transaction aborts and the item is not deleted.
Here my problem:
My device goes offline
I successfully delete the item
Another user modifies the item on the remote database
My device goes online and propagates his deletion
I thought it would have aborted remotely as the remote timestamp is newer.
I really can't see the point of the abort function if I can only abort basing the decision on my local data...
How should I handle these kinds of conflicts in Firebase?
-- UPDATE
This is the code I use to remove an item. It should abort if another user has changed the item remotely after the deletion has happened locally.
private void removeItem(final ListItem item, final Firebase itemRef) {
itemRef.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
if(mutableData == null) return Transaction.abort();
else if((long)mutableData.child("lastUpdate").getValue() > item.getLastUpdate()) return Transaction.abort();
else{
itemRef.removeValue();
return Transaction.success(mutableData);
}
}
});
Please note I use itemRef.removeValue() instead of mutableData.setValue(null) because the second one doesn't seem to work.
Firebase initially applies transactions client-side, to improve concurrency. If that decision does not meet your use-case, you can pass an additional argument to the transaction method that tells it to by-pass the local apply.
See https://www.firebase.com/docs/web/api/firebase/transaction.html
applyLocally Boolean Optional
By default, events are raised each time the transaction update function runs. So if it is run multiple times, you may see intermediate states. You can set this to false to suppress these intermediate states and instead wait until the transaction has completed before events are raised.
I solved my problem in this way:
I used mutableData.setValue(null), otherwise Firebase can't make the transaction work properly
I set the applyLocallyboolean explicitly to true as I need to see local events too
I don't understand why mutableData.setValue(null) wasn't working before, I may be missing some previous mistake, but that was the problem.
When an automatic update occurs, the old date save in the preferences for my app gets delete.
How can i prevent this deletion?
I want that when my app auto updates , it does not delete my old data saved.
I am not sure whether this will be a good solution but just a suggestion you can try.
step 1: whenever you changeg (add/edit/remove) your data store it in permanent storage, you may try any of the following
a. save the data in file in sd card
b. store the data to your remote server or
c. store in internal memory of the phone.
(I am not sure whether it will persists after update at case c, for reference can check here
Step 2: creae a BroadcastReceiver that listens to the ACTION_PACKAGE_REPLACED Intent. So you know when your application package is updated. NOw read the data again from the storage where you saved the data ( either 1a/1b/1c)
Caution: It is not a good thing to save user data without his concern.
Android save your SharedPreference under /data/data/your package name/shared_prefs
Generally, update application won't delete your sharedPreference.
private void replaceNonSystemPackageLI(PackageParser.Package deletedPackage,
PackageParser.Package pkg, int parseFlags, int scanMode, UserHandle user,
String installerPackageName, PackageInstalledInfo res) {
...
// First delete the existing package while retaining the data directory
if (!deletePackageLI(pkgName, null, true, PackageManager.DELETE_KEEP_DATA,
res.removedInfo, true)) {
// If the existing package wasn't successfully deleted
res.returnCode = PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
deletedPkg = false;
} else {
....
}
....
}
I think you need to check the following:
Is your device a rooted device? Apps on a rooted device can delete anything they want.
Did you use a different package name in that updated apk?