在使用launch()范围时无法将值分配给LiveData变量,但可以使用runBlocking()将值分配给LiveData

时间:2019-11-28 04:37:39

标签: android kotlin coroutine kotlin-coroutines

我有一个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变量?

3 个答案:

答案 0 :(得分:0)

尝试使用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")) } } } }

Uri uri = Uri.parse("content://com.android.externalstorage.documents/document/primary:Download");
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri);
startActivityForResult(intent, 1);

答案 1 :(得分:0)

您的函数需要成为挂起函数。这是因为您要调用的函数将在函数体内进行挂起操作,并且为此,调用函数也必须是挂起函数。暂停功能是非阻塞的,一旦触发即可进行管理,例如启动,暂停,恢复和取消。

   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")
          }
        }
      }
   }

答案 2 :(得分:0)

您的测试用例在请求发出之前完成,并且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)
}