livedata
This article is part III of a summary of the talk I gave with Yigit Boyar at the Android Dev Summit 2019.
本文是我与Yigit Boyar在2019 Android Dev Summit上的演讲摘要的第三部分。
Part II: Launching coroutines with Architecture Components
Part III: LiveData and Coroutines patterns (this post)
第三部分:LiveData和协程模式(本文)
ViewModel模式 (ViewModel patterns)
Let’s look at some patterns that can be used in ViewModels, comparing LiveData and Flow usages:
让我们看一下可以在ViewModels中使用的一些模式,比较LiveData和Flow的用法:
LiveData:将N个值发射为LiveData (LiveData: Emit N values as LiveData)
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
val currentWeather: LiveData<String> = dataSource.fetchWeather()
If we don’t do any transformations, we can simply assign one to the other.
如果我们不进行任何转换,则可以简单地将一个分配给另一个。
流程:将N个值发射为LiveData (Flow: Emit N values as LiveData)
We could use a combination of the liveData
coroutine builder and collect on the Flow (which is a terminal operator that receives each emitted value):
我们可以使用liveData
协liveData
器的组合,并在Flow上收集 (Flow是接收每个发出的值的终端运算符):
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
// Don't use this
val currentWeatherFlow: LiveData<String> = liveData {
dataSource.fetchWeatherFlow().collect {
emit(it)
}
}
But since it’s a lot of boilerplate, we added the Flow.asLiveData()
extension function, which does the same thing in one line:
但是由于涉及很多样板,我们添加了Flow.asLiveData()
扩展函数,该函数在一行中执行相同的操作:
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
val currentWeatherFlow: LiveData<String> = dataSource.fetchWeatherFlow().asLiveData()
LiveData:从数据源发出1个初始值+ N个值 (LiveData: Emit 1 initial value + N values from data source)
If the data source exposes a LiveData, we can use emitSource to pipe updates after emitting an initial value with emit:
如果数据源公开了LiveData,我们可以在发出带有emit的初始值之后使用emitSource来管道更新:
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
val currentWeather: LiveData<String> = liveData {
emit(LOADING_STRING)
emitSource(dataSource.fetchWeather())
}
流程:从数据源发出1个初始值+ N个值 (Flow: Emit 1 initial value + N values from data source)
Again, we could do this naively:
同样,我们可以天真地做到这一点:
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
// Don't use this
val currentWeatherFlow: LiveData<String> = liveData {
emit(LOADING_STRING)
emitSource(
dataSource.fetchWeatherFlow().asLiveData()
)
}
But if we leverage Flow’s own API, things look much neater:
但是,如果我们利用Flow自己的API,事情就会变得更加整洁:
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
val currentWeatherFlow: LiveData<String> =
dataSource.fetchWeatherFlow()
.onStart { emit(LOADING_STRING) }
.asLiveData()
onStart
sets the initial value and doing this we just need to convert to LiveData once.
onStart
设置初始值,然后我们只需将其转换为LiveData即可。
LiveData:暂停转换 (LiveData: Suspend transformation)
Let’s say you want to transform something coming from a data source but it might be CPU-heavy so it’s in a suspend function.
假设您要转换来自数据源的某些内容,但是它可能占用大量CPU,因此处于暂停功能中。
You can use switchMap
on the data source’s LiveData and then create the coroutine with the liveData
builder. Now you can just call emit on each result received.
您可以在数据源的switchMap
上使用switchMap
,然后使用liveData
构建器创建协程。 现在,您可以仅对收到的每个结果调用“发出”。
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
val currentWeatherLiveData: LiveData<String> =
dataSource.fetchWeather().switchMap {
liveData { emit(heavyTransformation(it)) }
}
流程:暂停转换 (Flow: Suspend transformation)
This is where Flow really shines compared to LiveData. Again we can use Flow’s API to do things more elegantly. In this case we use Flow.map
to apply the transformation on every update. This time, because we’re already in a coroutine context, we can call it directly:
与LiveData相比,Flow真正发挥了作用。 同样,我们可以使用Flow的API更优雅地进行操作。 在这种情况下,我们使用Flow.map
在每个更新上应用转换。 这次,因为我们已经处于协程环境中,所以可以直接调用它:
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
val currentWeatherFlow: LiveData<String> =
dataSource.fetchWeatherFlow()
.map { heavyTransformation(it) }
.asLiveData()
储存库模式 (Repository patterns)
![Image for post](https://i-blog.csdnimg.cn/blog_migrate/14ef2604aacedfce3f238f1b7de1fc14.png)
Not much to say about repositories, as if you’re consuming a Flow and exposing a Flow, you just use the Flow API to transform and combine data:
关于存储库,无需多说,就像您正在使用Flow并公开Flow一样,您只需使用Flow API即可转换和合并数据:
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
val currentWeatherFlow: Flow<String> =
dataSource.fetchWeatherFlow()
.map { ... }
.filter { ... }
.dropWhile { ... }
.combine { ... }
.flowOn(Dispatchers.IO)
.onCompletion { ... }
数据源模式 (Data source patterns)
Again, let’s make the distinction between a one-shot operation and a Flow.
再次,让我们区分一次操作和Flow。
![Image for post](https://i-blog.csdnimg.cn/blog_migrate/7effbdd8d62229a63a2c44a54355fb19.png)
数据源中的一键式操作 (One-shot operations in the data source)
If you’re using a library that supports suspend functions, like Room or Retrofit, you can simply use them from your suspend functions!
如果您正在使用支持诸如Room或Retrofit之类的挂起功能的库,则只需从挂起功能中使用它们即可!
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
suspend fun doOneShot(param: String) : String =
retrofitClient.doSomething(param)
However, some tools and libraries do not support coroutines yet and are callback-based.
但是,某些工具和库尚不支持协程,并且基于回调。
In that case you can use suspendCoroutine
or suspendCancellableCoroutine
.
在这种情况下,您可以使用suspendCoroutine
或suspendCancellableCoroutine
。
(I don’t know why you would want to use the non-cancellable version though, let me know in the comments!)
(不过,我不知道您为什么要使用不可取消的版本,请在评论中告诉我!)
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
suspend fun doOneShot(param: String) : Result<String> =
suspendCancellableCoroutine { continuation ->
api.addOnCompleteListener { result ->
continuation.resume(result)
}.addOnFailureListener { error ->
continuation.resumeWithException(error)
}.fetchSomething(param)
}
When you call it, you get a continuation
. In this example we are using an API that let us set a complete listener and a failure listener so in their callbacks we call continuation.resume
or continuation.resumeWithException
when we receive data or an error.
调用它时,您会得到continuation
。 在此示例中,我们使用的API允许我们设置完整的侦听器和失败的侦听器,因此在它们的回调中,当我们接收到数据或错误时,我们将调用continuation.resume
或continuation.resumeWithException
。
It’s important to note that if this coroutine is cancelled, resume
will be ignored so if your request takes a long time the coroutine will be active until one of the callbacks is executed.
重要的是要注意,如果取消了该协程,则resume
将被忽略,因此,如果您的请求花费很长时间,则协程将一直处于活动状态,直到执行了回调之一。
在数据源中公开流 (Exposing Flow in the data source)
Flow builder
流生成器
If you need to create a fake implementation of a data source or you just need something simple you can use the flow
constructor and do something like this:
如果您需要为数据源创建伪造的实现,或者只需要简单的操作,则可以使用flow
构造函数并执行以下操作:
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
override fun fetchWeatherFlow(): Flow<String> = flow {
var counter = 0
while(true) {
counter++
delay(2000)
emit(weatherConditions[counter % weatherConditions.size])
}
}
This code emits a weather condition every two seconds.
该代码每两秒钟发出一次天气状况。
Callback-based APIs
基于回调的API
If you want to convert a callback-based API to flows, you can use callbackFlow
.
如果要将基于回调的API转换为流,则可以使用callbackFlow
。
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
fun flowFrom(api: CallbackBasedApi): Flow<T> = callbackFlow {
val callback = object : Callback {
override fun onNextValue(value: T) {
offer(value)
}
override fun onApiError(cause: Throwable) {
close(cause)
}
override fun onCompleted() = close()
}
api.register(callback)
awaitClose { api.unregister(callback) }
}
It looks daunting, but if you split it apart you’ll find that it makes a lot of sense.
它看起来令人生畏,但如果将其分开,则会发现它很有意义。
- We call offer when we have a new value 当我们拥有新的价值时,我们称之为要约
- We call close(cause?) when we want to stop sending updates 当我们要停止发送更新时,我们调用close(原因?)
- We use awaitClose to define what needs to be executed when the flow is closed, which is perfect for unregistering callbacks. 我们使用awaitClose来定义关闭流时需要执行的操作,这对于注销回调非常理想。
In conclusion, coroutines and Flow are here to stay! But they don’t replace LiveData everywhere. Even with the very promising StateFlow (currently experimental) we still have the users of the Java Programming Language and Data Binding to support, so it won’ t be deprecated for a while :)
总而言之,协程和流程将继续存在! 但是他们并没有到处取代LiveData。 即使使用非常有前途的StateFlow (目前处于试验阶段),我们仍然有Java编程语言和数据绑定的用户来支持,因此暂时不会弃用它:)
Some links if you want to read more:
一些链接,如果您想了解更多信息:
Manu’s lessons learned migrating the Android Dev Summit to coroutines (including Flow)
Manu汲取的经验教训将Android Dev Summit迁移到协程(包括Flow)
My other blog posts about LiveData
我的其他有关LiveData的博客文章
livedata