First of all - background:
In my app among others I have two very similar activities. Both of them are lists of the same cards shown through RecyclerView. Difference is in different onClick/onLongClick listeners and missing context action mode for one of them.
I have implemented them by making two childs with one parent class accommodating all common code and two abstract methods for setting onClick and onLongClick listeners.
So, both activities use the same data and resources, main difference is in possible action set.
Problem:
Child A is started. from A other activity is called where I have possibility to execute child B. Everything works as expected at this point:
A -> X -> B
Issue appears when I navigate back from child B to child A using back button:
A <- X <- B
In debugger I see some cards in RecyclerView belong to activity B which is already destroyed at this moment. Since both activities show the same data - UI looks fine, but logical consequence of this is onClick callback of activity B being called from activity A, which is not what I want.
Code:
Parent class constructor and callbacks:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
dbClass_ = new DBClass(this);
rv_ = (RecyclerView) findViewById(R.id.entity_list);
rv_.setLayoutManager(new LinearLayoutManager(this));
ListedEntityAdapter entityAdapter = new ListedEntityAdapter(this, dbClass_, this, this);
rv_.setAdapter(entityAdapter);
}
@Override
abstract public void onClick(View view);
@Override
abstract public boolean onLongClick(View view);
Recyclerview in xml:
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:id="@+id/entity_list"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:showIn="@layout/activity_main">
OnClick listeners of class A:
@Override
public void onClick(View view) {
if ( contextHandler_.isActive() )
{
contextHandler_.setSelected( view );
} else {
TextView tv = (TextView)view.findViewById( R.id.listed_entity_name );
ShowDetailsActivity( (Long) tv.getTag( R.string.item_id_tag ), (Integer)tv.getTag( R.string.item_pos_tag ));
}
}
@Override
public boolean onLongClick(View view) {
startActionMode(contextHandler_);
contextHandler_.setSelected( view );
return true;
}
ShowDetailsActivity has possibility to call activity B:
@Override
public void onClick(View view) {
int id = view.getId();
switch ( id )
{
[...]
case R.id.item_path:
Intent itemList = new Intent( this, B.class );
startActivityForResult( itemList, B.CONTAINER_CHOICE );
break;
[...]
}
And finally class B has own callbacks, which do nothing at this phase:
@Override
public void onClick(View view) {
return;
}
@Override
public boolean onLongClick(View view) {
return true;
}
Both A and B has no constructor defined (they use one of parent class). Could someone explain me what cause this behaviour?
It appeared answer on my question was in code which is not posted here. In tutorials available in web ViewHolder frequently is declared as static, and I've done the same. And this was my mistake.
A and B has different instances of the same adapter which has reference to onClick callback from the caller. Since this references is static - callback is common for all instances:
class ListedEntityAdapter extends
RecyclerView.Adapter<ListedEntityAdapter.ViewHolder> {
private static View.OnLongClickListener onLongCardClick;
private static View.OnClickListener onCardClick;
[...]
public ListedEntityAdapter(Context ctx, DBClass db, View.OnClickListener onClick, View.OnLongClickListener onLongClick ) {
onCardClick = onClick;
onLongCardClick = onLongClick;
[...]
}
public static class ViewHolder extends RecyclerView.ViewHolder {
[...]
private ViewHolder(View itemView) {
super(itemView);
itemView.setOnLongClickListener( onLongCardClick );
itemView.setOnClickListener( onCardClick );
[...]
}
}
}
In result here what is happened:
1) A created and started, adapter initialized with reference to callback of A.
2) B activity created and started later, adapter initialized with reference to callback of B.
3) We return to A. Adapter is not recreated since onCreate of A is not called, it functions well with one remark: static onClick callbacks are not changed after B created. That's why different instance of adapter use the same callback.
Pretty dumb mistake which was fixed by removing 'static' from ViewHolder and callback members in adapter.