Skip to content

Instantly share code, notes, and snippets.

@javmarina
Last active May 14, 2021 07:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save javmarina/dcb86c7fc8445a2130ea0bf136a1b859 to your computer and use it in GitHub Desktop.
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)
// 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);
}
}
}
// 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);
}
}
}
/**
* 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();
}
}
}
}
}
@hongbenshun
Copy link

how to use function addvaluelistiner ?

@javmarina
Copy link
Author

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