繁体   English   中英

在 Kotlin 协程中等待 LiveData 结果

[英]Wait for LiveData result in a Kotlin coroutine

我有一个存储库 class ,其中异步方法返回User包装到LiveData

interface Repository {
    fun getUser(): LiveData<User>
}

在 ViewModel 的协程 scope 中,我想等待getUser()方法的结果并使用User实例。

这就是我正在寻找的:

private fun process() = viewModelScope.launch {
   val user = repository.getUser().await()
   // do something with a user instance
}

我找不到LiveData<>.await()扩展方法,以及任何实现它的尝试。 所以在我自己做之前,我想知道也许有更好的方法?

我发现的所有解决方案都是关于使getUser()成为suspend方法,但是如果我不能更改Repository怎么办?

您应该能够使用suspendCancellableCoroutine()创建一个await()扩展 function 。 这可能不完全正确,但沿着这些思路应该可以工作:

public suspend fun <T> LiveData<T>.await(): T {
  return withContext(Dispatchers.Main.immediate) {
    suspendCancellableCoroutine { continuation ->
      val observer = object : Observer<T> {
        override fun onChanged(value: T) {
          removeObserver(this)
          continuation.resume(value)
        }
      }

      observeForever(observer)

      continuation.invokeOnCancellation {
        removeObserver(observer)
      }
    }
  }
}

这应该返回LiveData发出的第一个值,而不会留下观察者。

这是一个适合您需要的扩展 function,这个 function 还包括最大等待时间参数。

fun <T> LiveData<T>.getOrAwaitValue(
    time: Long = 2,
    timeUnit: TimeUnit = TimeUnit.SECONDS,
    afterObserve: () -> Unit = {}
): T {
    var data: T? = null
    val latch = CountDownLatch(1)
    val observer = object : Observer<T> {
        override fun onChanged(o: T?) {
            data = o
            latch.countDown()
            this@getOrAwaitValue.removeObserver(this)
        }
    }
    this.observeForever(observer)

    afterObserve.invoke()

    // Don't wait indefinitely if the LiveData is not set.
    if (!latch.await(time, timeUnit)) {
        this.removeObserver(observer)
        throw TimeoutException("LiveData value was never set.")
    }

    @Suppress("UNCHECKED_CAST")
    return data as T
}
suspend inline fun <T> suspendCoroutineWithTimeout(
    timeout: Long,
    crossinline block: (CancellableContinuation<T>) -> Unit
): T? {
    var finalValue: T? = null
    withTimeoutOrNull(timeout) {
        finalValue = suspendCancellableCoroutine(block = block)
    }
    return finalValue
}

suspend inline fun <T> suspendCoroutineObserverWithTimeout(
    timeout: Long,
    data: LiveData<T>,
    crossinline block: (T) -> Boolean
): T? {
    return suspendCoroutineWithTimeout<T>(timeout) { suspend ->
        var observers : Observer<T>? = null
        val oldData = data.value
         observers = Observer<T> { t ->
             if (oldData == t) {
                 KLog.e("参数一样,直接return")
                 return@Observer
             }
             KLog.e("参数不一样,刷新一波")
            if (block(t) && !suspend.isCancelled) {
                suspend.resume(t)
                observers?.let { data.removeObserver(it) }
            }
        }

        data.observeForever(observers)
        suspend.invokeOnCancellation {
            KLog.e("删除observiers")
            observers.let { data.removeObserver(it) }
        }
    }
}

您实际上可以“等待”协程中的实时数据

这是一种解决方法,可能有更好的选择:

suspend fun retrieveUser(): LiveData<User> {
     val liveUser: MutableLiveData<User> = MutableLiveData()

     var counter = 0
     val timeOut = 20 // 2 sec

     while(liveUser.value.isNullOrEmpty()) {
          if(counter > timeout) break
          counter++
          delay(100)
     }

     return liveUser

真正的 Kotlin 方法是更改存储库接口并使 getUser() 成为挂起方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM