How to Update Recyclerview Adapter Data

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.

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);

How to refresh recyclerView after changing data?

Change this:

holder.cardView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
listener.onLongClicked(alarm);
return true;
}
});

To this:

holder.cardView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
listener.onLongClicked(alarm);
alarms.remove(holder.getAdapterPosition()); //or alarms.remove(alarm);
notifyItemRemoved(holder.getAdapterPosition()); //or notifyDataSetChanged();
return true;
}
});

The problem is that you're removing the alarm from your database when you call removeAlarm() in your Activity, but you're not actually removing it from the alarms list in your Adapter. The change isn't being reflected because the data set never changes in your code. When you recreate your Activity, you pass the new database list into the Adapter, so it updates then.

I also recommend not doing this:

public void getData(){
new Thread(new Runnable() {
@Override
public void run() {
for (Alarm alarm : alarmDao.getAll()){
alarmsList.add(alarm);
}
}
}).start();
}

While alarmsList in your Activity and alarms are the same instance, and thus reflect the same additions and removals, there's no guarantee this logic will complete before the RecyclerView is inflated and the adapter attached. That means you could end up with missing data, especially on slower devices.

Unless you have an absolute ton of Alarms, there's no need to run that in a new Thread. You could just move that for-loop right above where you initialize the Adapter, and use the addAll() method:

alarmsList.addAll(alarmDuo.getAll());
mAdapter = //your assignment from your code

If you do need it to be async, attach the Adapter later, inside your Thread:

public void getData(){
new Thread(new Runnable() {
@Override
public void run() {
alarmsList.addAll(alarmDuo.getAll());
runOnUiThread(new Runnable() {
mAdapter = new AlarmRecyclerAdapter(alarmsList, recyclerListener);
recyclerView.setAdapter(mAdapter);
}
}
}).start();
}

Remove the assignment, adapter setting and notifyDataSetChanged() calls from your onCreate() method:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.alarms_activity);
db = App.getInstance().getDataBase();
alarmDao = db.AlarmDao();
recyclerView = findViewById(R.id.recycler);
recyclerListener = new OnClickedRecyclerAdapter<Alarm>() {
@Override
public void onLongClicked(final Alarm data) {
removeAlarm(data);
}

@Override
public void onClicked(Alarm data) {

}
};
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
recyclerView.setLayoutManager(mLayoutManager);
getData();

}

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.

Update recyclerview item from background thread after changing configuration

AsyncTask is deprecated since API level 30. It has issues especially for configuration changes per documentation:

AsyncTask was intended to enable proper and easy use of the UI thread.
However, the most common use case was for integrating into UI, and
that would cause Context leaks, missed callbacks, or crashes on
configuration changes.

And because of these issues it shouldn't be used in long running tasks.

AsyncTasks should ideally be used for short operations (a few seconds
at the most.) If you need to keep threads running for long periods of
time, it is highly recommended you use the various APIs provided by
the java.util.concurrent package such as Executor, ThreadPoolExecutor
and FutureTask.

Issue:

When you run tasks in doInBackground, when the configuration changes, the activity is recreated (i.e. the old activity is destroyed, and a new activity is created); but the AsyncTask that was attached to the old activity continues to run (causing memory leaks); and so later on when onPostExecute() is ready to publish the results to the UI thread; it will release them to the destroyed activity rather than the current activity displayed on the screen; and the user won't see them consequently. Therefore the result is dismissed in your case.

So, it's recommended to see other background threading alternatives such as Executors either with callbacks to update the UI or with ViewModel (which survive configuration changes) & LiveData to publish the results to the the UI.

Solution With Executor

  • Here Executor is provided:

Listener:

interface ProgressListener {
void onPostExecute();

void onProgressUpdate(int value);
}

Fragment:

    ListAdapter adapter = new ListAdapter(activity, items, new ProgressListener() {
@Override
public void onPostExecute() {
((ListAdapter.ViewHolder) Objects.requireNonNull(recyclerView.
findViewHolderForLayoutPosition(0))).name.setText("task is finished");
}

@Override
public void onProgressUpdate(int value) {
recyclerView.post(() ->
((ListAdapter.ViewHolder) Objects.requireNonNull(recyclerView.findViewHolderForLayoutPosition(0))).
name.setText(String.valueOf(value)));
recyclerView.invalidate();
}
});

mRecyclerView.setAdapter(adapter);

Adapter: just use single thread executor:

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

ProgressListener progressListener;

public ListAdapter(Context context, ArrayList<String> items, ProgressListener listener) {
mInflater = LayoutInflater.from(context);
this.items = items;
progressListener = listener;
}


// ........... CODE IS OMITTED


// ViewHolder constructor

public ViewHolder(@NonNull View itemView, ListAdapter mAdapter) {
super(itemView);
this.mAdapter = mAdapter;
this.name = itemView.findViewById(R.id.file_name);
itemView.setOnClickListener(v -> {
Executors.newSingleThreadExecutor().execute(() -> {
for (int i = 0; i <= 30; i++) {
progressListener.onProgressUpdate(i);
SystemClock.sleep(500);
}
progressListener.onPostExecute();
});
});
}


}

With AsyncTask

  • If you still need to solve this using AsyncTask, which is not recommended as previously mentioned. You need someway to preserve the adapter & AsnyncTask instances; here a ViewModel is used:

Create the ViewModel to save the adapter & AsnyncTask instances

