如何结合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,提问作者Владислава Черкасова
相关产品推荐
相关产品推荐

