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

如何结合Room与RecyclerView开发待办事项应用?

待办应用Room+RecyclerView实现问题解决

我正在开发一款待办事项应用,需使用Room存储任务数据,并通过RecyclerView加载展示。作为Android开发新手,我希望使用data class而非ViewModel,但不知如何从Room数据库获取任务并在界面展示,请求帮助。以下是我编写的相关代码:

Todo 实体类

package com.bignerdranch.android.taskmaster

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable

@Entity
data class Todo(
    @PrimaryKey(autoGenerate = true) var id: Long = 0,
    @ColumnInfo(name = "title") var title: String,
    @ColumnInfo(name = "description") var description: String,
    @ColumnInfo(name = "date") var date: String,
    @ColumnInfo(name = "isChecked") var isChecked: Boolean = false
) : Serializable

TodoDao 数据访问接口

@Dao
interface TodoDao {
    @Query("SELECT * FROM todo")
    fun getAllTasks(): LiveData<List<Todo>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun saveTodo(todo: Todo)

    @Delete
    suspend fun delete(todo: Todo)
}

AppDatabase 数据库类

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase

@Database(entities = [Todo::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun todoDao(): TodoDao

    companion object {
        @Volatile
        private var instance: AppDatabase? = null

        fun getInstance(context: Context): AppDatabase?{
            if (AppDatabase == null){
                instance = Room.databaseBuilder(context,
                    AppDatabase::class.java, "roomDatabase")
                    .build()
            }
            return instance
        }
    }
}

TaskListFragment 列表页面

class TaskListFragment : Fragment(R.layout.fragment_task_list) {
    private lateinit var todoAdapter: TodoAdapter
    private var _binding: FragmentTaskListBinding? = null
    private lateinit var todoDao: TodoDao
    private val binding: FragmentTaskListBinding
        get() = checkNotNull(_binding)
    private var todos: MutableList<Todo> = mutableListOf()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val appDatabase = AppDatabase.getInstance(this.requireActivity())
        todoDao = appDatabase.todoDao()
        val task = Todo(title = "Заголовок", description = "Описание", date="1/12/23", isChecked = false)
        GlobalScope.launch {
            todoDao.insert(task)
        }
        todoDao.getAllTasks().observe(this, Observer { tasks -
        })
    }
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentTaskListBinding.inflate(inflater, container, false)
        return binding.root
    }
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        todoAdapter = TodoAdapter(todos)
        binding.rvTodoItems.adapter = todoAdapter
        binding.rvTodoItems.layoutManager = LinearLayoutManager(this.activity)
        binding.btnAddTodo.setOnClickListener {
            NewTaskSheet().show(requireActivity().supportFragmentManager, "NewTaskTag")
        }
        requireActivity().supportFragmentManager.setFragmentResultListener(
            "requestKey",
            this
        ) { requestKey, bundle -
            val newTodo = bundle.getSerializable("bundleKey") as Todo
            todos.add(newTodo)
            todoAdapter.notifyDataSetChanged()
        }
    }
    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

TodoAdapter 列表适配器

