Reputation: 330
I have a repository class with an asynchronous method returning User
wrapped into a LiveData
:
interface Repository {
fun getUser(): LiveData<User>
}
In a ViewModel's coruotine scope I want to wait for a result of getUser()
method and use an User
instance.
this is what, I am looking for:
private fun process() = viewModelScope.launch {
val user = repository.getUser().await()
// do something with a user instance
}
I could not find LiveData<>.await()
extension method, and any attempts to implement it.
So before doing it my self, I wonder maybe there is some better way?
All solutions that I have found were about making getUser()
a suspend
method, but what if I can not change Repository
?
Upvotes: 7
Views: 8340
Reputation: 1
You can actually "await" for live data inside coroutine
it is a workaround, there might be better options:
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
Upvotes: -1
Reputation: 11
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) }
}
}
}
Upvotes: 1
Reputation: 1006594
You should be able to create an await()
extension function using suspendCancellableCoroutine()
. This probably is not exactly correct, but something along these lines should work:
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)
}
}
}
}
This should return the first value emitted by the LiveData
, without leaving an observer behind.
Upvotes: 10
Reputation: 330
True Kotlin way was to change Repository interface and make getUser() a suspend method.
Upvotes: -1
Reputation: 605
here is an extension function that suits your needs, this function also include maximum wait time parameter.
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()
[email protected](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
}
Upvotes: 0