0

I am trying to implement a Task creating app. The user should be able to check the task to mark it as completed. But when the checkbox of more than one viewholder is pressed in recyclerview it starts updating the other rows. Suppose I have 3 rows - A,B,C , I check A's checkbox, and when I check B's checkbox then odd things start to happen, A's row's title changed to and now I have 2 items with B's title. And when I uncheck B it unchecks A as well. So here is my code. This mess stops when in the equals method I am removing the Object.equals(completed, task.completed) check. But in that case the setStyle function is not called, which is responsible for drawing a stroke on the textview.


@Entity(tableName = "tasks")
public class Task {
    @ColumnInfo(name = "title")
    String title;

    public String getDescr() {
        return descr;
    }

    @ColumnInfo(name = "description")
    String descr;
    @ColumnInfo(name = "completed")
    boolean completed = false;

    @ColumnInfo(name = "task_date")
    String taskDate;
    @ColumnInfo(name = "task_deadline")
    String taskDeadline;


    @NonNull
    @PrimaryKey
    @ColumnInfo(name = "taskId")
    String id;

    public Task(String title, String descr, boolean completed) {
        this.title = title;
        this.descr = descr;
        this.completed = completed;
        id = UUID.randomUUID().toString();
    }

    public Task(String title, String descr, String date, boolean completed) {
        this.title = title;
        this.descr = descr;
        this.completed = completed;
        this.taskDate = date;
        id = UUID.randomUUID().toString();
    }


    public Task(String title, String descr, String date, String time, boolean completed) {
        this.title = title;
        this.descr = descr;
        this.completed = completed;
        this.taskDate = date;
        this.taskDeadline = time;
        id = UUID.randomUUID().toString();
    }

    public boolean isEmpty() {
        return title.isEmpty() || descr.isEmpty();
    }

    public boolean isActive() {
        return !completed;
    }

    public boolean isCompleted() {
        return completed;
    }

    @NonNull
    public String getId() {
        return id;
    }


    public String getTaskDeadline() {
        return taskDeadline;
    }

    public void setId(@NonNull String id) {
        this.id = id;
    }

    public String getTitle() {
        return title != null ? title : descr;
    }

    public String getTaskDate() {
        return taskDate != null ? taskDate : "";
    }

    @Override
    public boolean equals(@Nullable Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Task task = (Task) o;
        return Objects.equals(id, task.id) && Objects.equals(title, task.title) &&
                Objects.equals(descr, task.descr) && Objects.equals(completed, task.completed);
    }
}


public class TasksAdapter extends ListAdapter {

private ItemTaskBinding binding;
private final TaskViewModel mViewModel;

TasksAdapter(TaskViewModel viewModel) {
    super(DIFF_CALLBACK);
    mViewModel = viewModel;
}

@NonNull
@Override
public TaskViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
    binding = ItemTaskBinding.inflate(layoutInflater, parent, false);
    return new TaskViewHolder(binding);
}

@Override
public void onBindViewHolder(@NonNull TaskViewHolder holder, int position) {
    Task item = getItem(position);
    holder.bind(mViewModel, item);
}

class TaskViewHolder extends RecyclerView.ViewHolder {
    private TaskViewHolder(ItemTaskBinding binding) {
        super(binding.getRoot());
    }

    void bind(TaskViewModel viewModel, Task item) {
        binding.setViewModel(viewModel);
        binding.setTask(item);
        binding.executePendingBindings();
    }
}

private static final DiffUtil.ItemCallback<Task> DIFF_CALLBACK = new DiffUtil.ItemCallback<Task>() {

    @Override
    public boolean areItemsTheSame(@NonNull Task oldItem, @NonNull Task newItem) {
        return oldItem.getId().equals(newItem.getId());
    }

    @Nullable
    @Override
    public Object getChangePayload(@NonNull Task oldItem, @NonNull Task newItem) {
        return super.getChangePayload(oldItem, newItem);

    }

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

}

And this is the XML

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <import type="android.widget.CompoundButton" />

        <variable
            name="viewModel"
            type="com.example.taskmanagement.tasks.TaskViewModel" />

        <variable
            name="task"
            type="com.example.taskmanagement.room.Task" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/rounded_border_white"
        android:onClick="@{() -> viewModel.openTaskToUpdate(task)}"
        android:paddingLeft="12dp"
        android:paddingRight="12dp"
        android:paddingEnd="12dp"
        android:paddingStart="12dp"
        android:paddingBottom="8dp"
        android:layout_marginBottom="8dp">

        <CheckBox
            android:id="@+id/completed_checkBox"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:checked="@{task.completed}"
            android:onClick="@{(view) -> viewModel.completeTask(task, ((CompoundButton)view).isChecked())}"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:layout_marginTop="10dp"/>


        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="15dp"
            android:layout_marginLeft="15dp"
            android:text="@{task.title}"
            android:textAppearance="@style/TextAppearance.AppCompat.Title"
            app:completedTask="@{task.completed}"
            app:layout_constraintStart_toEndOf="@+id/completed_checkBox"
            app:layout_constraintTop_toTopOf="@id/completed_checkBox"
            android:layout_marginEnd="5dp"
            android:layout_marginRight="5dp"
            tools:text="aaaaaaaaaaa" />

        <TextView
            android:id="@+id/description"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text='@{task.descr!=null? task.descr : "" }'
            app:layout_constraintStart_toStartOf="@id/title"
            app:layout_constraintTop_toBottomOf="@id/title"
            tools:text="aaaaaaaaa" />

        <TextView
            android:id="@+id/task_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:text="@{task.taskDeadline}"
            app:layout_constraintStart_toEndOf="@id/task_date"
            app:layout_constraintTop_toTopOf="@id/task_date"
            tools:text="15:30" />

        <TextView
            android:id="@+id/task_date"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="5dp"
            android:drawableLeft="@drawable/choose_date_resized"
            android:drawablePadding="5dp"
            android:text="@{task.taskDate}"
            app:layout_constraintStart_toStartOf="@id/completed_checkBox"
            app:layout_constraintTop_toBottomOf="@id/description"
            tools:text="12/16/2020" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>



