使用viewModelScope.launch获取返回值及LiveData改造等技术问题咨询
Hey there, let's break down each of your questions one by one with clear explanations and fixes:
1. 如何在viewModelScope.launch中获取Int返回值?修复Code A
首先,你的原始Code A存在两个核心问题:
viewModelScope.launch启动的是异步协程,它的lambda表达式返回类型是Unit,你无法直接在里面return一个Int作为edit方法的返回值(编译器会报错)。- 同步方法中启动异步协程,方法会立刻返回,根本等不到数据库操作完成的结果。
修复方案
把HomeViewModel的edit方法改成挂起函数,让调用方在协程环境中调用它,这样就能同步获取数据库操作的返回值:
class HomeViewModel(private val mDBVoiceRepository: DBVoiceRepository) : ViewModel() { // 改成suspend函数,允许在协程中挂起等待结果 suspend fun edit(aMVoice: MVoice): Int { return mDBVoiceRepository.edit(aMVoice) } }
调用示例(在Fragment中)
binding.button.setOnClickListener { lifecycleScope.launch { val aMVoice = MVoice() val rowId = mHomeViewModel.edit(aMVoice) binding.button.text = "ID is: $rowId" } }
这样,协程会挂起直到数据库edit操作完成,你就能拿到正确的Int返回值。
2. 设计为LiveData<Int>的简便实现方式,修复Code B
首先要纠正一个误区:Room中返回LiveData的DAO方法不需要加suspend修饰符——Room会自动在内部的后台线程执行数据库操作,并将结果发送到LiveData中,无需手动处理协程。
先修正Code B的DAO和Repository
@Dao interface DBVoiceDao{ // Room返回LiveData的方法无需suspend,自动后台执行 @Update fun edit(aMVoice: MVoice): LiveData<Int> } class DBVoiceRepository private constructor(private val mDBVoiceDao: DBVoiceDao){ // 直接返回DAO的LiveData即可 fun edit(aMVoice: MVoice): LiveData<Int>{ return mDBVoiceDao.edit(aMVoice) } }
然后编写HomeViewModel的edit方法
直接返回Repository的LiveData即可,ViewModel无需额外处理线程:
class HomeViewModel(private val mDBVoiceRepository: DBVoiceRepository) : ViewModel() { fun edit(aMVoice: MVoice): LiveData<Int>{ return mDBVoiceRepository.edit(aMVoice) } }
这种方式最简便,Room已经帮我们处理了所有异步逻辑,调用方只需要观察LiveData即可。
3. Code C正确、Code D返回null的原因
Code C能得到正确结果的原因
binding.button.setOnClickListener { val aMVoice = MVoice() var id=mHomeViewModel.edit(aMVoice) id.observe(viewLifecycleOwner){ rowId-> binding.button.text="ID is: "+ rowId.toString() } }
这里使用observe(viewLifecycleOwner)注册了一个观察者,当Room的数据库操作完成、LiveData发射结果时,观察者的回调会被触发。此时拿到的rowId是已经加载完成的有效值,所以能正确显示。
Code D返回null的原因
binding.button.setOnClickListener { lifecycleScope.launch{ val aMVoice = MVoice() var id=mHomeViewModel.edit(aMVoice) binding.button.text="ID is: "+ id.value.toString() } }
id.value是同步获取LiveData当前的缓存值,而此时Room的数据库操作还在后台线程执行,LiveData还没有收到任何结果,所以value为null。即使你在协程中调用,也不会等待LiveData完成异步加载——因为LiveData的加载逻辑是独立于当前协程的,协程只是调用了edit方法,不会阻塞等待结果。
如果想在协程中获取LiveData的值,可以使用liveData.observeOnce()之类的扩展方法,或者改用Flow并调用first()来等待结果。
内容的提问来源于stack exchange,提问作者HelloCW

