Search code examples
javaandroidandroid-recyclerviewandroid-livedataandroid-viewmodel

Changes in LiveData<List<Obj>> aren't shown in recyclerView, which is subscrived on the LiveData<List>


When I click on an item, OnItemListener.onItemClick (see in Adapter code) works, and remove the respective from LiveData<List> mListLivedata (see in ViewModel). The problem is that this doesn't update recyclerView, despite the fact that there is an Observer which subscribes on this LiveData. There is still 4 views in recyclerView, but if if I click on the last item in view (after having already clicked any item before), it crashes because there is already less by one object in Livedata<List<>> (cause previous click has deleted one Object). So, the number of Objects in LiveData is reduced by one after click (which is what I need), but the number of items in view stays the same (which is bug and I don't understant where is my mistake). Where is my mistake and what is the solution?

In ViewModel:

public class PersonViewModel extends AndroidViewModel {
    private PersonRepository mRepository;
    private CompositeDisposable composite = new CompositeDisposable();
    private Single<List<Person>> mThreePersons;
    private ArrayList<Person> mList = new ArrayList<>();
    private MutableLiveData<List<Person>> mListLivedata = new MutableLiveData<>();

    public PersonViewModel(@NonNull Application application) {
        super(application);
        mRepository = new PersonRepository(application);
        mThreePersons = mRepository.getThreePersons();
        mThreePersons.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new SingleObserver<List<Person>>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                        Log.d(TAG, "onSubscribe(Disposable d): called");
                        composite.add(d);
                    }

                    @Override
                    public void onSuccess(List<Person> people) {
                        Log.d(TAG, "onSuccess: called");
                        mList.addAll(people);
                        mListLivedata.setValue(mList);
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.d(TAG, "onError: called");
                        Toast.makeText(application, "NO DATA", Toast.LENGTH_SHORT).show();
                    }
                });
    }

    LiveData<List<Person>> getListLivedata() {
        return mListLivedata;
    }

    public void removePerson(Person person) {
        mList.remove(person);
        mListLivedata.setValue(mList);
        Log.d(TAG, "removePerson: called");
    }
}

In Activity:

public class PersonRecyclerActivity extends AppCompatActivity implements PersonRecyclerAdapter.OnItemListener {

    private PersonViewModel personRecyclerViewModel;
    private PersonRecyclerAdapter personRecyclerAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.recycler_simple_layout);

        RecyclerView personRecyclerView = findViewById(R.id.recycler_view);
        personRecyclerAdapter =
                new MudakRecyclerAdapter(new PersonRecyclerAdapter.PersonRecyclerDiff(), this);
        personRecyclerView.setAdapter(personRecyclerAdapter);
        personRecyclerView.setLayoutManager(new LinearLayoutManager(this));

        personRecyclerViewModel = new ViewModelProvider(this,
                ViewModelProvider.AndroidViewModelFactory.getInstance(this.getApplication()))
                .get(PersonViewModel.class);
        personRecyclerViewModel.getListLivedata().observe(this, personList -> personRecyclerAdapter.submitList(personList));

    }

    @Override
    public void onItemClick(int position) {
        Log.d(TAG, "onItemClick: called for " + personRecyclerAdapter.getPersonAt(position).getName() + ", at the position " + position);
        personRecyclerViewModel.removePerson(personRecyclerAdapter.getPersonAt(position));
    }
}

In Adapter:

public class PersonRecyclerAdapter extends ListAdapter<Person, PersonRecyclerAdapter.PersonRecyclerViewHolder> {

    private OnItemListener mOnItemListener;

    public interface OnItemListener {
        void onItemClick(int position);
    }

    protected PersonRecyclerAdapter(@NonNull DiffUtil.ItemCallback<Person> diffCallback, OnItemListener onItemListener) {
        super(diffCallback);
        mOnItemListener = onItemListener;
    }

    public class PersonRecyclerViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        private final TextView mrName;
        private final TextView mrStatus;
        OnItemListener onItemListener;

        public PersonRecyclerViewHolder(@NonNull View itemView, OnItemListener onItemListener) {
            super(itemView);
            mrName = itemView.findViewById(R.id.tv_rec_name);
            mrStatus = itemView.findViewById(R.id.tv_rec_status);
            this.onItemListener = onItemListener;

            itemView.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {
            onItemListener.onItemClick(getAdapterPosition());
        }
    }

    public Person getPersonAt(int position) {
        return getItem(position);
    }

    @NonNull
    @Override
    public MudakRecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.recycler_simple_item, parent, false);
        return new MudakRecyclerAdapter.MudakRecyclerViewHolder(view, mOnItemListener);
    }

    @Override
    public void onBindViewHolder(@NonNull MudakRecyclerViewHolder holder, int position) {
        Person currentPerson = getItem(position);
        holder.mrName.setText(currentPerson.getName());
        holder.mrStatus.setText(currentPerson.getStatus());
    }

    static class PersonRecyclerDiff extends DiffUtil.ItemCallback<Person> {

        @Override
        public boolean areItemsTheSame(@NonNull Person oldItem, @NonNull Person newItem) {
            return oldItem == newItem;
        }

        @Override
        public boolean areContentsTheSame(@NonNull Person oldItem, @NonNull Person newItem) {
            return oldItem.getName().equals(newItem.getName());
        }
    }
}

Solution

  • ListAdapter won't do anything if you submit the same list instance to it that it was previously using, because it is designed to compare two different lists for differences between them. You should make the removePerson() method create a new List instance with the item removed and pass that to the LiveData.

    Example:

        public void removePerson(Person person) {
            mList = new ArrayList(mList); // create a new list with same contents
            mList.remove(person); // remove the item from the new list.
            mListLivedata.setValue(mList);
            Log.d(TAG, "removePerson: called");
        }