Best Way to Update Data with a Recyclerview Adapter

How to update RecyclerView Adapter Data

I'm working with RecyclerView and both the remove and the update work well.

  1. Remove:

    There are four steps to remove an item from a RecyclerView

     list.remove(position);
    recycler.removeViewAt(position);
    mAdapter.notifyItemRemoved(position);
    mAdapter.notifyItemRangeChanged(position, list.size());

    These lines of code work for me.

  2. Update the data:

    The only things I had to do was:

     mAdapter.notifyDataSetChanged();

You had to do all of this in the Actvity/Fragment code, not in the RecyclerView Adapter code.

Best way to update data with a RecyclerView adapter

RecyclerView's Adapter doesn't come with many methods otherwise available in ListView's adapter. But your swap can be implemented quite simply as:

class MyRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
List<Data> data;
...

public void swap(ArrayList<Data> datas)
{
data.clear();
data.addAll(datas);
notifyDataSetChanged();
}
}

Also there is a difference between

list.clear();
list.add(data);

and

list = newList;

The first is reusing the same list object. The other is dereferencing and referencing the list. The old list object which can no longer be reached will be garbage collected but not without first piling up heap memory. This would be the same as initializing new adapter everytime you want to swap data.

Update data from one element in RecyclerView using ViewModel and LiveData

Broadly, the idea is that your ViewModel is what the UI layer (the View) talks to. It exposes a data state (e.g. through a LiveData object) that your UI observes, and that's what controls what the UI displays. The observer gets some new data -> you display it.

The UI also notifies the VM of user interactions - button presses and the like. So in your case, you have the deletePlace function which is basically telling the VM "hey, the user has just decided to delete this, do what you need to do". Because the VM represents the current state, that's what you need to update whenever anything happens (and it internally handles things like persisting that state)

Those are two completely separate paths, isolated from each other. Remember, the UI displays what the VM tells it to, so it doesn't need to react to the user hitting a delete button. That just gets forwarded to the VM. If the VM decides the state has changed then it will update its LiveData, the UI will observe the new data, and then it'll update. You're basically treating the VM as the source of truth, and keeping the update logic out of the view layer.


This simplifies things a lot, because when your UI just needs to observe some state in the VM, every update is the same. Fragment loaded, populating the list? Observe the LiveData, get a result, show it. App restored from background, possibly with a destroyed process? Observe the thing, display whenever an update comes in. User deletes a thing, or updates it? You're observing the data, when a change is stored, it'll update. Etc! Same logic every time. No need to worry about saving state when the fragment is destroyed - all the state is in the VM!

(There could be some exceptions to this, e.g. if a deletion requires a network call, could take some time, but you want the change to happen immediately in the view. But again, that could just be an implementation detail in the VM - it could update its local data state, and let the remote update happen in the background)


As far as implementation goes, personally I feel like it's cleaner to have the ViewHolder call a method on the Adapter, like deleteItem(index: Int) or whatever - just because the VH is a UI component, and the adapter is the thing that sits between a data set and the UI that's meant to display it. Feels more like it's the adapter's job to handle "the user clicked delete on this item" etc., if that makes sense. Doesn't hugely matter though.

And it's typical to make the Adapter an inner class of its parent Fragment - that way if the Fragment has a viewmodel reference, the Adapter can just see it. Otherwise you could just pass/set it on the adapter as a property during setup. A ViewModel is a shared resource so no worries about passing it around

Update recyclerview data

This is what you have to do

add this method into your adapter class

public void updateData(List<String> filesList, int flag) {
if (flag == 0) { //append
for (int i = 0; i < filesList.size(); i++) {
files.add(messageList.get(i));
notifyItemInserted(getItemCount());
}
} else { //clear all
files.clear();
notifyDataSetChanged();
}
}

Then whenever you need to update recycle view call like below

mItemListAdapter.updateData(yourNewListOfItems, 0);

if you need to reset recycle-view call like below

mItemListAdapter.updateData(null, 1);

Update recyclerview in android studio

It's not going to work, because you can't add to recycler view data from SQLite(i suggest) directly without refreshing the whole app.

