Android Jetpack Compose中基于Flow的Room查询优化方案咨询
TODO应用交互逻辑优化咨询
需求场景
- 用户点击顶部栏圆形控件选择日期后,任务列表切换展示该日期的任务;
- 用户点击复选框或滑动删除任务时,触发数据库操作,UI同步更新状态。
当前实现方案
我在TaskViewModel中采用以下逻辑:
- 用
MutableStateFlow存储选中日期; - 通过
flatMapLatest操作符将选中日期映射为Room的taskDao.getTasksForDate(latestSelectedDate)查询(返回Flow<List<Task>>); - 结合
combine生成供UI订阅的readTasksState。
该方案可正常运行,但我认为并非最优,希望得到更合理的优化建议或替代方案。
核心代码
TaskViewModel
package com.pochopsp.dailytasks.domain import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.pochopsp.dailytasks.data.database.entity.Task import com.pochopsp.dailytasks.data.database.dao.TaskDao import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.last import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import java.util.Date class TaskViewModel( private val taskDao: TaskDao ): ViewModel() { private val _selectedDate = MutableStateFlow(Date()) private var selectedDate: StateFlow<Date> = _selectedDate.asStateFlow() private val _readTasksState = MutableStateFlow(ReadTasksState()) @OptIn(ExperimentalCoroutinesApi::class) private val _tasks = selectedDate.flatMapLatest { latestSelectedDate -> taskDao.getTasksForDate(latestSelectedDate) } val readTasksState = combine(_readTasksState, _tasks){ readtaskstate, tasks -> readtaskstate.copy( tasksForSelectedDate = tasksToDtos(tasks) ) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), ReadTasksState()) private fun tasksToDtos (tasks: List<Task>): List<TaskCardDto> { return tasks.map { t -> TaskCardDto(t.id, t.title, t.icon, t.done) }.toList() } fun onEvent(event: TaskEvent){ when(event){ is TaskEvent.DeleteTask -> { viewModelScope.launch { taskDao.deleteById(event.id) } } is TaskEvent.SetDone -> { viewModelScope.launch { taskDao.updateDoneById(event.done, event.id) } } is TaskEvent.SetSelectedDate -> { _selectedDate.value = event.selectedDate } } } }
MainActivity
package com.pochopsp.dailytasks import android.annotation.SuppressLint import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.tooling.preview.Preview import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.room.Room import com.pochopsp.dailytasks.data.database.Database import com.pochopsp.dailytasks.domain.TaskCardDto import com.pochopsp.dailytasks.domain.ReadTasksState import com.pochopsp.dailytasks.domain.TaskViewModel import com.pochopsp.dailytasks.presentation.navigation.Destinations import com.pochopsp.dailytasks.presentation.screen.MainScreen import com.pochopsp.dailytasks.presentation.theme.DailyTasksTheme import kotlinx.coroutines.flow.MutableStateFlow class MainActivity : ComponentActivity() { private val db by lazy { Room.databaseBuilder( applicationContext, Database::class.java, "tasks.db" ).fallbackToDestructiveMigration().build() } private val viewModel by viewModels<TaskViewModel>( factoryProducer = { object : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T { return TaskViewModel(db.taskDao, db.dayDao) as T } } } ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { DailyTasksTheme(darkTheme = false) { val readTasksState by viewModel.readTasksState.collectAsState() val navController = rememberNavController() NavHost( navController = navController, startDestination = "main") { composable(Destinations.Main.route) { MainScreen(state = readTasksState, onEvent = viewModel::onEvent){ navController.navigate(it.route) } } } } } } }
ReadTasksState.kt
package com.pochopsp.dailytasks.domain import java.util.Date data class ReadTasksState( val tasksForSelectedDate: List<TaskCardDto> = emptyList() )
优化建议
1. 简化状态组合逻辑
当前_readTasksState是冗余的空状态,仅用于和_tasks组合,完全可以直接将_tasks映射转换为ReadTasksState,减少不必要的Flow层级:
@OptIn(ExperimentalCoroutinesApi::class) val readTasksState = selectedDate.flatMapLatest { date -> taskDao.getTasksForDate(date) }.map { tasks -> ReadTasksState(tasksForSelectedDate = tasksToDtos(tasks)) }.stateIn( viewModelScope, SharingStarted.WhileSubscribed(5000), // 延长订阅保持时间,避免快速切换时频繁重启Flow ReadTasksState() )
这样可以去掉_readTasksState,代码更简洁,逻辑更直接。
2. 优化日期处理
- 建议用
LocalDate替代Date,它是更适合处理纯日期的类,避免时区问题,Room对LocalDate有良好支持(需添加kotlinx-datetime依赖或Room日期转换器); - 对选中日期添加防抖处理,避免用户快速切换日期触发过多数据库查询:
private val _selectedDate = MutableStateFlow(LocalDate.now()) private val selectedDate = _selectedDate.asStateFlow() .debounce(300) // 300ms防抖
3. 扩展状态封装
后续若需添加加载、错误状态,可扩展ReadTasksState并在Flow中统一处理:
// 扩展状态类 data class ReadTasksState( val tasksForSelectedDate: List<TaskCardDto> = emptyList(), val isLoading: Boolean = false, val error: String? = null ) // ViewModel中处理状态流 @OptIn(ExperimentalCoroutinesApi::class) val readTasksState = selectedDate.flatMapLatest { date -> flow { emit(ReadTasksState(isLoading = true)) try { val tasks = taskDao.getTasksForDate(date).first() emit(ReadTasksState(tasksForSelectedDate = tasksToDtos(tasks))) } catch (e: Exception) { emit(ReadTasksState(error = e.localizedMessage)) } } }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), ReadTasksState(isLoading = true))
UI可根据状态显示加载动画或错误提示,提升体验。
4. 数据库查询优化
- 给
Task表的日期字段添加索引,加速getTasksForDate查询:
@Entity(tableName = "tasks", indices = [Index(value = ["date"])]) data class Task( // ... 其他字段 val date: LocalDate )
5. 简化ViewModel创建
用扩展函数简化ViewModelProvider.Factory的写法:
// 定义通用扩展函数 inline fun <reified T : ViewModel> viewModelFactory(crossinline factory: () -> T) = object : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T { return factory() as T } } // MainActivity中使用 private val viewModel by viewModels<TaskViewModel> { viewModelFactory { TaskViewModel(db.taskDao, db.dayDao) } }
内容的提问来源于stack exchange,提问作者pochopsp
相关产品推荐
相关产品推荐

