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);
Related
we came across what might be a major bug in the firebase database, pls see code below. the code below tries to set a value to a child "EXAMPLE" which doesnt have a read or write permission. the write operation doesn't write anything to the database and throws an error " setValue at /EXAMPLE/VALUE failed: DatabaseError: Permission denied" in the log, which is a good thing.
however a major issue is with the code that comes after which tries to read the value of child "EXAMPLE", the code actually goes into the ondatachange method and reads the value as "ONE" instead of going into the onCancelled method to throw a permission error, the data doesnt even exist in the database and there is no read or write permission for the child "EXAMPLE" so how can fireabase claim to read a value that is not even there.
myReftwo.child("EXAMPLE").child("VALUE").setValue("ONE");
myReftwo.child("EXAMPLE").child("VALUE").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot)
{
Log.d("print", dataSnapshot.getValue().toString() );
}
#Override
public void onCancelled(DatabaseError databaseError)
{
Log.d("print", databaseError.getMessage() );
}
});
In the snippet as you share it, most likely the listener is getting the value from the local cache before that cache has been updated because of the rejection from the server.
When you add a listener, Firebase tries to give you the value it expects the node to have immediately. And since you call addListenerForSingleValueEvent, it them immediately stops listening for the value. So you end up seeing only stale value from the local cache, and never see the actual value (or lack thereof) from the server.
For this reason you should not use both disk persistence and addListenerForSingleValueEvent in your app. Using addValueEventListener in the same scenario would lead to two calls to onDataChange: the first one with the value from the local cache, and the second one with the correct snapshot from the server.
For a longer answer on how these work, and why they don't result in the behavior you'd like, see: Firebase Offline Capabilities and addListenerForSingleValueEvent
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()
I'm keeping track of a count that users update on the Firebase database through an Android app. The way it works right now is that upon interaction the user's app looks up the current count on the database (using a addListenerForSingleValueEvent() and onDataChange() method defined within the new ValueEventListener) and adds one to it and then sets the count to this new value using mRef.setValue() where mRef is the reference to the database.
The issue I'm worried about is what would happen if a large number of users interacted with the database together at the same time; does Firebase take care of making sure that the value is read and incremented properly or is there a lot of overlap and potentially a loss of data because of that.
When working with complex data that could be corrupted by concurrent modifications, such as incremental counters, Firebase provides a transaction operation.
You give this operation two arguments: an update function and an optional completion callback. The update function takes the current state of the data as an argument and will return the new desired state you would like to write.
For example, if we wanted to increment the number of upvotes on a specific blog post, we would write a transaction like the following (Legacy code):
Firebase upvotesRef = new Firebase("https://docs-examples.firebaseio.com/android/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes");
upvotesRef.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData currentData) {
if(currentData.getValue() == null) {
currentData.setValue(1);
} else {
currentData.setValue((Long) currentData.getValue() + 1);
}
return Transaction.success(currentData); //we can also abort by calling Transaction.abort()
}
#Override
public void onComplete(FirebaseError firebaseError, boolean committed, DataSnapshot currentData) {
//This method will be called once with the results of the transaction.
}
});
Legacy source
New firebase version source
Firebase database handles up to 100 simultaneous real time connections to your database if your are using their free plan but once the 101st users connects to your database the database would stop responding and would display the values that were last edited. Firebase is really good at handling real time connections simultaneously so it depends on your pricing plans. If you want to use the database for free, there will be no issues handling 100 connections but if you want to handle more users use their generous pricing plans.
I'm wondering on Android, how the underlying actual mechanism work when you add an listener to the database. Is it just more frequent pulling or something else special?
Update:
To make it clearer, I understand what a listener is, but I meant how does the 'listening' scheme work, how a client (Android) knows the data on the server changed. Is it just a periodical pulling? (and Firebase engineers already do the hard work to cover that and make it easy for us).
Looks like firebase is not open-source.
// Attach an listener to read the data at our posts reference
ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
System.out.println(snapshot.getValue());
}
#Override
public void onCancelled(FirebaseError firebaseError) {
System.out.println("The read failed: " + firebaseError.getMessage());
}
});
disclaimer: this is a simplified description of how things work at the time of writing. Things may have changed by the time your read it.
When your app connects to the Firebase Database, it opens a web socket connection from the device to a Firebase server. This connection stays open for the lifetime of your app or until you call goOffline().
When you attach a listener, the client sends the location (and potential query parameters) to the server. The server adds that listener to a list of all listeners of all connected clients. It then also sends back the initial data for that listeners.
Whenever a write operation is committed to the database, the server scans the listeners. For each relevant listener, the server sends an update to the client over the open web socket.
It happens Asynchronously
Adding listeners to a node reference will fetch any changes made to the node reference asynchronously
void onDataChange(DataSnapshot snapshot)
This method will be called with a snapshot of the data at this location. It will also be called each time that data changes.
void onCancelled(FirebaseError error)
This method will be triggered in the event that this listener either failed at the server, or is removed as a result of the security and Firebase rules. For more information on securing your data, see: Security Quickstart
Example
ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
System.out.println(snapshot.getValue());
Users users = snapshot.getValue(Users.class) //This is your POJO class
String name = users.getName(); //Other getter methods to fetch data from firbase
}
#Override
public void onCancelled(FirebaseError firebaseError) {
System.out.println("The read failed: " + firebaseError.getMessage());
}
});
It's explained in the guide Firebase - Retrieve Data on Android
This method is triggered once when the listener is attached and again every time the data, including children, changes.
I'm using Firebase for Android for the chat component of our app.
I'm having trouble figuring out how to reliably implement status updates on each chat message.
For example, showing "Sending.." when the chat is being synced with the server, and having a success feedback after sync.
I have a onChildAdded listener that supplies the messages to my adapter. However, this listener is fired immediately when each node is added locally, and I can't check the status of each node
My Current solution is to keep a set of node keys, and add keys whenever I push something to Firebase. Then on the setValue callback, I remove the node key from the set. However, this is very unreliable since the nodes can be synced when the calling activity has been destroyed, etc.
I am wondering if there is a simpler way to check if each node has been synced to the server?
Thanks!
From the Firebase documentation on writing data:
If you'd like to know when your data has been committed, you can add a completion listener. Both setValue() and updateChildren() take an optional completion listener that is called when the write has been committed to the database.
With this handy code sample:
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.");
}
}
});