【问题标题】:In Activity/Fragment, How to get/wait the return value from ViewModel coroutines operation?在 Activity/Fragment 中,如何获取/等待 ViewModel 协程操作的返回值?
【发布时间】:2019-06-20 14:53:27
【问题描述】:

根据 Google (link) 的 codelab 演示,我尝试将我的代码重构为 ViewModel + 协程。我的问题是,我想等待插入操作的结果,而不是仅仅插入数据(original code),如果插入成功,它应该返回 id,然后根据结果做一些事情。那么该怎么做呢?

目前,我向 ViewModel 插入方法发送一个方法作为回调。当然,观察 ViewModel 是另一种选择。但是有没有更好的解决方案?

我当前的代码:

事件活动:

viewModel.insert(Event("name"), {
    if (it == -1L) {
        Log.i("insert", "failure")
    } else {
        Log.i("insert", "success: $it")
    }
})

事件视图模型:

private val mEventDao: EventDao = AppDatabase.getDatabase(application).eventDao()
private val mJob = Job()
private val mScope = CoroutineScope(Dispatchers.Main + mJob)

fun insert(event: Event, callback: (id: Long) -> Unit) {
    mScope.launch(Dispatchers.IO) {
        val result =
            try {
                // just for testing delay situation
                delay(5000)
                val id = mEventDao.insertEvent(event)
                id
            } catch (e: Exception) {
                -1L
            }
        withContext(Dispatchers.Main) {
            callback(result)
        }
    }
}

EventDao:

@Dao
interface EventDao {
    fun insertEvent(event: Event): Long
}

【问题讨论】:

    标签: android kotlin viewmodel kotlin-coroutines coroutine


    【解决方案1】:
      suspend fun insert(data: String): String = suspendCoroutine { cont ->
        //put logic here
        cont.resume("Done")
    
        //if error use this
        cont.resumeWithException(Exception("Error"))
      }
    

    我可以用 return 移除这个函数中的回调并等待返回

      fun insertData(){
        GlobalScope.launch {
          val status = insert("This is Data!")
    
          if( status == "Done"){
          }else{
          }
        }
      }
    

    【讨论】:

    • 这是否意味着我需要在Activity 中实现一个新的scope 来调用insert(event: Event)
    【解决方案2】:

    @rofie-sagara 完全正确。您可以将suspend 添加到您的模型insert 方法中。但是没有必要使用suspendCoroutine 语法。你可以写在你的模型中

    class ViewModel {
        private val job = Job()
        private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO + job)
        private val dao = Dao()
    
        suspend fun insert(event: Event): Long =
            withContext(scope.coroutineContext) {
                try {
                    // just for testing delay situation
                    delay(5000)
                    dao.insert(event)
                } catch (e: Exception) {
                    -1L
                }
            }
    }
    

    【讨论】:

    • 这是否意味着我需要在Activity 中实现一个新的scope 来调用insert(event: Event)
    • 是的,例如,如果您想在您的活动中显示此操作的结果。否则,如果您想一劳永逸地插入此插入,您可以使用 GlobalScope。
    【解决方案3】:

    您可以将LiveData 对象添加到EventViewModel,插入完成后更新它并在Activity 订阅它:

    class EventViewModel : ViewModel() {
        //...
        var insertionId = MutableLiveData<Long>()
    
        fun insert(event: String) {
            mScope.launch(Dispatchers.IO) {
                val result =
                        try {
                            // just for testing delay situation
                            delay(5000)
                            val id = mEventDao.insertEvent(event)
                            id
                        } catch (e: Exception) {
                            -1L
                        }
    
                insertionId.postValue(result)
            }
        }
    }
    

    并在EventActivity订阅:

    class EventActivity : AppCompatActivity() {
    
        lateinit var viewModel: EventViewModel
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            viewModel = ViewModelProviders.of(this).get(EventViewModel::class.java)
            viewModel.insertionId.observe(this, android.arch.lifecycle.Observer { id ->
                // Use `id` for example to update UI. 
            })
    
            // ...
    
            viewModel.insert(Event("name"))
        }
    }
    

    【讨论】:

    • 这似乎是一个更好的解决方案。但我只是在徘徊,我的请求是反模式吗?是否需要检查插入/删除结果?
    • 当然你可以检查结果,这取决于你。例如,当您检查插入结果并且它失败时,如果他/她应该知道错误,您可以向用户显示一些错误消息。
    【解决方案4】:

    但是有没有更好的解决方案?

    我认为有。

    androidx.lifecycle.*:2.2.0 alpha01 liveData { . . . } , emit() 函数可用。

    您可以像这样在viewModel 中重写您的代码。

    fun insertData(event: String) = liveData {
            val id = mEventDao.insertEvent(event)
            emit(id)
        }
    

    并在你的活动中观察它。

    viewModel.insertData("YourEvent").observe(this) {
                updateUi(id)
        }
    

    不要忘记将您的 Dao 方法更改为 suspend

    @Dao
    interface EventDao {
        suspend fun insertEvent(event: Event): Long
    }
    

    顺便说一句,此时最新版本的实时数据是2.2.0-rc03

    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-rc03"
    

    您可以在 android 文档中看到更好的实现:Use coroutines with LiveData

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-04-20
      • 2021-01-01
      • 2013-09-07
      • 2021-08-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多