Android devs recommend to use Android Room instead of pure SQLite API:

Caution: Although these APIs are powerful, they are fairly low-level
and require a great deal of time and effort to use:

There is no compile-time verification of raw SQL queries. As your data graph changes, you need to update the affected SQL queries
manually. This process can be time consuming and error prone.
You need to use lots of boilerplate code to convert between SQL queries and data objects.

For these reasons, we highly recommended using the Room Persistence
Library as an abstraction layer for accessing information in your
app's SQLite databases.

However, you can use Use Android Room (abstract layer of SQLite) with MVVM pattern - it would work with data and you will able to add data to RecyclerView dynamically (LiveData).

Read android devs tutorial about MVVM, LiveData, and Room, it's relatively simple.

You will need to create : Data entity (your table items), DAO interface, Database itself (without manually writing bulky stuff), Repository (mediator between DAO and ViewModel), and ViewModel (who will track your data and keep it through the lifecycle, then connect it to recycler view.

For further reading

  • LiveData
  • ViewModel
  • Migrate from SQLite to Room
  • Android Architecture Components

How to update data correctly in RecyclerView to see changes after insert or update data from another activity

You should use startActivityForResult to launch this other activity, when the other activity is closed, onActivityResult in your main activity is triggered, so when that happens you know that the other activity has finished its work and you should update the item from your DB.

it goes like this :

  1. main activity calls startActivityForResult (Intent intent, int requestCode) to launch the second activity with the intent that contains the data to be used in second activity and a request code which is an integer to indicate which activity you're opening (we will use later).
  2. second activity does the required work provided the intent from the main activity, just before it calls finish it should call setResult(int resultCode, Intent data)

    where result code : an integer to indicate if the work it did resulted in success or failure (it can be any integer that you assign e.g 1 for success and 0 for failure)

    data : is an intent that carry some data from your second activity, for the sake of our case here just send the same intent that you received in second activity to the first one since it carries the necessary data.
  3. since you started your second activity with startActivityForResult, when it gets finished, onActivityResult (int requestCode, int resultCode, Intent data) is called in main activity with the requestCode which you should use if statement on to make sure its the requestCode that you started the second activity with (where in case you have third activity that does something else you'd carry the correct operation flow when it returns), and data that should indicate which item you were doing work on.
  4. since you have the required data you're free here to query your DB again to correctly update the mentioned item and then call adapter.notifyItemChanged.

your main activity should look like :

public class MainActivity extends AppCompatActivity {
private final int MY_SECOND_ACTIVITY_REQUEST_CODE =2 , MY_THIRD_ACTIVITY_REQUEST_CODE =3 ;
private final int RESULT_SUCCESS =1 , RESULT_FALIURE =0;

...

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == MY_SECOND_ACTIVITY_REQUEST_CODE){
if (resultCode ==RESULT_SUCCESS)
SecondActivityFinishedUpdateAdapterData(data);
else somethingWrongHappenedInSecondActivity();
}else if (requestCode == MY_THIRD_ACTIVITY_REQUEST_CODE){
//do something else here in case if you have third activity
}else super.onActivityResult(requestCode, resultCode, data);
}

public void startMyActivity(Intent data)
{
startActivityForResult(data), MY_SECOND_ACTIVITY_REQUEST_CODE );
}
}

your view holder should carry a reference to your main activity to be able to call startActivityForResult (I'm not sure it's the correct way but at the moment I can't think of any other way), so it might look like this :

public class RecyclerViewListaCartuchos extends RecyclerView.Adapter<RecyclerViewListaCartuchos.ViewHolder> {
ArrayList<Cartuchos> mValues;
Context mContext;
MainActivity myMainActivity
public RecyclerViewListaCartuchos(MainActivity mainActivity, Context context, ArrayList<Cartuchos> values) {
mValues = values;
mContext = context;
this.myMainActivity = mainActivity ;
}
...
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public CardView cardView;
public TextView idCartucho;
public TextView modeloColor;
public TextView tv_fecha_mod_usuario_mod;
public ImageView iv_cartucho_img;
public View layout;
Cartuchos item;
MainActivity myMainActivity ;
public ViewHolder(View v, MainActivity mainActivity) {
super(v);
this.myMainActivity = mainActivity ;
...
}

@Override
public void onClick(View view) {
int itemPosition = getLayoutPosition();
String idC = String.valueOf(idCartucho.getText());
String cantidad = String.valueOf(item.getCantidad());
//String mensaje = itemPosition + " / " + idC + "/Canti:" + cantidad;
Intent abrirEditar = new Intent(mContext, Editar.class);
abrirEditar.putExtra("idcartucho", String.valueOf(item.getIdCartucho()));
abrirEditar.putExtra("cantidad", String.valueOf(item.getCantidad()));
abrirEditar.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

myMainActivity.startMyActivity(abrirEditar);

//mContext.startActivity(abrirEditar); //commented this
//Toast.makeText(mContext, mensaje, Toast.LENGTH_SHORT).show();
}
}
}

