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