Firebase Offline Capabilities and addListenerForSingleValueEvent
Update (2021): There is a new method call (get
on Android and getData
on iOS) that implement the behavior you'll like want: it first tries to get the latest value from the server, and only falls back to the cache when it can't reach the server. The recommendation to use persistent listeners still applies, but at least there's a cleaner option for getting data once even when you have local caching enabled.
How persistence works
The Firebase client keeps a copy of all data you're actively listening to in memory. Once the last listener disconnects, the data is flushed from memory.
If you enable disk persistence in a Firebase Android application with:
Firebase.getDefaultConfig().setPersistenceEnabled(true);
The Firebase client will keep a local copy (on disk) of all data that the app has recently listened to.
What happens when you attach a listener
Say you have the following ValueEventListener
:
ValueEventListener listener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
System.out.println(snapshot.getValue());
}
@Override
public void onCancelled(FirebaseError firebaseError) {
// No-op
}
};
When you add a ValueEventListener
to a location:
ref.addValueEventListener(listener);
// OR
ref.addListenerForSingleValueEvent(listener);
If the value of the location is in the local disk cache, the Firebase client will invoke onDataChange()
immediately for that value from the local cache. If will then also initiate a check with the server, to ask for any updates to the value. It may subsequently invoke onDataChange()
again if there has been a change of the data on the server since it was last added to the cache.
What happens when you use addListenerForSingleValueEvent
When you add a single value event listener to the same location:
ref.addListenerForSingleValueEvent(listener);
The Firebase client will (like in the previous situation) immediately invoke onDataChange()
for the value from the local disk cache. It will not invoke the onDataChange()
any more times, even if the value on the server turns out to be different. Do note that updated data still will be requested and returned on subsequent requests.
This was covered previously in How does Firebase sync work, with shared data?
Solution and workaround
The best solution is to use addValueEventListener()
, instead of a single-value event listener. A regular value listener will get both the immediate local event and the potential update from the server.
A second solution is to use the new get
method (introduced in early 2021), which doesn't have this problematic behavior. Note that this method always tries to first fetch the value from the server, so it will take longer to completely. If your value never changes, it might still be better to use addListenerForSingleValueEvent
(but you probably wouldn't have ended up on this page in that case).
As a workaround you can also call keepSynced(true)
on the locations where you use a single-value event listener. This ensures that the data is updated whenever it changes, which drastically improves the chance that your single-value event listener will see the current value.
Android Firebase keep addListenerForSingleValueEvent updated
If you have enabled offline capabilities, it means that you'll be able to use your app even if it temporarily loses its network connection. So the ValueEventListener
will keep listening on the local stored datas as long as you are offline
. If you want the results to be updated, then you need to go online.
If you want to listen to data only once, then you need to use: addListenerForSingleValueEvent(). If you want to use addValueEventListener
, just don't forget to remove the listener according to the life-cycle of your activity as I have already answered here.
Firebase offline capabilities as cache
Yes, you can easily re-query your Firebase Database in each activity instead of passing data around. If you enable disk persistence, this will be a local read operation. But since you attach a listener (or keep it attached through keepSynced()
), it will cause network traffic.
But don't use Firebase as an offline-only database. It is really designed as an online database that can work for short to intermediate periods of being disconnected. While offline it will keep queue of write operations. As this queue grows, local operations and app startup will slow down. Nothing major, but over time these may add up.
Firebase SingleValueEventListener with SetPersistence
As Frank said in the comments, this isn't possible This post explains why, but has also been updated to include a fix to this issue.
The post explains a new method that allows you to get the most recent data while only relying on cacheing when connection fails.
firebase realtime database reads data that doesnt exist
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
Firebase Offline Capabilities and addListenerForSingleValueEvent
Update (2021): There is a new method call (get
on Android and getData
on iOS) that implement the behavior you'll like want: it first tries to get the latest value from the server, and only falls back to the cache when it can't reach the server. The recommendation to use persistent listeners still applies, but at least there's a cleaner option for getting data once even when you have local caching enabled.
How persistence works
The Firebase client keeps a copy of all data you're actively listening to in memory. Once the last listener disconnects, the data is flushed from memory.
If you enable disk persistence in a Firebase Android application with:
Firebase.getDefaultConfig().setPersistenceEnabled(true);
The Firebase client will keep a local copy (on disk) of all data that the app has recently listened to.
What happens when you attach a listener
Say you have the following ValueEventListener
:
ValueEventListener listener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
System.out.println(snapshot.getValue());
}
@Override
public void onCancelled(FirebaseError firebaseError) {
// No-op
}
};
When you add a ValueEventListener
to a location:
ref.addValueEventListener(listener);
// OR
ref.addListenerForSingleValueEvent(listener);
If the value of the location is in the local disk cache, the Firebase client will invoke onDataChange()
immediately for that value from the local cache. If will then also initiate a check with the server, to ask for any updates to the value. It may subsequently invoke onDataChange()
again if there has been a change of the data on the server since it was last added to the cache.
What happens when you use addListenerForSingleValueEvent
When you add a single value event listener to the same location:
ref.addListenerForSingleValueEvent(listener);
The Firebase client will (like in the previous situation) immediately invoke onDataChange()
for the value from the local disk cache. It will not invoke the onDataChange()
any more times, even if the value on the server turns out to be different. Do note that updated data still will be requested and returned on subsequent requests.
This was covered previously in How does Firebase sync work, with shared data?
Solution and workaround
The best solution is to use addValueEventListener()
, instead of a single-value event listener. A regular value listener will get both the immediate local event and the potential update from the server.
A second solution is to use the new get
method (introduced in early 2021), which doesn't have this problematic behavior. Note that this method always tries to first fetch the value from the server, so it will take longer to completely. If your value never changes, it might still be better to use addListenerForSingleValueEvent
(but you probably wouldn't have ended up on this page in that case).
As a workaround you can also call keepSynced(true)
on the locations where you use a single-value event listener. This ensures that the data is updated whenever it changes, which drastically improves the chance that your single-value event listener will see the current value.
Related Topics
Jdbc VS Web Service For Android
Open Another Application from Your Own (Intent)
How to Change Fontfamily of Textview in Android
How to Hide the Title Bar For an Activity in Xml With Existing Custom Theme
App Not Installed' Error on Android
How to Store Image as Blob in Sqlite & How to Retrieve It
Sending Images Using Http Post
How to Change the Font on the Textview
"Android.View.Windowmanager$Badtokenexception: Unable to Add Window" on Buider.Show()
Can't Find Theme.Appcompat.Light For New Android Actionbar Support
How to Change the Status Bar Color in Android
Best Practice For Storing and Protecting Private API Keys in Applications
Horizontal Listview in Android
Programmatically Register a Broadcast Receiver