After deleting data from my Firestore Database, it takes my Android app some time to realize that the data was deleted, and I assume that it's happening due the auto data cache. My app has nothing to do with offline usage and I'd like to disable this feature...
I have added this in my custom Application Class:
import android.app.Application;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.FirebaseFirestoreSettings;
public class ApplicationClass extends Application {
#Override
public void onCreate() {
super.onCreate();
FirebaseFirestore db=FirebaseFirestore.getInstance();
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
.setPersistenceEnabled(false)
.build();
db.setFirestoreSettings(settings);
}
}
The problem occurs after turning off the internet connection and than turning it back on (while the app is still running, in the background or not)- the Firestore module seems to lose connection to the server, and it makes the opposite operation than the intended one - instead of stop taking data from the cache, it takes data from the cache only.
For example, debugging this code will always show that isFromCache is true and documentSnapshot is empty (even though that on the server side - it's not empty):
usersRef.document(loggedEmail).collection("challenges_received").get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot documentSnapshots) {
boolean isFromCache=documentSnapshots.getMetadata().isFromCache();
if (!documentSnapshots.isEmpty()) {
}
}
});
Is this normal behavior?
Is there another way to disable the data cache in Cloud Firestore?
EDIT:
Adding: FirebaseFirestore.setLoggingEnabled(flase); (instead of the code above) in the custom Application Class gives the same result.
According to Cloud Firestore 16.0.0 SDK update, there is now a solution to this problem:
You are now able to choose if you would like to fetch your data from the server only, or from the cache only, like this (an example for server only):
DocumentReference documentReference= FirebaseFirestore.getInstance().document("example");
documentReference.get(Source.SERVER).addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
//...
}
});
For cache only, just change the code above to Source.CACHE.
By default, both methods still attempt server and fall back to the cache.
I just ran a few tests in an Android application to see how this works. Because Firestore is currently still in beta release and the product might suffer changes any time, i cannot guarantee that this behaviour will still hold in the future.
db.collection("tests").document("fOpCiqmUjAzjnZimjd5c").get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
DocumentSnapshot documentSnapshot = task.getResult();
System.out.println("isFromCache: " + documentSnapshot.getMetadata().isFromCache());
}
});
Regarding the code, is the same no matter if we're getting the data from the cache or you are connected to the servers.
When I'm online it prints:
isFromCache: false
When I'm offline, it prints:
isFromCache: true
So, for the moment, there is no way to stop the retrieval of the data from the cache while you are not connected to the server, as you cannot force the retrieval of the data from the cache while you're connected to the server.
If instead I use a listener:
db.collection("tests").document("fOpCiqmUjAzjnZimjd5c").addSnapshotListener(new DocumentListenOptions().includeMetadataChanges(), new EventListener<DocumentSnapshot>() {
#Override
public void onEvent(DocumentSnapshot documentSnapshot, FirebaseFirestoreException e) {
System.out.println("listener.isFromCache: " + documentSnapshot.getMetadata().isFromCache());
}
});
I get two prints when I'm online:
listener.isFromCache: true
listener.isFromCache: false
Firestore is desinged to retrieve data from the chache when the device is permanently offline or while your application temporarily loses its network connection and for the moment you cannot change this behaviour.
As a concusion, an API that does something like this, currently doesn't exist yet.
Edit: Unlike in Firebase, where to enable the offline persistence you need use this line of code:
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
In Firestore, for Android and iOS, offline persistence is enabled by default.
Using the above line of code, means that you tell Firebase to create a local (internal) copy of your database so that your app can work even if it temporarily loses its network connection.
In Firestore we find the opposite, to disable persistence, we need to set the PersistenceEnabled option to false. This means that you tell Firestore not to create a local copy of your database on user device, which in term means that you'll not be able to query your database unless your are connected to Firebase servers. So without having a local copy of your database and if beeing disconected, an Exception will be thrown. That's why is a good practice to use the OnFailureListener.
Update (2018-06-13): As also #TalBarda mentioned in his answer this is now possible starting with the 16.0.0 SDK version update. So we can achieve this with the help of the DocumentReference.get(Source source) and Query.get(Source source) methods.
By default, get() attempts to provide up-to-date data when possible by waiting for data from the server, but it may return cached data or fail if you are offline and the server cannot be reached. This behavior can be altered via the Source parameter.
So we can now pass as an argument to the DocumentReference or to the Query the source so we can force the retrieval of data from the server only, chache only or attempt server and fall back to the cache.
So something like this is now possible:
FirebaseFirestore db = FirebaseFirestore.getInstance();
DocumentReference docIdRef = db.collection("tests").document("fOpCiqmUjAzjnZimjd5c");
docIdRef.get(Source.SERVER).addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
//Get data from the documentSnapshot object
}
});
In this case, we force the data to be retrieved from the server only. If you want to force the data to be retrieved from the cache only, you should pass as an argument to the get() method, Source.CACHE. More informations here.
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
.setPersistenceEnabled(false)
.build();
dbEventHome.setFirestoreSettings(settings);
By setting this it is fetching from server always.
In Kotlin:
val db:FirebaseFirestore = Firebase.firestore
val settings = firestoreSettings {
isPersistenceEnabled = false
}
db.firestoreSettings = settings
// Enable Firestore logging
FirebaseFirestore.setLoggingEnabled(flase);
// Firestore
mFirestore = FirebaseFirestore.getInstance();
In general: the Firebase client tries to minimize the number of times it downloads data. But it also tries to minimize the amount of memory/disk space it uses.
The exact behavior depends on many things, such as whether the another listener has remained active on that location and whether you're using disk persistence. If you have two listeners for the same (or overlapping) data, updates will only be downloaded once. But if you remove the last listener for a location, the data for that location is removed from the (memory and/or disk) cache.
Without seeing a complete piece of code, it's hard to tell what will happen in your case.
Alternatively: you can check for yourself by enabling Firebase's logging [Firebase setLoggingEnabled:YES];
try this For FireBase DataBase
mDatabase.getReference().keepSynced(false);
FirebaseDatabase.getInstance().setPersistenceEnabled(false);
In Kotlin;
val settings = FirebaseFirestoreSettings.Builder()
with(settings){
isPersistenceEnabled = false
}
Firebase.firestore.firestoreSettings = settings.build()
Related
I am looking for a solution for my problem, i have a chat app which displays all the users correspondence as a Recyclerview a listener, in-order to improve user experience when a user send a message i manually update the Recyclerview with the current list plus the new message with an "uploading indication" then when the message actually uploads to Firebase the listener overrides the local list i used and updates the last message to "uploaded", problem is if i rebuild the activity the listener automatically update the Recyclerview's list to the one cached therefore the user cannot see his "uploading" message any more until it hits the server.
is was thinking if there is a manually way i can set the listener's cache to the local list without waiting for it to upload? if not maybe there is a better solution i hadn't thought about? (i have many different chats with many different users so i need to be able to fetch every chat channels own unique list)
There is an option in which you can force a query to retrieve data only from the cache. If this is what you need, you can achieve this with the help of the DocumentReference.get(Source source) and Query.get(Source source) methods.
By default, get() attempts to provide up-to-date data when possible by waiting for data from the server, but it may return cached data or fail if you are offline and the server cannot be reached. This behavior can be altered via the Source parameter.
So you can pass as an argument to the DocumentReference or to the Query the source so we can force the retrieval of data from the server only, chache only or attempt server and fall back to the cache.
So something like this will do the trick:
FirebaseFirestore db = FirebaseFirestore.getInstance();
DocumentReference docIdRef = db.collection("tests").document("fOpCiqmUjAzjnZimjd5c");
docIdRef.get(Source.CACHE).addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
//Get data from the documentSnapshot object
}
});
In this case, we force the data to be retrieved from the cache only. If you want to force the data to be retrieved from the server only, you should pass as an argument to the get() method, Source.SERVER. More informations here.
Objective:App should work in Local and Live database or Auto sync when WI-FI enabled
I have 3 text fields:Name, Father name, Mother name
I want to save these text fields data into local database with out WIFI
Step 1: when internet is connected,they should be automatically uploaded to cloud database.
How can i achieve this using android?
You can use firebase realtime database if you want to do it the easy way. This takes care of data being persisted and managed both online and offline.
There are other options too. You can always save database yourself locally, then manually sync with the server both while offline and online. But it is a lot cumbersome to do it than using some service like firebase or etc
You could use WorkManager to meet your goal.
Solution for your problem:
Extend Workerclass and Implement doWork().
public class SyncWorker extends Worker {
//...
public WorkerResult doWork() {
String name = getInputData().getString("name", null);
String fatherName = getInputData().getString("father_name", null);
String motherName = getInputData().getString("mother_name", null);
//Post data to your server.
return WorkerResult.SUCCESS;
}
}
And add this worker to WorkManager like this.
Constraints constraints = new Constraints.Builder().setRequiredNetworkType(NetworkType
.CONNECTED).build();
Data inputData = new Data.Builder()
.putString("name", "XXX")
.putString("father_name", "XXX's Father")
.putString("mother_name", "XXX's Mother")
.build();
OneTimeWorkRequest uploadWork = new OneTimeWorkRequest.Builder(SyncWorker.class)
.setConstraints(constraints).setInputData(inputData).build();
WorkManager.getInstance().enqueue(uploadWork);
WorkManager will take care of persisting your input data and calling SyncWorker::doWork function when internet connection is available.
I noticed this behavior:
Disconnect a device from the internet
Create a few new documents while still offline
Close the app Then
Go online and reopen App
Documents are automatically synced with firestore (I lose control over completeListener)
Problem is that actions I had in onCompleteListener are not run.
Like in this snippet (do some super important stuff) is not run in the described scenario, also OnFailureListener is not called when a user is offline, so I cannot tell if it went ok or not.
FirebaseFirestore.getInstance().document(...).collection(...)
.set(message).addOnCompleteListener {
//do some super important stuff
}
I would rather do sync in this case on my own so I want it to fail and not repeat again.
How can I disable automatic sync of firestore?, for this one case only
So I wanted to ask this question and somehow Firestore transactions got to me.
So to fix this problem I used transactions (firestore / realtime dbs)
Transactions will fail when the client is offline.
So how it works now.
I try to run the transaction.
A If it fails I store it into a database
B If it succeeds I try to remove it from the database
And on every app start, I run sync service (check unsynced dbs and insert missing)
val OBJECT = ...
val ref = FirebaseFirestore.getInstance().document(...).collection(...)
FirebaseFirestore.getInstance().runTransaction(
object : Transaction.Function<Boolean> {
override fun apply(transaction: Transaction): Boolean? {
transaction.set(ref, OBJECT)
transaction.update(ref, PROPERTY, VALUE)
return true
}
})
.addOnSuccessListener {
println("Inserting $OBJECT ended with success==$it")
//todo remove from dbs (unsynced messages)
}.addOnFailureListener {
it.printStackTrace()
//todo save to dbs (unsynced messages)
}
//They work similarly in realtime database
I have been using Firebase Database in my Android app for almost a year now and it works pretty nice. Unfortunately the data stops being synced to the could after some time. It is just never synced/stored to the cloud. Only local. So when user reinstalls the app, it only contains the data which was stored in the cloud. So to the user it looks like the data was removed, but actually is was never stored. I checked and the data is not visible in the firebase-console. Because it happens after a reinstall I guess it has something to do with the syncing. Users report losing data of about 2-3 months.
I'm using the following singleton helper class. Note I use the setPersistenceEnabled(true) and keepSynced(true).
public class FirebaseHelper{
protected FirebaseHelper(Context c) {
this.c = c.getApplicationContext();
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
mAuth = FirebaseAuth.getInstance();
this.userRef = FirebaseDatabase.getInstance().getReference().child(((BuildConfig.DEBUG ? "debug" : "release"))).child("users").child(getUID());
this.userRef.keepSynced(true);
this.path1 = userRef.child("path1");
this.path2 = userRef.child("path2");
this.path3 = userRef.child("path3");
this.path4 = userRef.child("path4");
}
public static FirebaseHelper getInstance(Context c) {
if (instance == null) {
instance = new FirebaseHelper(c);
}
return instance;
}
public String insertObject(MyObject obj) {
DatabaseReference newItem = this.path1.push();
String pushID = newItem.getKey();
obj.id = pushID;
newItem.setValue(obj.getObject());
return pushID;
}
public void updateData(...){}
...other methods
}
What could possibly be the cause of this?
There are only three reasons for this to happen to the best of my knowledge.
1) The method getUID()
Somehow the getUID() method is returning a null or invalid value which leads the data to be stored to somewhere else or it is not getting stored at all.
You are using simply getUid() instead of FirebaseAuth.getInstance().getCurrentUser().getUid(). So it must be a user defined method.
I think your getUid() does something like this.
String getUid() {
try{
return FirebaseAuth.getInstance().getCurrentUser().getUid() ;
} catch (NullPointerException e) {
return null;
}
}
FirebaseAuth.getInstance().getCurrentUser() will return null if cache is cleared. It will lead your data to be lost.
2) Redundant data
Since Marshmallow, data is backed up to the cloud including shared preference. If you are checking shared preference to decide if user is logged in, user will be gone taken inside after reinstalling the app, skipping the login page. But actually he is not logged in which means FirebaseAuth.getInstance().getCurrentUser() returns null and any attempt to access the database will fail (depends on your database rules).
Solution: use FirebaseAuth.getInstance().getCurrentUser()==null to check if user is enabled.
You can alternatively set backup to false. But I prefer first method.
3) An internal bug in Firebase SDK
Unfortunately there is nothing much we can do with it. Try narrowing it down and find a scenario by which the issue can be reproduced and report it to Firebase.
By the way, child(((BuildConfig.DEBUG ? "debug" : "release"))) is really smart. I am going to adopt it.
Well, if your database has been syncing and you have not made any changes to the code whatsoever, it means that this is a firebase error, particularly related to the mobile phone the user is using.
Most developers who use firebase find problems querying the database when certain carriers are used. I have researched into this issue but i have not yet resolved it yet. If you happen to be using mobile data, actions like authenticating a user may not work.
Solution
Use a different internet source to test your code. Try using wifi instead of mobile data while debugging or testing your app.
if I find any helpful work around, I will file it on a firebase project experiencing the problem on open source Lucem
Throwing this out as a guess as well:
At some point you distributed an app where BuildConfig.DEBUG = true, so users that install an updated version "lose" their data. Doesn't explain why other users haven't reported shorter losses though...
The solution would be a data migration, checking which has newer data and then copying the data if DEBUG is newer.
Consider the following example:
I'm writing a value on a test/ node, and I have set up a rule, that will allow the write, only if the "(new value) equals (old value + 1)", that is newData.val() == data.val() + 1
Let's say that the initial value of the test/ node is 0.
If the client goes offline and executes the following commands:
testRef.setValue(1);
testRef.setValue(2);
testRef.setValue(3);
testRef.setValue(4);
testRef.setValue(5);
Then when he goes back online, the value 5 will be written in the database, but I'm not sure I understand why, since 5 != 0 + 1. I guess that this happens due to the caching of the previous values in the local database, but unfortunately that's not the result I am trying to achieve. I want the server to reject that value since it doesn't follow the rules of the database.
Is there a way to achieve that?
Or is there any other workaround so that I can implement the following:
testRef.setValue(1); -> write value to local database -> check if value follows the rules of the online database -> if the value does not follow the rules or if we can't check that because we are offline, then delete the value from the local database
I assume that for your question "offline" means the client has no connection. In my testing, I simulated that by enabling Airplane Mode.
Firebase offline capabilities are described in the user guide. One detail provided there that is important to your question is:
The Firebase Realtime Database client automatically keeps a queue of
all write operations that are performed while your app is offline ...
When the app regains connectivity, all of the operations are sent to
the Firebase Realtime Database server.
You can see this behavior using the code below, which adds a completion listener to the setValue() calls. In my test, I put the device into Airplane Mode (offline), ran the code, and then disabled Airplane Mode to go back online. A log message is generated for each setValue(), confirming that the write operations were queued and sent when a connection was re-established. This explains why the writes satisfy your validation rule: the client does not send one write request with the final value 5, it sends five requests with the original incrementing values.
You can confirm that the rule works by running the test again without first resetting the value of test back to 0. Each write will fail.
This code also demonstrates how the Firebase client handles changes made when the client is offline, and which are later rejected by security rules. While offline, the change is made in the client cache and the onDataChange() callback fires with the new (unvalidated) value. Later, when the client goes online and the change is rejected by the server, onDataChange() fires again with the previous value.
final DatabaseReference ref = FirebaseDatabase.getInstance().getReference("test");
ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Log.d(TAG, "onDataChange: test=" + dataSnapshot.getValue(Integer.class));
}
#Override
public void onCancelled(DatabaseError databaseError) {
throw databaseError.toException();
}
});
final DatabaseReference.CompletionListener completionListener =
new DatabaseReference.CompletionListener() {
#Override
public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
if (databaseError == null) {
Log.d(TAG, "setValue() Success");
} else {
Log.d(TAG, "setValue() Failed " + databaseError.getMessage());
}
}
};
ref.setValue(1, completionListener);
ref.setValue(2, completionListener);
ref.setValue(3, completionListener);
ref.setValue(4, completionListener);
ref.setValue(5, completionListener);