Reputation: 309
I have a RecyclerView with a RecyclerView adapter that only updates as it´s supposed to when there´s just 1 child when there´s 2 or more does not update the information. To get the correct information displayed I need to close and then reopen the app.
Checked with the debugger and found that the right and complete information does reach the onBindViewHolder method when the data is updated but does not update the UI even when I call notifyDataSetChanged, here is the code:
This is when the adapter and the recyclerview is initialized:
LinearLayoutManager layoutManagerForServers = new LinearLayoutManager(getContext());
recyclerViewServers.setLayoutManager(layoutManagerForServers);
mainArrayListForServers = new ArrayList<>();
mAdapterForServers = new ViewServersAdapter(getContext());
recyclerViewServers.setAdapter(mAdapterForServers);
this is when the data gets updated:
final ViewModelForServers viewModel = new ViewModelProvider(this).get(ViewModelForServers.class);
final LiveData<ArrayList<Server>> liveData = viewModel.getServersMediatorLiveData();
liveData.observe(this, new Observer<ArrayList<Server>>() {
@Override
public void onChanged(@Nullable ArrayList<Server> serverArrayList) {
if (serverArrayList != null) {
mainArrayListForServers = serverArrayList;
// update the UI here with values in the snapshot
mAdapterForServers.setServersArrayList(mainArrayListForServers);
mAdapterForServers.notifyDataSetChanged();
setEmptyView();
}
}
});
and finally this is the recycler adapter:
public class ViewServersAdapter extends RecyclerView.Adapter<ViewServersAdapter.MyViewHolder> {
private ArrayList<Server> serversArrayList = new ArrayList<>();
private ViewServersFragmentItemBinding viewServersFragmentItemBinding;
private Context context;
ViewServersAdapter(Context context){
this.context = context;
}
static class MyViewHolder extends RecyclerView.ViewHolder{
Context context;
MyViewHolder(@NonNull View itemView, Context context) {
super(itemView);
this.context = context;
}
void bindView(Server server, ViewServersFragmentItemBinding viewQFragmentItemBinding){
TextView serverNameTxt = viewQFragmentItemBinding.serverName;
TextView serverStatus = viewQFragmentItemBinding.serverStatus;
TextView serverNumber = viewQFragmentItemBinding.serverNumber;
TextView userOnCall = viewQFragmentItemBinding.userOnCall;
TextView duration = viewQFragmentItemBinding.callDuration;
ImageView userProfile = viewQFragmentItemBinding.profilePicture;
serverNameTxt.setText(server.getServerName());
serverStatus.setText(Utils.getServerStatus(server.getServerStatus(),context));
serverNumber.setText(String.valueOf(server.getServerNumber()).substring(3));
if (server.getCallRequest()!= null){
userOnCall.setText(server.getCallRequest().getUser());
duration.setText(Utils.getTimeOnMinutes(server.getCallRequest().getTime(),context,false));
Uri profilePictureUri = Uri.parse(server.getCallRequest().getProfilePicture());
userProfile.setScaleX(1);
userProfile.setImageResource(ListOfProfilePictureSmallsInt.get(Utils.getPhotoUrlIndex(profilePictureUri)));
} else {
userOnCall.setText("");
duration.setText("");
userProfile.setScaleX(-1);
userProfile.setImageResource(R.drawable.ic_waiting);
}
}
}
void setServersArrayList(ArrayList<Server> serverArrayList){
this.serversArrayList = serverArrayList;
}
@NonNull
@Override
public ViewServersAdapter.MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
viewServersFragmentItemBinding = ViewServersFragmentItemBinding.inflate(inflater,parent,false);
// create a new view
ConstraintLayout v = (ConstraintLayout) viewServersFragmentItemBinding.getRoot();
RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, //width
ViewGroup.LayoutParams.WRAP_CONTENT);//height
v.setLayoutParams(lp);//override default layout params
return new MyViewHolder(v, context);
}
@Override
public void onBindViewHolder(@NonNull ViewServersAdapter.MyViewHolder holder, int position) {
Server server = serversArrayList.get(position);
holder.bindView(server, viewServersFragmentItemBinding);
}
@Override
public int getItemCount() {
return serversArrayList.size();
}
}
I would really appreciate any help. Thanks
Upvotes: 0
Views: 1942
Reputation: 636
first you need to initialize recyclerview adapter with emptylist
LinearLayoutManager layoutManagerForServers = new LinearLayoutManager(getContext());
recyclerViewServers.setLayoutManager(layoutManagerForServers);
mainArrayListForServers = new ArrayList<>();
mAdapterForServers = new ViewServersAdapter(getContext(),mainArrayListForServers);
recyclerViewServers.setAdapter(mAdapterForServers);
your adapter should take arraylist as a parameter in constructor
public class ViewServersAdapter extends RecyclerView.Adapter<ViewServersAdapter.MyViewHolder> {
private ArrayList<Server> serversArrayList;
private ViewServersFragmentItemBinding viewServersFragmentItemBinding;
private Context context;
ViewServersAdapter(Context context,ArrayList<Server> serversArrayList){
this.context = context;
this.serversArrayList = serversArrayList;
}
static class MyViewHolder extends RecyclerView.ViewHolder{
Context context;
MyViewHolder(@NonNull View itemView, Context context) {
super(itemView);
this.context = context;
}
void bindView(Server server, ViewServersFragmentItemBinding viewQFragmentItemBinding){
TextView serverNameTxt = viewQFragmentItemBinding.serverName;
TextView serverStatus = viewQFragmentItemBinding.serverStatus;
TextView serverNumber = viewQFragmentItemBinding.serverNumber;
TextView userOnCall = viewQFragmentItemBinding.userOnCall;
TextView duration = viewQFragmentItemBinding.callDuration;
ImageView userProfile = viewQFragmentItemBinding.profilePicture;
serverNameTxt.setText(server.getServerName());
serverStatus.setText(Utils.getServerStatus(server.getServerStatus(),context));
serverNumber.setText(String.valueOf(server.getServerNumber()).substring(3));
if (server.getCallRequest()!= null){
userOnCall.setText(server.getCallRequest().getUser());
duration.setText(Utils.getTimeOnMinutes(server.getCallRequest().getTime(),context,false));
Uri profilePictureUri = Uri.parse(server.getCallRequest().getProfilePicture());
userProfile.setScaleX(1);
userProfile.setImageResource(ListOfProfilePictureSmallsInt.get(Utils.getPhotoUrlIndex(profilePictureUri)));
} else {
userOnCall.setText("");
duration.setText("");
userProfile.setScaleX(-1);
userProfile.setImageResource(R.drawable.ic_waiting);
}
}
}
@NonNull
@Override
public ViewServersAdapter.MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
viewServersFragmentItemBinding = ViewServersFragmentItemBinding.inflate(inflater,parent,false);
// create a new view
ConstraintLayout v = (ConstraintLayout) viewServersFragmentItemBinding.getRoot();
RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, //width
ViewGroup.LayoutParams.WRAP_CONTENT);//height
v.setLayoutParams(lp);//override default layout params
return new MyViewHolder(v, context);
}
@Override
public void onBindViewHolder(@NonNull ViewServersAdapter.MyViewHolder holder, int position) {
Server server = serversArrayList.get(position);
holder.bindView(server, viewServersFragmentItemBinding);
}
@Override
public int getItemCount() {
return serversArrayList.size();
}
}
then you should update arraylist like:
final ViewModelForServers viewModel = new ViewModelProvider(this).get(ViewModelForServers.class);
final LiveData<ArrayList<Server>> liveData = viewModel.getServersMediatorLiveData();
liveData.observe(this, new Observer<ArrayList<Server>>() {
@Override
public void onChanged(@Nullable ArrayList<Server> serverArrayList) {
if (serverArrayList != null) {
mainArrayListForServers.clear();
mainArrayListForServers.addAll(serverArrayList);
mAdapterForServers.notifyDataSetChanged();
}
}
});
Upvotes: 0
Reputation: 491
Is it nested RecyclerView
in another RecyclerView
? Does it update after configuration change (screen rotation)?
I met the same bug multiple times, the recyclerview does not update the UI when new elements are added, though all the adapter things are done. Im using a workaround here: mRecyclerView.scrollBy(0,1)
, try to call it right after notifyDataSetChanged()
call.
How does RecyclerView
actually behave? Does it show all the items? Are they all updated with the same information? Or is it only the last item which is updated?
Edit
Seems like the problem is not the RV not updating the UI, but the databinding and how you use it. Is it actually only the last item which is updated correctly?
The problem is your viewServersFragmentItemBinding
is declared in your adapter class, but it should be declared and kept in viewholder class (also being final). This means after all your onCreateViewHolder
methods are called, only the databinding of the last viewholder is stored, and all the following onBindViewHolder
methods call bindView
passing the reference to the last viewholder
's binding.
RecyclerView
recycles viewholders, which means it does not recreate them on data changes, and the views are created only on the first call - thats why it works when you reopen the app or rotate the screen - onCreateViewHolder
and onBindViewHolder
are called one by one for each record in your list, binding view correctly.
Then, when data changes and you call onDataSetChanged()
, onCreateViewHolder
method is never called, because you already have all your ViewHolder
s, only onBindViewHolder
methods get called. The thing is they call bindView
with the same reference to the last item's databinding.
Check the article https://medium.com/androiddevelopers/android-data-binding-recyclerview-db7c40d9f0e4
The databinding is created in onCreateViewHolder
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
ViewDataBinding binding = DataBindingUtil.inflate(layoutInflater, viewType, parent, false);
return new MyViewHolder(binding);
}
public void onBindViewHolder(MyViewHolder holder, int position) {
Object obj = getObjForPosition(position);
holder.bind(obj);
}
But stored and accessed only in ViewHolder
public class MyViewHolder extends RecyclerView.ViewHolder {
private final ViewDataBinding binding;
public MyViewHolder(ViewDataBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(Object obj) {
binding.setVariable(BR.obj, obj);
binding.executePendingBindings();
}
}
Upvotes: 1
Reputation: 379
You can do one thing, add a method in adapter class like below
public void updateData(ArrayList<Server> serversArrayList)
{
this.serversArrayList = serversArrayList;
notifyDataSetChanged();
}
then call it from activity and viewmodel like this
mAdapterForServers.updateData(serverArrayList);
Upvotes: 0