and in your second activity just before finish you should be calling setResult like this :

Intent data = getIntent();
//put necessary data if there's any and send it back
setResult(1,data);
finish()

where intent data should contain the necessary data to be able to update the adapter when you go back if there's any.
as an example you might use holder.getAbsoulteAdapterPosition in your view holder onclick to get the position that was clicked and store it in the intent that will be send to be restored later to the main activity onActivityResult.

How to update recyclerview adapter for the changed data after Volley call

The problem is this line:

PersonDataAdapterClassList.add(GetPerDataModel); 

You are actually appending the whole response at the end of the list.

Instead, you should change the way you store the data, maybe a map with p_id as key, and then update or create when parsing the response.

Another thing that you can do, easier, but maybe less efficient: you can wipe the whole list before processing the response:

public void PERSON_DATA_PROCESSING(JSONArray array) {
PersonDataAdapterClassList.clear();
for (...

By doing that at the beginning it should work and you can keep the rest of your current code. Nevertheless, it sounds a fair use case for a map.

One more question, if one element stopped showing in a subsequent request should it be removed? If yes, clearing the data is the right approach, if you need to preserve it, then use the map.

One more tip, instead of resetting the adapter everytime you can notify the updates to the adapter. By resetting the adapter the recyclerview could recreate all the views.

For this, you should change the code of your adapter:

public class PersonRecyclerAdapter extends RecyclerView.Adapter<PersonRecyclerAdapter.ViewHolder> {
private Context context;
private final List<PersonDataModel> dataModels;
private static int currentPosition = 0;

public PersonRecyclerAdapter(Context context) {
super();
this.context = context;
this.dataModels = new ArrayList<PersonDataModel>();
}

public void updateModels(List<PersonDataModel> newModels) {
dataModels.clear();
dataModels.adAll(newModels);
notifyDataSetChaged();
}
...

Now, when you create the recyclerview, you should create the adapter there, and keep a reference to it

recyclerview = findViewById...
recyclerViewAdapter = new PersonRecyclerAdapter(this);
recyclerview.setAdaper(recyclerViewAdapter);

then in your api call:

public void PERSON_DATA_PROCESSING(JSONArray array) {
List<PersonDataModel> newModels = new ArrayList<>();
for (int i = 0; i < array.length(); i++) {
PersonDataModel GetPerDataModel = new PersonDataModel();
JSONObject json = null;
try {
json = array.getJSONObject(i);
GetPerDataModel.setID(json.getString("p_id"));
GetPerDataModel.setTaskStatus(json.getString("task_status"));
GetPerDataModel.setName(json.getString("p_name"));
GetPerDataModel.setPosition(json.getString("p_position"));
GetPerDataModel.setLoction(json.getString("p_location"));

newModels.add(GetPerDataModel);
} catch (JSONException e) {
e.printStackTrace();
}
}

if (array.length() != 0) {
recyclerViewAdapter.updateModels(newModels);
}
}

Update RecyclerView.Adapter

You could simplify your full refresh method if null is allowed as data source:

public void updateData(List data) {
this.data = data;
notifyDataSetChanged();
}

There is also class in support library for handling RecyclerView model update: DiffUtil

If you need to handle move/add/remove in more elegant way, that is definitely worth looking.



Related Topics



Leave a reply



Submit