class TodoAdapter(
    private var todos: MutableList<Todo>
):RecyclerView.Adapter<TodoAdapter.TodoViewHolder>() {

    class TodoViewHolder( ItemTodoBinding: ItemTodoBinding): RecyclerView.ViewHolder(ItemTodoBinding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {

        val layoutInflater = LayoutInflater.from(parent.context)
        val ItemTodoBinding = ItemTodoBinding.inflate(layoutInflater, parent, false)

        return TodoViewHolder(ItemTodoBinding)
    }


    private fun toggleStrikeThrough(tvTodoTitle: TextView, isChecked:Boolean){
        if(isChecked){
            tvTodoTitle.paintFlags = tvTodoTitle.paintFlags or STRIKE_THRU_TEXT_FLAG
        }else{
            tvTodoTitle.paintFlags = tvTodoTitle.paintFlags and STRIKE_THRU_TEXT_FLAG.inv()
        }
    }

    override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
          val curTodo = todos[position]
          holder.itemView.apply{
              val tvTodoTitle = findViewById(R.id.tvTodoTitle) as TextView
              val tvTodoDate = findViewById(R.id.tvTodoDate) as TextView
              tvTodoTitle.text = curTodo.title
              tvTodoDate.text=curTodo.date
              val cbDone = findViewById(R.id.cbDone) as CheckBox
              cbDone.isChecked = curTodo.isChecked
              toggleStrikeThrough(tvTodoTitle, curTodo.isChecked)
              cbDone.setOnCheckedChangeListener { _, isChecked -
                  toggleStrikeThrough(tvTodoTitle, isChecked)
                  curTodo.isChecked = !curTodo.isChecked
              }

              cbDone.setOnClickListener(View.OnClickListener{
                      todos.removeAll { todo -
                          todo.isChecked

                      }
                      notifyDataSetChanged()
              })
        }
    }

    override fun getItemCount(): Int {
          return todos.size
    }

}

问题修复与实现方案

1. 修正AppDatabase单例逻辑

原单例判断错误,且未处理多线程场景,修改为:

companion object {
    @Volatile
    private var instance: AppDatabase? = null

    fun getInstance(context: Context): AppDatabase {
        return instance ?: synchronized(this) {
            instance ?: Room.databaseBuilder(
                context.applicationContext,
                AppDatabase::class.java,
                "roomDatabase"
            ).build().also { instance = it }
        }
    }
}

2. 正确观察Room LiveData更新列表

在TaskListFragment的onCreate方法中补全LiveData观察回调,将数据库返回的任务同步到适配器:

todoDao.getAllTasks().observe(this) { tasks ->
    todos.clear()
    todos.addAll(tasks)
    todoAdapter.notifyDataSetChanged()
}

3. 替换GlobalScope为LifecycleScope

使用与Fragment生命周期绑定的协程作用域,避免内存泄漏:

// 替换原GlobalScope代码
viewLifecycleOwner.lifecycleScope.launch {
    todoDao.saveTodo(task)
}

需在模块级build.gradle添加依赖:

implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2"

4. 优化TodoAdapter逻辑

  • 直接使用Binding对象代替重复findViewById:
class TodoViewHolder(val binding: ItemTodoBinding): RecyclerView.ViewHolder(binding.root)

override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
    val curTodo = todos[position]
    holder.binding.apply {
        tvTodoTitle.text = curTodo.title
        tvTodoDate.text = curTodo.date
        cbDone.isChecked = curTodo.isChecked
        toggleStrikeThrough(tvTodoTitle, curTodo.isChecked)

        cbDone.setOnCheckedChangeListener { _, isChecked ->
            toggleStrikeThrough(tvTodoTitle, isChecked)
            curTodo.isChecked = isChecked
            // 同步更新到数据库
            viewLifecycleOwner.lifecycleScope.launch {
                todoDao.saveTodo(curTodo)
            }
        }

        // 示例:长按删除当前任务
        itemView.setOnLongClickListener {
            viewLifecycleOwner.lifecycleScope.launch {
                todoDao.delete(curTodo)
            }
            true
        }
    }
}
  • 添加适配器数据更新方法:
fun updateData(newTodos: List<Todo>) {
    todos.clear()
    todos.addAll(newTodos)
    notifyDataSetChanged()
}

然后在LiveData观察中调用:

todoDao.getAllTasks().observe(this) { tasks ->
    todoAdapter.updateData(tasks)
}

5. 同步新增任务到数据库

原代码仅更新本地列表,需将新增任务保存到Room,LiveData会自动触发列表更新:

requireActivity().supportFragmentManager.setFragmentResultListener(
    "requestKey",
    this
) { _, bundle ->
    val newTodo = bundle.getSerializable("bundleKey") as Todo
    viewLifecycleOwner.lifecycleScope.launch {
        todoDao.saveTodo(newTodo)
    }
}

内容的提问来源于stack exchange,提问作者Владислава Черкасова

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 02:47:07