Android通过ID从Room数据库获取单条Client数据的问题求助
我需要从Room数据库中获取单条Client数据,客户端列表的每个条目都带编辑按钮,点击按钮时通过ID获取对应Client数据并在编辑页面展示详情。目前尝试了两种方案,均存在问题:
基于协程的方案
使用Room的协程函数获取数据,能运行但协程会在编辑页面展示后才返回新查询的Client数据,导致编辑的是上一次选中的客户端。试过join()、viewModelScope.async配合await等方法,都没解决。
相关代码
ClientDao.kt
@Dao interface ClientDao { @Query("SELECT * FROM tblClient WHERE id = :id") suspend fun getClientToEdit(id: Int): List<Client> }
ClientRepository.kt
class ClientRepository(private val clientDao: ClientDao) { val clientSearchResults = MutableLiveData<List<Client>>() suspend fun getClientToEdit(id: Int) { clientSearchResults.value = clientDao.getClientToEdit(id) } }
ClientViewModel.kt
class ClientViewModel(application: Application): ViewModel() { private val repository: ClientRepository val clientSearchResults: MutableLiveData<List<Client>> init { val clientDB = ManagementDatabase.getDatabase(application) val clientDao = clientDB.clientDao() repository = ClientRepository(clientDao) clientSearchResults = repository.clientSearchResults } fun getClientToEdit(clientId: Int) = viewModelScope.launch { repository.getClientToEdit(clientId) } }
ManagementApp.kt
ClientScreen( onEditClient = { id -> clientViewModel.getClientToEdit(id) val editClientList: List<Client>? = clientViewModel.clientSearchResults.value //This looks awful but it works // It just gets the client details of the selected client if (editClientList != null) { if (editClientList.firstOrNull() != null) { selectedClient = editClientList[0]
核心问题
需要让clientViewModel.getClientToEdit(id)执行完成后再运行后续代码,但不知道怎么实现。
基于Flow的方案
尝试用Flow获取数据,方式和全量列表一致,但遇到错误。
相关代码
ClientDao.kt
@Dao interface ClientDao { @Query("SELECT * FROM tblClient WHERE id = :id") fun getClientToEdit(id: Int): Flow<List<Client>> }
ClientRepository.kt
class ClientRepository(private val clientDao: ClientDao) { fun getClientSearchResults(id: Int): Flow<List<Client>> = clientDao.getClientToEdit(id) }
ClientViewModel.kt
class ClientViewModel(application: Application): ViewModel() { private val repository: ClientRepository init { val clientDB = ManagementDatabase.getDatabase(application) val clientDao = clientDB.clientDao() repository = ClientRepository(clientDao) } fun getClientToEdit(clientId: Int): LiveData<List<Client>> { return repository.getClientSearchResults(id = clientId).asLiveData() } }
ManagementApp.kt
ClientScreen( onEditClient = { id -> val editClientList by clientViewModel.getClientToEdit(id).observeAsState(listOf()) //This looks awful but it works // It just gets the client details of the selected client if (editClientList != null) { if (editClientList.firstOrNull() != null) { selectedClient = editClientList[0]
核心问题
使用observeAsState时出现“@Composable调用只能在@Composable函数上下文进行”的错误,尽管代码确实处于@Composable函数中。
解决方案
修复协程方案的问题
核心问题是点击事件中直接读取LiveData值时,协程异步操作还未完成,导致拿到旧数据。正确做法是在Composable中观察LiveData变化,值更新后再执行后续逻辑:
- 给Repository的LiveData设置初始空列表:
// ClientRepository.kt val clientSearchResults = MutableLiveData<List<Client>>(emptyList())
- 在Composable中观察状态,用
LaunchedEffect响应数据更新:
// ManagementApp.kt val editClientList by clientViewModel.clientSearchResults.observeAsState(emptyList()) ClientScreen( onEditClient = { id -> // 仅触发查询,不直接读取结果 clientViewModel.getClientToEdit(id) } ) // 当查询结果更新时执行后续逻辑 LaunchedEffect(editClientList) { if (editClientList.isNotEmpty()) { selectedClient = editClientList.first() // 此处可添加导航到编辑页的逻辑,如navController.navigate("edit/${selectedClient.id}") } }
修复Flow方案的问题
错误原因是observeAsState是Composable函数,不能放在点击事件的回调(非Composable上下文)中。优化后的Flow方案更适合Jetpack Compose:
- 优化ViewModel,用StateFlow存储选中的客户端数据:
// ClientViewModel.kt class ClientViewModel(application: Application): ViewModel() { private val repository: ClientRepository private val _selectedClient = MutableStateFlow<Client?>(null) val selectedClient: StateFlow<Client?> = _selectedClient init { val clientDB = ManagementDatabase.getDatabase(application) val clientDao = clientDB.clientDao() repository = ClientRepository(clientDao) } fun getClientToEdit(clientId: Int) { viewModelScope.launch { repository.getClientSearchResults(clientId) .first() // 单条数据查询仅取一次结果 .let { list -> _selectedClient.value = list.firstOrNull() } } } }
- 在Composable中观察StateFlow状态:
// ManagementApp.kt val selectedClient by clientViewModel.selectedClient.collectAsState() ClientScreen( onEditClient = { id -> clientViewModel.getClientToEdit(id) } ) // 选中的客户端数据更新时执行逻辑 LaunchedEffect(selectedClient) { selectedClient?.let { client -> // 更新选中状态或导航到编辑页 // 如:navController.navigate("edit/${client.id}") } }
- 额外优化:Dao层直接返回单条Client(而非列表),更符合语义:
// ClientDao.kt @Dao interface ClientDao { @Query("SELECT * FROM tblClient WHERE id = :id LIMIT 1") fun getClientToEdit(id: Int): Flow<Client?> } // ClientRepository.kt class ClientRepository(private val clientDao: ClientDao) { fun getClientSearchResults(id: Int): Flow<Client?> = clientDao.getClientToEdit(id) }
内容的提问来源于stack exchange,提问作者Ryderr

