Fran Tardencilla
Fran Tardencilla

Reputation: 309

RecyclerView with RecyclerView.Adapter not updating when more than 1 item

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

Answers (3)

Asad Mahmood
Asad Mahmood

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

Teempy
Teempy

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 ViewHolders, 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

shakac
shakac

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

Related Questions