Last active
May 14, 2021 07:56
-
-
Save javmarina/dcb86c7fc8445a2130ea0bf136a1b859 to your computer and use it in GitHub Desktop.
Firebase Realtime Database automatic listener handler (removes ValueEventListener from DatabaseReference when no longer needed)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Make all activities in your app extend BaseActivity | |
public class BaseActivity extends AppCompatActivity { | |
@Nullable | |
private FirebaseListenerHandler firebaseListenerHandler; | |
@Override | |
protected void onCreate(final Bundle savedInstanceState) { | |
firebaseListenerHandler = new FirebaseListenerHandler(this); | |
super.onCreate(savedInstanceState); | |
} | |
@Override | |
public void onDestroy() { | |
super.onDestroy(); | |
firebaseListenerHandler = null; | |
} | |
/** | |
* Add a new value event listener to the specified reference. Will be removed when the activity | |
* leaves its current state. For example, if you add the listener after onStart(), it will be | |
* removed in onStop(). | |
* @param databaseReference reference to Firebase Realtime Database path | |
* @param valueEventListener listener to add | |
*/ | |
final void addValueEventListener(final DatabaseReference databaseReference, | |
final ValueEventListener valueEventListener) { | |
if (firebaseListenerHandler != null) { | |
firebaseListenerHandler.addValueEventListener(databaseReference, valueEventListener); | |
} | |
} | |
/** | |
* Add a new single event listener to the specified reference. It will be removed automatically | |
* when called. If never fired (i.e. device is offline), it will be removed when the activity | |
* leaves its current state. | |
* @param databaseReference reference to Firebase Realtime Database path | |
* @param valueEventListener listener to add | |
* @see BaseActivity#addValueEventListener(DatabaseReference, ValueEventListener) | |
*/ | |
final void addListenerForSingleValueEvent(final DatabaseReference databaseReference, | |
final ValueEventListener valueEventListener) { | |
if (firebaseListenerHandler != null) { | |
firebaseListenerHandler.addListenerForSingleValueEvent(databaseReference, valueEventListener); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Make all fragments in your app extend BaseFragment | |
public class BaseFragment extends Fragment { | |
@Nullable | |
private FirebaseListenerHandler firebaseListenerHandler; | |
@Override | |
public void onCreate(final Bundle savedInstanceState) { | |
firebaseListenerHandler = new FirebaseListenerHandler(this); | |
super.onCreate(savedInstanceState); | |
} | |
@Override | |
public void onDestroy() { | |
super.onDestroy(); | |
firebaseListenerHandler = null; | |
} | |
/** | |
* Add a new value event listener to the specified reference. Will be removed when the fragment | |
* leaves its current state. For example, if you add the listener after onStart(), it will be | |
* removed in onStop(). | |
* @param databaseReference reference to Firebase Realtime Database path | |
* @param valueEventListener listener to add | |
*/ | |
final void addValueEventListener(final DatabaseReference databaseReference, | |
final ValueEventListener valueEventListener) { | |
if (firebaseListenerHandler != null) { | |
firebaseListenerHandler.addValueEventListener(databaseReference, valueEventListener); | |
} | |
} | |
/** | |
* Add a new single event listener to the specified reference. It will be removed automatically | |
* when called. If never fired (i.e. device is offline), it will be removed when the fragment | |
* leaves its current state. | |
* @param databaseReference reference to Firebase Realtime Database path | |
* @param valueEventListener listener to add | |
* @see BaseFragment#addValueEventListener(DatabaseReference, ValueEventListener) | |
*/ | |
final void addListenerForSingleValueEvent(final DatabaseReference databaseReference, | |
final ValueEventListener valueEventListener) { | |
if (firebaseListenerHandler != null) { | |
firebaseListenerHandler.addListenerForSingleValueEvent(databaseReference, valueEventListener); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Class that handles value event listeners attached to Firebase Realtime Database references. | |
* Implements a lifecycle observer that removes listeners no longer used. For example, if a Firebase | |
* listener is added to a reference after onStart() (when the {@link androidx.appcompat.app.AppCompatActivity} | |
* or the {@link androidx.fragment.app.Fragment} is {@link Lifecycle.State#STARTED}), then it is | |
* automatically removed when onStop(). Same happens with onResume() -> onPause() and onCreate() | |
* -> onDestroy(). | |
* <br>The goal of this class is to avoid memory leaks caused by not removing value event listeners | |
* from Firebase DB references. It's valid for both activities and fragments (or any class that | |
* implements {@link LifecycleOwner}. If used, activities and fragments don't need to worry about | |
* lifecycle recreation (i.e. savedInstanceState != null), and can register new listeners with | |
* {@link FirebaseListenerHandler#addValueEventListener(DatabaseReference, ValueEventListener)} and | |
* {@link FirebaseListenerHandler#addListenerForSingleValueEvent(DatabaseReference, ValueEventListener)}. | |
*/ | |
public final class FirebaseListenerHandler { | |
// Value event listeners are removed from DB references when state changes to a lower value | |
// (resumed -> started -> created -> destroyed) | |
// See https://developer.android.com/reference/androidx/lifecycle/Lifecycle?hl=en | |
private final LifecycleOwner lifecycleOwner; | |
private final EnumMap<Lifecycle.State, HashMap<DatabaseReference, ValueEventListener>> listenerRegistry = | |
new EnumMap<>(Lifecycle.State.class); | |
public FirebaseListenerHandler(@Nullable final LifecycleOwner lifecycleOwner) { | |
this.lifecycleOwner = lifecycleOwner; | |
this.lifecycleOwner.getLifecycle().addObserver(new LifecycleEventObserver() { | |
private Lifecycle.State oldState = Lifecycle.State.DESTROYED; | |
@Override | |
public void onStateChanged(@NonNull final LifecycleOwner source, @NonNull final Lifecycle.Event event) { | |
final Lifecycle.State newState = source.getLifecycle().getCurrentState(); | |
FirebaseListenerHandler.this.onStateChanged(oldState, newState); | |
if (event == Lifecycle.Event.ON_DESTROY) { | |
FirebaseListenerHandler.this.lifecycleOwner.getLifecycle().removeObserver(this); | |
} | |
oldState = newState; | |
} | |
}); | |
} | |
/** | |
* Add a new value event listener to the specified reference. Will be removed when the {@link LifecycleOwner} | |
* leaves its current state. For example, if you add the listener after onStart(), it will be | |
* removed in onStop(). | |
* @param databaseReference reference to Firebase Realtime Database path | |
* @param valueEventListener listener to add | |
*/ | |
public final void addValueEventListener(final DatabaseReference databaseReference, | |
final ValueEventListener valueEventListener) { | |
addFirebaseListener(databaseReference, valueEventListener, false); | |
} | |
/** | |
* Add a new single event listener to the specified reference. It will be removed automatically | |
* when called. If never fired (i.e. device is offline), it will be removed when the {@link LifecycleOwner} | |
* leaves its current state. | |
* @param databaseReference reference to Firebase Realtime Database path | |
* @param valueEventListener listener to add | |
* @see FirebaseListenerHandler#addValueEventListener(DatabaseReference, ValueEventListener) | |
*/ | |
public final void addListenerForSingleValueEvent(final DatabaseReference databaseReference, | |
final ValueEventListener valueEventListener) { | |
final Lifecycle.State currentState = lifecycleOwner.getLifecycle().getCurrentState(); | |
addFirebaseListener(databaseReference, new ValueEventListener() { | |
@Override | |
public void onDataChange(@NonNull final DataSnapshot snapshot) { | |
valueEventListener.onDataChange(snapshot); | |
listenerRegistry.get(currentState).remove(databaseReference); | |
} | |
@Override | |
public void onCancelled(@NonNull final DatabaseError error) { | |
valueEventListener.onCancelled(error); | |
} | |
}, true); | |
} | |
private void addFirebaseListener(final DatabaseReference databaseReference, | |
final ValueEventListener valueEventListener, | |
final boolean isSingleValueEvent) { | |
final Lifecycle.State state = lifecycleOwner.getLifecycle().getCurrentState(); | |
if (lifecycleOwner.getLifecycle().getCurrentState() == Lifecycle.State.DESTROYED) { | |
throw new IllegalArgumentException("Can't add listeners when LifecycleOwner is destroyed"); | |
} | |
final HashMap<DatabaseReference, ValueEventListener> currentRegistry; | |
if (listenerRegistry.containsKey(state)) { | |
currentRegistry = listenerRegistry.get(state); | |
} else { | |
// Create new Map for this lifecycle state with room for 5 listeners | |
currentRegistry = new HashMap<>(5); | |
listenerRegistry.put(state, currentRegistry); | |
} | |
if (currentRegistry.containsKey(databaseReference)) { | |
throw new IllegalArgumentException("Database reference " + databaseReference + | |
" already exists in registry " + state); | |
} else { | |
if (isSingleValueEvent) { | |
databaseReference.addListenerForSingleValueEvent(valueEventListener); | |
} else { | |
databaseReference.addValueEventListener(valueEventListener); | |
} | |
currentRegistry.put(databaseReference, valueEventListener); | |
} | |
} | |
private void onStateChanged(final Lifecycle.State oldState, final Lifecycle.State newState) { | |
if (newState.compareTo(oldState) >= 0) { | |
// Listeners only removed when state decreases | |
return; | |
} | |
for (final Lifecycle.State state : Lifecycle.State.values()) { | |
if (state != Lifecycle.State.DESTROYED && state.compareTo(newState) > 0) { | |
final Map<DatabaseReference, ValueEventListener> map = listenerRegistry.get(state); | |
if (map != null) { | |
// Listeners were registered for this state (otherwise it would be null) | |
for (final Map.Entry<DatabaseReference, ValueEventListener> entry : map.entrySet()) { | |
entry.getKey().removeEventListener(entry.getValue()); | |
} | |
map.clear(); | |
} | |
} | |
} | |
} | |
} |
addValueEventListener()
must be called in a fragment or activity. If you have a reference to a fragment or activity, you can also do fragment.addValueEventListener()
, though this is not a good practice as it might cause a memory leak.
With Firebase Realtime Database, you would normally do databaseReference.addValueEventListener(new ValueEventListener(...));
Now, you do addValueEventListener(databaseReference, new ValueEventListener(...));
Same applies to addListenerForSingleValueEvent()
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
how to use function addvaluelistiner ?