【问题标题】:Cannot assign value to LiveData variable while using launch() scope but can assign value to LiveData using runBlocking()使用 launch() 范围时无法为 LiveData 变量赋值,但可以使用 runBlocking() 为 LiveData 赋值
【发布时间】:2020-03-23 16:42:31
【问题描述】:

我有一个LiveData 变量,我想在收到来自HTTP request 的响应后为其赋值。我在launch {} 范围内分配了这个值。但是,当我测试代码以查看该值是否已实际分配时,测试失败并指出该值为null

我尝试了另一种选择,即使用runBlocking {} 范围而不是launch {} 范围,并且我的测试用例通过了。用于联网的库是Retrofit

以下是使用launch() 并使测试用例失败的代码。

private var _state = MutableLiveData<State>()
val state: LiveData<State>
  get() = _state 

private var job = Job()

private val uiScope = CoroutineScope(job + Dispatchers.Main)

init {
    executeRequest(....)
}

// Test cases fails with this function
private fun executeRequest(params) {
  uiScope.launch {
    val deferred = repository.getApi()
        .requestAsync(params)

    try {
        val response = deferred.await()

        _state.value = StateSuccess(response)
    } catch (e: Exception) {
        _state.value = StateError("Failure: $e.message")
    }
  }
}

以下是使用runBlocking() 并使测试用例通过的代码。

private var _state = MutableLiveData<State>()
val state: LiveData<State>
  get() = _state 

private var job = Job()

private val uiScope = CoroutineScope(job + Dispatchers.Main)

init {
    executeRequest(....)
}

// Test cases passes with this function
private fun executeRequest(params) {
  runBlocking {
    val deferred = repository.getApi()
        .requestAsync(params)

    try {
        val response = deferred.await()

        _state.value = StateSuccess(response)
    } catch (e: Exception) {
        _state.value = StateError("Failure: $e.message")
    }
  }
}

这是测试用例

val viewModel = MyViewModel(fakeClientRepository)

val observer = Observer<State> {}

try {
    viewModel.state.observeForever(observer)

    assert(viewModel.state.value == StateSuccess("success"))
} finally {
    viewModel.state.removeObserver(observer)
}

为什么launch() 没有将值赋给LiveData 变量?

【问题讨论】:

  • 您必须检查Observer 中的值。在您的测试用例中,assert(viewModel.state.value == StateSuccess("success"))observeForever() 之后立即被调用,尽管您的 http 请求尚未完成。

标签: android kotlin coroutine kotlin-coroutines


【解决方案1】:

尝试使用Dispatchers.IO 调度程序,然后将值分配给LiveData,使用postValue() 而不是value

private fun executeRequest(params) {
  uiScope.launch {
    withContext(Dispatcher.IO){
 val deferred = repository.getApi()
        .requestAsync(params)
    try {
        val response = deferred.await()
        _state.postValue(RESPONSE)
    } catch (e: Exception) {
        _state.postValue(StateError("Failure: $e.message"))
    }

    }
  }
}

【讨论】:

  • 仍然有同样的结果,我也试着让它成为一个挂起功能,但没有奏效。
【解决方案2】:

您的函数需要成为挂起函数。这是因为您正在调用将在函数体内进行暂停操作,并且为了做到这一点,调用函数也必须是暂停函数。挂起函数是非阻塞的,一旦触发就可以进行管理——例如启动、暂停、恢复和取消。

   private suspend  fun executeRequest(params){
       job = CoroutineScope(Dispatchers.IO).launch {
       val response = repository.getApi()
            .requestAsync(params).await()

        withContext(Dispatchers.Main) {
          try {
             _state.value = StateSuccess(response)
          } catch (e: Exception) {
             _state.value = StateError("Failure: $e.message")
          }
        }
      }
   }

【讨论】:

  • 我试过这个,测试用例没有通过。我将上面的函数包装为launch 范围,但效果不佳。
  • 好的,不要使用 Dispatcher.Main,而是使用 Dispatcher.IO 并尝试一下。
  • 我已经更新了我的答案,你可以试试这样吗?
  • 暂停操作是在launch内部进行的,所以不,函数本身不需要suspend
【解决方案3】:

您的测试用例在请求完成之前完成,LiveData 的值发生变化。使用runBlockingexecuteRequest(以及您的构造函数)仅在块执行时返回;使用launch,它会在单独的Job 中启动它并立即返回。

如果您确实需要在构造函数中执行此操作,可以将 Job 分配给字段,例如

private var _state = MutableLiveData<State>()
val state: LiveData<State>
  get() = _state 

private var job = Job()

private val uiScope = CoroutineScope(job + Dispatchers.Main)

internal val requestJob = executeRequest(....)

private fun executeRequest(params): Job = uiScope.launch {
    try {
        val deferred = repository.getApi()
            .requestAsync(params)

        val response = deferred.await()

        _state.value = StateSuccess(response)
    } catch (e: Exception) {
        _state.value = StateError("Failure: $e.message")
    }
}

然后在检查值之前在您的测试用例中调用requestJob.await(),例如

val viewModel = MyViewModel(fakeClientRepository)

val observer = Observer<State> {}

runBlocking { 
    viewModel.requestJob.await()
}

try {
    viewModel.state.observeForever(observer)

    assert(viewModel.state.value == StateSuccess("success"))
} finally {
    viewModel.state.removeObserver(observer)
}

【讨论】:

  • 我已经附上了我的测试用例。
  • 是的,这个测试用例应该给出描述的结果。我添加了你将如何修改它(但我根本不明白你为什么使用observer)。
  • 我使用 observer 是因为我想观察 UI 中的值何时发生变化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-25
  • 1970-01-01
  • 2015-03-24
  • 2020-07-28
  • 1970-01-01
  • 2014-07-03
相关资源
最近更新 更多