11

What's the way to go if I want my ViewHolders in an RecyclerView to clean up internal state (in this case unregister from an EventBus and clean up Rx-Subscriptions)? I thought that the methods onViewDetachedFromWindow or onViewRecycledin the adapter is the callback where I can cleanup resources (as described in the API), but this method is never called when I change from the Activity with the RecyclerView to another Activity.

Only onViewAttachedToWindow is called when the activity is entered and i can see my items.

My adapter looks like this:

public class Adapter extends RecyclerView.Adapter<MyViewHolder> 
{
    @Override 
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return factory.getViewholder(parent, viewType, this);
    }

    @Override 
    public void onBindViewHolder(MyViewHolder holder, int position) {
        MyItem item = items.get(position);
    holder.bind(item);
    }

    @Override
    public void onViewDetachedFromWindow(MyViewHolder holder)
    {
        holder.viewDetached();
        super.onViewDetachedFromWindow(holder);
    }

    @Override
    public void onViewAttachedToWindow(MyViewHolder holder)
    {  
        super.onViewAttachedToWindow(holder);
        holder.viewAttached();
    }

    @Override
    public void onViewRecycled(MyViewHolder holder)
    {
        super.onViewRecycled(holder);
        holder.viewRecycled();
    }

    @Override 
    public int getItemCount() {
        return items.size();
    }

    @Override
    public int getItemViewType(int position)
    {
        return items.get(position).getType(this.factory);
    }
}

The Activity holding the RecyclerView:

public class MyActivity extends AppCompatActivity
{

   protected void setupView()
   {
        //called in onCreate

        recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new GridLayoutManager(this, 3));
        this.adapter = new Adapter(new ItemViewHolderFactory());
   }

    @Override
    public void setItems(List<MyItem> items)
    {
        //items are loaded async
        this.adapter.setItems(items);
        this.recyclerView.setAdapter(this.adapter);
        this.recyclerView.scheduleLayoutAnimation();
    }
}

The ViewHolder looks like this

public class MyViewHolder extends RecyclerView.ViewHolder
{
        public void bind(MyItem item)
        {
          //set initial data
        }

       public void viewAttached()
       {
        registerToEventBus();
        loadDataAsync(); // here an rx operation is scheduled and a subscription is hold
       }

     public void viewDetached()
     {
         unregisterFromEventBus();
         cancelAsyncOperationAndCleanSubscription();
     }
}

Thanks for any advices.

EDIT I tried to override onDetachedFromRecyclerView in the adapter, but this method is also not called.

Dokumans
  • 359
  • 1
  • 3
  • 14
  • Doesn't this [Question](http://stackoverflow.com/a/37675317/1889768) answer yours? – Abbas Feb 17 '17 at 12:36
  • Not really, because in my case i have no knowledge about the implementation details of the view holders. The code snippets are not complete, the adapter is a generic adapter and there can be different viewholders plugged in, each viewholder can either use an EventBus or Rx or whatever else. All i want to do is to inform the viewholders to cleanup. – Dokumans Feb 17 '17 at 12:44
  • Provide a base class for your `ViewHolder` with an abstract method `destroy()` and extend all your different `ViewHolder`s from that base class. – Abbas Feb 17 '17 at 12:50
  • @Abbas i am already doing this, but when to call `destroy`?? I also tried to hook into `onDetachedFromRecyclerView`, but his method is also not called. – Dokumans Feb 17 '17 at 12:51
  • 7
    Are you sure you are setting adapter in your `RecyclerView` to null by calling mRecyclerView.setAdapter(null)? That should give you `onDetachedFromRecyclerView()` callback – Abbas Feb 17 '17 at 12:58
  • @Abbas that was the missing part, thank you. I am now calling `recyclerView.setAdapter(null)` in `onPause` of my activity. It has now only the poor sideeffect, that (i tested that on emulator) the items disappear immediately before the next activity is started, so i see an "empty screen" for a short time. – Dokumans Feb 17 '17 at 13:16
  • 1
    An alternative is to call `setLayoutManager(null)`; not sure about the side-effect in your case; my RecyclerView is in a TabHost and it works fine. – ris8_allo_zen0 Sep 07 '17 at 15:39

1 Answers1

15

I run into the same problem:

As already mentioned you can set the adapter null in the onPause (Activity, Fragment) or onDetachFromWindow (Activity, Fragment)

recyclerview.setAdapter(null)

Then you get viewDetachedFromWindow(...) where you can release your internal states and subscriptions. I would setup your subscriptions in on bind, make sure before every bind call you relase old subscriptions as a view can be recycled.

Another possibility is to inflate a custom view instead of only a layout in your factory. Then you can make the cleanup in the custom view onDetachFromWindow(). You get the onDetachedFromWindow also without setting the adapter to null.

eugstman
  • 978
  • 2
  • 15
  • 18