public class MainViewModel extends AndroidViewModel {

AsyncTask<Void, Integer, Void> myTask;
ListAdapter adapter;

public MainViewModel(@NonNull Application application) {
super(application);
}

}

In fragment: Instantiate the adapter only if it's null, and pass an instance of AsynkTask to the adapter:

    if (viewModel.adapter == null)
viewModel.adapter = new ListAdapter(activity, items, viewModel);
recyclerView.setAdapter(viewModel.adapter);

Finally use the ViewModel AsynkTask instance in the adapter:

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

// ......... code is omitted

private AsyncTask<Void, Integer, Void> task;

public ListAdapter(Context context, ArrayList<String> items, AsyncTask<Void, Integer, Void> myTask) {
mInflater = LayoutInflater.from(context);
this.items = items;
this.task = myTask;
}

// ......... code is omitted


public class ViewHolder extends RecyclerView.ViewHolder {
final ListAdapter mAdapter;
final TextView name;

public ViewHolder(@NonNull View itemView, ListAdapter mAdapter) {
super(itemView);
this.mAdapter = mAdapter;
this.name = itemView.findViewById(R.id.file_name);
itemView.setOnClickListener(v -> {
task = new Task();
task.execute();
});
}
}


}

Problem Implement Update UI with LiveData, Retrofit, Coroutine on Recyclerview : adapter Recyclerview not update

You have 3 different LiveDatas, right? One with a list of news data, one with a loading error message, and one with a loading state.

You set up an Observer for each of these, and that observer function gets called whenever the LiveData's value updates. That's important, because here's what happens when your request succeeds, and you get some new data:

if (response.isSuccessful) {
newsList.postValue(response.body()?.data)
loadError.postValue(null)
loading.postValue(false)
}

which means newsList updates, then loadError updates, then loading updates.

So your observer functions for each of those LiveDatas run, in that order. But you're not testing the new values (except for null checks), so the code in each one always runs when the value updates. So when your response is successful, this happens:

  • newsList observer runs, displays as successful, calls getDataNews
  • loadError observer runs, value is null so nothing happens
  • loading observer runs, value is false but isn't checked, displays as loading, calls getDataNewsShimmer

So even when the response is successful, the last thing you do is display the loading state


You need to check the state (like loading) before you try to display it. But if you check that loading is true, you'll have a bug in fetchNews - that sets loading = true, starts a coroutine that finishes later, and then immediately sets loading = false.

I'd recommend trying to create a class that represents different states, with a single LiveData that represents the current state. Something like this:

// a class representing the different states, and any data they need
sealed class State {
object Loading : State()
data class Success(val newsList: List<NewsModel>?) : State()
data class Error(val message: String) : State()
}

// this is just a way to keep the mutable LiveData private, so it can't be updated
private val _state = MutableLiveData<State>()
val state: LiveData<State> get() = _state

private fun fetchNews() {
// initial state is Loading, until we get a response
_state.value = State.Loading
job = CoroutineScope(Dispatchers.IO + exceptionHandler).launch {
val response = newsService.getNewsList()

// if you're using postValue I don't think you need to switch to Dispatchers.Main?
_state.postValue(
// when you get a response, the state is now either Success or Error
if (response.isSuccessful) State.Success(response.body()?.data)
else State.Error("Error : ${response.message()} ")
)
}
}

And then you just need to observe that state:

// you don't need to create an Observer object, you can use a lambda!
newsViewModel.state.observe(viewLifecycleOwner) { state ->
// Handle the different possible states, and display the current one

// this lets us avoid repeating 'binding.frameNews' before everything
with(binding.frameNews) {
// You could use a when block, and describe each state explicitly,
// like your current setup:
when(state) {
// just checking equality because Loading is a -singleton object instance-
State.Loading -> {
rvNews.visibility = View.VISIBLE
rvNews.isNestedScrollingEnabled = false
itemNewsLayoutFailed.visibility = View.GONE
getDataNewsShimmer()
}
// Error and Success are both -classes- so we need to check their type with 'is'
is State.Error -> {
rvNews.visibility = View.INVISIBLE
itemNewsLayoutFailed.visibility = View.VISIBLE
btnNewsFailed.setOnClickListener {
newsViewModel.refresh()
}
}
is State.Success -> {
rvNews.visibility = View.VISIBLE
rvNews.isNestedScrollingEnabled = true
itemNewsLayoutFailed.visibility = View.GONE

// Because we know state is a Success, we can access newsList on it
// newsList can be null - I don't know how you want to handle that,
// I'm just treating it as defaulting to size == 0
// (make sure you make this visible when necessary too)
if (state.newsList?.size ?: 0 == 0) root.visibility = View.GONE
else getDataNews(state.newsList)
}
}

// or, if you like, you could do this kind of thing instead:
itemNewsLayoutFailed.visibility = if (state is Error) VISIBLE else GONE
}
}

You also might want to break that display code out into separate functions (like showError(), showList(state.newsList) etc) and call those from the branches of the when, if that makes it more readable

I hope that makes sense! When you have a single value representing a state, it's a lot easier to work with - set the current state as things change, and make your observer handle each possible UI state by updating the display. When it's Loading, make it look like this. When there's an Error, make it look like this

That should help avoid bugs where you update multiple times for multiple things, trying to coordinate everything. I'm not sure why you're seeing that problem when you reload after an error, but doing this might help fix it (or make it easier to see what's causing it)



Related Topics



Leave a reply



Submit