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.

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



Leave a reply



Submit