abegosum abegosum - 4 years ago 213
Android Question

What is the best practice for updating a RecyclerView within View.OnClick?

Not certain the best way to tackle this. I have a

RecyclerView
that represents a list of records from a database (I'm using SugarOrm; but, that's inconsequential to the question).

I'd like to represent data changes and allow the user to do CRUD functionality via
onClick
and
onLongClick
events. For example, if a user long-presses on a view in the
RecyclerView
, I'd like them to have the option to delete the record. The problem is that an update is easy to reflect in the view using only the
ViewHolder
; but, deleting a record is not so easy. The
ViewHolder
, as a static inner class, doesn't have access to the
RecyclerView
itself to to modify the adapter data or notify that the data changed.

One option is that I could make the inner
ViewHolder
class not static; but, should I be concerned about potential memory leaks? It feels like that would be the simplest solution; but, is there completely different pattern that I should be using (such as having another class be the
onClickListener
)?

I'd like to keep my code readable and as standard practice as I can; but, not if it will violate best practices or become inefficient.

See below for clarity.

SugarOrm Model Class to Display In ViewHolder:

public class SomeModel extends SugarRecord{

@Column(name="Name")
public String name;
@Column(name="AddedDate")
public Date addedDate = new Date();
}


RecyclerViewAdapter and ViewHolder:

public class SomeModelRecyclerViewAdapter
extends RecyclerView.Adapter<SomeModelRecyclerViewAdapter.ViewHolder>{

private List<SomeModel> data;

public SomeModelRecyclerViewAdapter(List<SomeModel> data) {
this.data = data;
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.model_item, parent, false);
ViewHolder holder = new ViewHolder(view);
view.setOnClickListener(holder);
view.setOnLongClickListener(holder);
return holder;
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.someModel = data.get(position);
holder.bindData();
}

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

public static class ViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener, View.OnLongClickListener {

private static final SimpleDateFormat dateFormat =
new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

SomeModel someModel;
TextView modelNameLabel;
TextView modelDateLabel;

public SomeModel getSomeModel() {
return someModel;
}

public void setSomeModel(SomeModel someModel) {
this.someModel = someModel;
}


public ViewHolder(View itemView) {
super(itemView);
}

public void bindData() {
modelNameLabel = (TextView) itemView.findViewById(R.id.modelNameLabel);
modelDateLabel = (TextView) itemView.findViewById(R.id.modelDateLabel);
modelNameLabel.setText(someModel.name);
modelDateLabel.setText(dateFormat.format(someModel.addedDate));
}

@Override
public void onClick(View v) {
someModel.addedDate = new Date();
someModel.save();
bindData();
}

@Override
public boolean onLongClick(View v) {
someModel.delete();
return true;
}
}
}


Activity:

public class MainActivity extends AppCompatActivity {

EditText modelName;
Button addModelButton;
RecyclerView modelList;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
modelName = (EditText) findViewById(R.id.modelName);
addModelButton = (Button) findViewById(R.id.addModelButton);
modelList = (RecyclerView) findViewById(R.id.modelList);
addModelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SomeModel newRecord = new SomeModel();
newRecord.name = modelName.getText().toString();
newRecord.save();
setupRecyclerView();
}
});
setupRecyclerView();
}

private void setupRecyclerView() {
List<SomeModel> allModels = SomeModel.listAll(SomeModel.class);
SomeModelRecyclerViewAdapter adapter = new SomeModelRecyclerViewAdapter(allModels);
modelList.setHasFixedSize(true);
modelList.setLayoutManager(new LinearLayoutManager(this));
modelList.setAdapter(adapter);
}
}


Activity layout (
activity_main.xml
)
:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.aaronmbond.recyclerviewdilemaexample.MainActivity">

<EditText
android:id="@+id/modelName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
/>

<Button
android:id="@+id/addModelButton"
android:layout_alignParentStart="true"
android:layout_below="@id/modelName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/addModelButtonText"
/>

<android.support.v7.widget.RecyclerView
android:id="@+id/modelList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/addModelButton"
android:layout_alignParentStart="true"
/>

</RelativeLayout>


RecyclerView Item Layout (
model_item.xml
)
:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
android:id="@+id/modelNameLabel"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/modelDateLabel"
android:layout_alignParentStart="true"
android:layout_below="@id/modelNameLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>

</RelativeLayout>

Answer Source

I ran into a similar requirement few days back. You are right about the part - The ViewHolder, as a static inner class, doesn't have access to the RecyclerView itself to to modify the adapter data or notify that the data changed.

So the way to handle this is to define an interface which encapsulates all operations within your viewholder which need it to trigger something on the RecyclerView. Sample interface definition from my code -

/**
 * Parent fragment or activity to implement this interface to listen to item deletes.
 * Item deletes effect the state of the parent
 */
public interface OnItemModifiedListener {
    void itemDeleted(Cart.CartItem item);
    void itemQuantityChanged(Cart.CartItem item, int newQuantity);
    void itemRemovedAll();
}

The parent fragment or Activity implements this interface and passes it on to the Adapter as part of it's constructor. Sample constructor from my code again -

public SimpleItemRecyclerViewAdapter(Context context, List<Cart.CartItem> items, OnItemModifiedListener l) {

    //this variable is declared as a adapter state variable
    mItemModifiedListener = l;

}

Now when a certain operation happens within your viewholder (specifically clicks) then invoke the appropriate method on this interface. Sample again from my code where i invoke this interface when a row is deleted -

holder.mDeleteView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    //show an alert to user to confirm before remving the item from cart
                    AlertDialog alertDialog = new AlertDialog.Builder(getActivity()).create();
                    //alertDialog.setTitle("Alert");
                    alertDialog.setMessage(getString(R.string.alert_remove_item_from_cart_text));
                    alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, getString(android.R.string.ok),
                            new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog, int which) {
                                    mValues.remove(holder.mItem);
                                    if(null != mItemModifiedListener)mItemModifiedListener.itemDeleted(holder.mItem);
                                    notifyItemRemoved(position);
                                    //notifyDataSetChanged();
                                }
                            });
                    alertDialog.setButton(AlertDialog.BUTTON_NEGATIVE, getString(android.R.string.no),
                            new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog, int which) {
                                    dialog.dismiss();
                                }
                            });
                    alertDialog.show();
                }
            });

the below link is a good read too - https://antonioleiva.com/recyclerview-listener/

Hope this helps...

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download