(Deprecated) Why you should call setAdapter(null)

03 June 2017

Notice: If you've come here by chance, I want to say thanks for the interest.

And also want to have notice: This post is wrongly representing my message. It (I actually) also uses a bad example to raise the Problem, which cause a misleading discussion.

At the moment, I'm revising my message as well as updating this post. By the meantime, this post is no longer a good place for reference, this is my apologize.

TL;DR: Always call RecyclerView#setAdapter(null) before your RecyclerView is going away (in onDestroy/onDestroyView/...).

1. Problem

In common tutorials about RecyclerView out there, you may see this quite frequently, inside an Activity's onCreate or a Fragment's onCreateView.

mAdapter = new MyCoolAdapter(); // here mAdapter is a member of enclosing class.

It is pretty common step when one initialize his/her screen's RecyclerView. But I rarely see people telling those "learners" what should them do on the "terminal" point. I mean, does anyone care about tearing down the RecyclerView after all?

One may ask: what's wrong with that? Is Activity destroying everything after it is destroyed?

Well, for your curiosity: Will it be destroyed?

Let's take a quick look at this question on StackOverflow: RecyclerView doesn't unregister itself from the adapter on orientation change. I bet everyone can easily find the problem as well as the answer for it. But let me talk a bit more.

Check points to 'The Problem':

  • If you have used RecyclerView quite a while, you may know there are couples of useful callback for Adapter: Adapter#onAttachedToRecyclerView(RecyclerView), Adapter#onDetachedFromRecyclerView(RecyclerView), Adapter#onViewAttachedToWindow(ViewHolder), Adapter#onViewDetachedFromWindow(ViewHolder).
  • Have you ever debuged to see when they are called? Let me sort them out for you:

    • onAttachedToRecyclerView is called when the Adapter is set to RecyclerView, after a call to RecyclerView#setAdapter(Adapter) or RecyclerView#swapAdapter(Adapter, boolean). This is quite obvious.
    • onDetachedFromRecyclerView, on the other hand, is called when current Adapter if going to be replaced by another Adapter (this another 'Adapter' can be Null). What is the point here: if you don't replace the Adapter, this method will never be called. And what happens if an Adapter is never be "detached" from a RecyclerView? Let's see after I explain about the other couples.
    • onViewAttachedToWindow is called once RecyclerView or its LayoutManager add a View into RecyclerView (hint: go to RecyclerView source code and search for the following keywords: dispatchChildAttached).
    • onViewDetachedFromWindow, on opposite, is called when RecyclerView or its LayoutManager detach a View from current Window.
  • What happens when an Adapter is not detached from a RecyclerView? The couple of attach/detach call to an Adapter is tight to another "couple method" of Adapter: registerAdapterDataObserver/unregisterAdapterDataObserver. In fact, registerAdapterDataObserver is always called with (right before) onAttachedToRecyclerView and unregisterAdapterDataObserver is always called with (right before) onDetachedFromRecyclerView. In depth, Adapter#registerAdapterDataObserver(AdapterDataObserver) will add an instance of AdapterDataObserver to its observer system, to broadcast the changes to RecycleView's ecosystem. And unregisterAdapterDataObserver will clean it up. RecyclerView holds an instance of RecyclerViewDataObserver which is a non-static inner class that extends AdapterDataObserver (hint: go searching for mObserver in RecyclerView source code). So here you see the problem?

→ If onDetachedFromRecyclerView is not called, which is equivalent to the fact that unregisterAdapterDataObserver will not be called, an instance of RecyclerViewDataObserver will stay alive inside your Adapter as long as that Adapter is alive. RecyclerViewDataObserver, in turn, holds a reference to its enclosing RecyclerView, which in turn holds a reference to its enclosing Context. In this case, it is your Activity or Fragment's host Context (which is 99% an Activity). So your Activity will be held there for quite a long time, longer than you may expect.

2. The Solution

As we have discussed quite long about the Problem, let's see how we can solve it. Since the solution is quite obvious, I would like you to not leave here right now and maybe continue reading to the next paragraph: more motivations for you to always use this solution.

So the solution is:

  • In short: remove your Adapter from the RecyclerView as soon as your RecyclerView is going to leave. Which is equivalent to calling: recyclerView.setAdapter(null).
  • Why this solve the problem? - by calling setAdapter with whatever value the Adapter is (as long as it is different to current Adapter), your RecyclerView will always detach current Adapter, which means that Adapter#onDetachedFromRecyclerView and Adapter#unregisterAdapterDataObserver will properly be called. Using a null Adapter will stop the works there. That is!

3. One more thing.

You may say well, it is not bad, but I'm not convinced yet..., let's keep going a bit further. There is another couple I have discussed above: Adapter#onViewAttachedToWindow and Adapter#onViewDetachedFromWindow. Let see 2 (plus 1) screenshots below to see what is the difference between calling setAdapter to null and not calling it.

Note: my experiment is to setup an Activity with a RecyclerView, and by pressing "Current App Stack" button I can easily make the Activity destroy/recreated. I debug on RecyclerView#onChildAttachedToWindow and RecyclerView#onChildDetachedFromWindow which is called along with those 2 methods above, respectively.

  • Screenshot 0: after clicking to "App Stack" button, your Activity is destroyed, and by clicking to its "Snapshot" in the stack, you bring it back to life with a "recreation" (savedInstanceState is not null).

"Screenshot 0: Activity is re-created"

  • Screenshot 1: not calling setAdapter to null in onDestroy

"Screenshot 1: not calling setAdapter to null in onDestroy"

  • Screenshot 2: calling setAdapter to null in onDestroy

"Screenshot 2: calling setAdapter to null in onDestroy"

You see the difference?

  1. With proper call to remove the Adapter, current Views on RecyclerView are also properly detached from Window. Otherwise, your Views will not be detached, and I cannot tell where they will be after your Activity is destroyed (or maybe it will not...).
  2. If you ignore the fact, let see further: the attached Views after your Activity is recreated are all different (see their hashCode in my Screenshot), which means that those older Views is going somewhere else, without a proper "detaching" from the old Window. Where are they? I have no idea.
  3. Well, I maybe a bit OCD, but not seeing any "onChildDetachedFromWindow" while there is a whole bunch of "onChildAttachedToWindow" make me feel bad. Hope it is not only me out there.

That's all. Happy Coding!