You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Room数据库基于Boolean值隐藏条目 隐藏已完成Todo功能失效排查

问题修复方案

核心错误点

你遇到的「勾选隐藏已完成任务时复选框被取消勾选、已完成任务不隐藏」问题来自两处核心错误:

  • Room的SQL查询条件逻辑写反,无法正确过滤已完成任务
  • RecyclerView Adapter的复选框绑定逻辑存在缺陷,列表重绘时会反向篡改任务的勾选状态

第一步:修正Dao层查询逻辑

你原查询中的(todoCheckBox != :hideCompleted OR todoCheckBox = 0)条件逻辑完全不符合过滤需求,正确的过滤规则是:

  • 当hideCompleted = false时,返回所有匹配搜索关键词的任务,不区分完成状态
  • 当hideCompleted = true时,仅返回未完成(todoCheckBox = 0)且匹配搜索关键词的任务

将两个排序查询的WHERE条件替换为正确逻辑,修改后的Dao查询代码如下:

@Query("SELECT * FROM todo_table WHERE (:hideCompleted = 0 OR todoCheckBox = 0) AND title LIKE '%' || :searchQueryText || '%'  ORDER BY title COLLATE NOCASE")
fun getTasksSortedByName(searchQueryText : String, hideCompleted : Boolean): Flow<List<Todo>>

@Query("SELECT * FROM todo_table WHERE (:hideCompleted = 0 OR todoCheckBox = 0) AND title LIKE '%' || :searchQueryText || '%'  ORDER BY time ASC")
fun getTasksSortedByDateCreated(searchQueryText : String, hideCompleted : Boolean): Flow<List<Todo>>

第二步:修复Adapter复选框状态错乱问题

复选框被自动取消勾选是RecyclerView的经典适配问题:你在绑定ViewHolder时没有先解绑复选框的监听器,导致列表刷新重绘时,setChecked操作触发了旧的勾选监听器,反向把任务的todoCheckBox字段改成了false。

2.1 补全数据库更新能力

首先在Dao、Repository、ViewModel中补全更新单个任务的方法,用于持久化用户主动勾选的状态:

  1. Dao层新增更新方法
@Update
suspend fun updateTodo(todo: Todo)
  1. Repository层对应新增代理方法
suspend fun updateTodo(todo: Todo) {
    todoDao.updateTodo(todo)
}
  1. ViewModel层新增主线程可调用的更新方法,同时删除init块中重复定义userDao的冗余代码
fun updateTodo(todo: Todo) {
    viewModelScope.launch(Dispatchers.IO) {
        repository.updateTodo(todo)
    }
}

2.2 修正Adapter绑定逻辑

修改ListAdapter的绑定逻辑,绑定数据前先清空监听器,避免状态误改,参考实现如下:

class ListAdapter : ListAdapter<Todo, ListAdapter.TodoViewHolder>(TodoDiffCallback()) {

    private var onCheckedChangeListener: ((Todo) -> Unit)? = null

    // 对外提供勾选状态变化的监听入口
    fun setOnCheckedChangeListener(listener: (Todo) -> Unit) {
        onCheckedChangeListener = listener
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
        val binding = ItemTodoBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return TodoViewHolder(binding)
    }

    override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
        val currentTodo = getItem(position)
        val binding = holder.binding

        // 核心修复:绑定数据前先置空监听器,避免setChecked触发监听篡改状态
        binding.todoCheckBox.setOnCheckedChangeListener(null)
        // 绑定当前任务的真实勾选状态
        binding.todoCheckBox.isChecked = currentTodo.todoCheckBox
        // 给已完成任务标题加删除线
        if (currentTodo.todoCheckBox) {
            binding.todoTitle.paintFlags = binding.todoTitle.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
        } else {
            binding.todoTitle.paintFlags = binding.todoTitle.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
        }
        binding.todoTitle.text = currentTodo.title

        // 重新绑定监听器,仅响应用户主动点击的勾选操作
        binding.todoCheckBox.setOnCheckedChangeListener { _, isChecked ->
            val updatedTodo = currentTodo.copy(todoCheckBox = isChecked)
            onCheckedChangeListener?.invoke(updatedTodo)
        }
    }

    inner class TodoViewHolder(val binding: ItemTodoBinding) : RecyclerView.ViewHolder(binding.root)

    class TodoDiffCallback : DiffUtil.ItemCallback<Todo>() {
        override fun areItemsTheSame(oldItem: Todo, newItem: Todo): Boolean = oldItem.id == newItem.id
        override fun areContentsTheSame(oldItem: Todo, newItem: Todo): Boolean = oldItem == newItem
    }
}

2.3 Fragment中绑定监听

在Fragment初始化RecyclerView的位置,给Adapter设置勾选监听,把用户的勾选操作同步到数据库:

// 原有的adapter初始化代码后追加
adapter.setOnCheckedChangeListener { updatedTodo ->
    mTodoViewModel.updateTodo(updatedTodo)
}

验证效果

修改完成后重新运行,搜索、排序、隐藏已完成任务三个功能可正常联动:勾选隐藏已完成选项时,所有标记为完成的任务会从列表中过滤,不会再出现复选框被莫名取消勾选的问题。

内容的提问来源于stack exchange,提问作者Daniel Iroka

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 10:48:11