Issue
The Firebase API is designed around a call-back architecture. It is filled with constructs like this Firestore example (taken from here):
db.collection("cities").document("DC")
.delete()
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
Log.d(TAG, "DocumentSnapshot successfully deleted!");
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.w(TAG, "Error deleting document", e);
}
});
or this Firebase auth example (from here):
AuthUI.getInstance()
.signOut(this)
.addOnCompleteListener(new OnCompleteListener<Void>() {
public void onComplete(@NonNull Task<Void> task) {
// ...
}
});
Google's own sample code and apps are full of such callbacks (usually assigned as listeners to Task
objects), always coded directly in an activity or fragment.
For iOS and web programming, such a callback-based API is really nice. It would be nice for Android as well, except that it seems to be contrary to Google's own guidance on dealing with activity (and fragment) lifecycles. Specifically, the calls to API methods like delete()
or signOut()
operate asynchronously. The call-back objects passed as listeners will be retained until the associated task completes and the call-back is invoked. If the activity (or fragment) is destroyed and then recreated (say, by a configuration change) while the task is executing, the callback will be invoked on a stale object. The callback also retains a reference to the destroyed activity, causing a memory leak.
How are we supposed to handle these callbacks? For observing queries, Google suggests using LiveData
objects (as in this blog post). However, for many Firebase tasks (such as the two examples above), using LiveData
doesn't make much sense. Are we just supposed to code all these calls in retained fragments or in services? That doesn't seem very workable. On the other hand, perhaps it's the only reasonable thing to do (considering that Task
objects don't have any way to remove listeners once they've been added).
Solution
When using the Task API, if you have work that should be bound to the lifecycle of an Activity, you should be using the activity-scoped overloads of addOnCompleteListener, addOnSuccessListener, and addOnFailureListener that accept an Activity argument as the first parameter. When you do this, the callbacks will automatically be removed when the activity is stopped, and the callback objects will not leak (unless you've done something else wrong).
For a majority of the task implementations that I'm aware of, there is little to no cost in adding and removing listeners like this, as the SDK will effectively not duplicate work by caching results internally.
If you have work that really must be continually listened to for results without, or you don't trust the SDK to do the right thing when adding a automatically removing listeners, then LiveData from architecture components can help retain the work across lifecycle changes.
Answered By - Doug Stevenson
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.