This is the view model


    private static TasksRepository mTaskRepository;
    private static MutableLiveData<List<Task>> allItemsMutableLiveData = new MutableLiveData<>();
    private static MutableLiveData<List<Task>> completedItemsMutableLiveData = new MutableLiveData<>();

    private MutableLiveData<Event<String>> _isTaskClicked = new MutableLiveData<>();
    private static List<Task> listOfTasks;
    private static List<Task> listOfCompletedTasks;

    private static final CompositeDisposable mDisposable = new CompositeDisposable();


    LiveData<Event<String>> getIsTaskClicked() {
        return _isTaskClicked;
    }

    TaskViewModel(TasksRepository taskRepository) {
        mTaskRepository = taskRepository;
        getListOfTasks();
    }

    public void openTaskToUpdate(Task task) {
        _isTaskClicked.setValue(new Event(task.getId())); // Trigger the event by setting a new Event as a new value
    }

    private static void getListOfTasks() {
        mDisposable.add(mTaskRepository.getAllTasksFromDB().map(taskList -> {
                    listOfTasks = taskList;
                    return listOfTasks;
                }).observeOn(AndroidSchedulers.mainThread())
                        .subscribe(new Consumer<List<Task>>() {
                            @Override
                            public void accept(List<Task> taskList) throws Exception {
                                allItemsMutableLiveData.setValue(taskList);
                            }
                        })
        );
    }

    public static LiveData<List<Task>> getItems() {
        if (allItemsMutableLiveData.getValue() != null) {
            for (int i = 0; i < allItemsMutableLiveData.getValue().size(); i++) {
                Timber.d("task" + i + allItemsMutableLiveData.getValue().get(i).getTitle());
            }
        }
        return allItemsMutableLiveData;

    }

    void getAllActiveTasks() {
        mDisposable.add(mTaskRepository.getAllActiveTasks().subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer<List<Task>>() {
                    @Override
                    public void accept(List<Task> taskList) throws Exception {
                        allItemsMutableLiveData.setValue(taskList);
                    }
                }));
    }

    public static LiveData<List<Task>> getCompletedItems() {
        mDisposable.add(mTaskRepository.getAllCompletedTasks()
                .observeOn(Schedulers.io()).map(taskList -> {
                    listOfCompletedTasks = taskList;
                    return listOfCompletedTasks;
                }).observeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer<List<Task>>() {
                    @Override
                    public void accept(List<Task> taskList) throws Exception {
                        completedItemsMutableLiveData.setValue(listOfCompletedTasks);
                    }
                }));

        return completedItemsMutableLiveData;
    }

    void deleteAllTasks() {
        mDisposable.add(Completable.fromAction(new Action() {
            @Override
            public void run() throws Exception {
                mTaskRepository.deleteAllTasks();
            }
        }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe());
    }


    public void completeTask(Task task, Boolean isChecked) {
        if (isChecked) {
            mDisposable.add(Completable.fromAction(() -> mTaskRepository.completeTask(task.getId(), true))
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread()).subscribe());
        } else {
            mDisposable.add(Completable.fromAction(() -> mTaskRepository.activateTask(task.getId()))
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread()).subscribe());
        }
    }


    @Override
    protected void onCleared() {
        mDisposable.clear();
        super.onCleared();
    }
}
  • I think you require a change in your implementation. There should be a field "isChecked" in your Task data model, when it is selected, set it true, unselected, set it false. And in your method areContentsTheSame(), should have oldItem.isSelected.equals(newitem.isSelected), This way it data population will be consistent, and will be refreshed using diffutils on content change. One more reason to keep that in model to retain state of the checked item, suppose you check an item, then scroll the recycelrview to bottom, then come again to that item, its checked state will be lost. – akashzincle Apr 24 '20 at 21:39
  • I have isCompleted() method which does what you have just described. The checkbox part works as intended. When I add a checkbox condition check in areContentsTheSame() method the mess starts. As you can see in the data model I have equals() method overridden where the last check relates to checkbox's condition. – Anush Hakobyan Apr 25 '20 at 07:56
  • can u post viewmodel code here – akashzincle Apr 25 '20 at 08:29
  • @akashzincle I have added it. – Anush Hakobyan Apr 25 '20 at 14:16
  • In checkbox you are calling viewModel.completeTask on "click", instead can you try once checkonCangelistner, This might help. For check box change listener in data binding you may refer this link https://stackoverflow.com/a/37288929/3497972 – akashzincle Apr 25 '20 at 14:29

0 Answers0