在Activity/Fragment中,如何从ViewModel协程操作中get/wait取return值?

In Activity/Fragment, How to get/wait the return value from ViewModel coroutines operation?

按照Google(link), I try to refactor my code to ViewModel + coroutines. My question is, instead of just insert the data (original code)的codelab demo,我想等待插入操作的结果,如果插入成功应该return 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)
        }
    }
}

事件道:

@Dao
interface EventDao {
    fun insertEvent(event: Event): Long
}
  suspend fun insert(data: String): String = suspendCoroutine { cont ->
    //put logic here
    cont.resume("Done")

    //if error use this
    cont.resumeWithException(Exception("Error"))
  }

我可以使用 return 删除此函数中的回调并等待 return

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

      if( status == "Done"){
      }else{
      }
    }
  }

@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
            }
        }
}

您可以将 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"))
    }
}

But is there any better solution?

我觉得有。

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

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

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

并在你的 activity 中观察它。

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

别忘了将您的 Dao 方法更改为 suspend

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

BTW此时最新版本的直播数据是2.2.0-rc03

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

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