livedata使用详解
重点 (Top highlight)
This article is part II 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 (this post)
第二部分:使用体系结构组件启动协程(本文)
Jetpack’s Architecture Components provide a bunch of shortcuts so you don’t have to worry about Jobs and cancellation. You simply have to choose the scope of your opeations:
Jetpack的体系结构组件提供了许多快捷方式,因此您不必担心作业和取消。 您只需要选择操作范围即可:
ViewModel范围 (ViewModel scope)
This is one of the most common ways to launch a coroutine because most data operations begin in the ViewModel. With the viewModelScope extension, jobs are cancelled automatically when the ViewModel is cleared. Use viewModelScope.launch
to start coroutines.
这是启动协程的最常用方法之一,因为大多数数据操作始于ViewModel。 使用viewModelScope扩展,清除ViewModel时将自动取消作业。 使用viewModelScope.launch
启动协程。
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
class MainActivityViewModel : ViewModel {
init {
viewModelScope.launch {
// Do things!
}
}
}
活动和片段范围 (Activity and Fragment scopes)
Similarly, you can scope an operation to a specific instance of a view if you use lifecycleScope.launch
.
同样,如果您使用lifecycleScope .launch
,则可以将操作范围lifecycleScope .launch
于视图的特定实例。
You can even have a narrower scope if you limit the operation to a certain lifecycle state with launchWhenResumed
, launchWhenStarted
or launchWhenCreated
.
如果使用launchWhenResumed
, launchWhenStarted
或launchWhenCreated
将操作限制在某个生命周期状态, launchWhenResumed
范围甚至更窄。
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
class MyActivity : Activity {
override fun onCreate(state: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
// Run
}
lifecycleScope.launchWhenResumed {
// Run
}
}
}
适用范围 (Application scope)
There are good use cases for an application-wide scope (read all about it here) but, first, you should consider using WorkManager if your job must be executed eventually.
在整个应用程序范围内都有很好的用例(请在此处阅读全部内容),但是,首先,如果必须最终执行作业,则应考虑使用WorkManager 。
ViewModel + LiveData (ViewModel + LiveData)
So far we’ve seen how to start a coroutine but not how to receive a result from it. You could use a MutableLiveData
like so:
到目前为止,我们已经看到了如何启动协程,但是没有看到如何从协程中接收结果。 您可以这样使用MutableLiveData
:
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
// Don't do this. Use liveData instead.
class MyViewModel : ViewModel() {
private val _result = MutableLiveData<String>()
val result: LiveData<String> = _result
init {
viewModelScope.launch {
val computationResult = doComputation()
_result.value = computationResult
}
}
}
But, since you will be exposing this result to your view, you can save some typing by using the liveData
coroutine builder which launches a coroutine and lets you expose results through an immutable LiveData. You use emit()
to send updates to it.
但是,由于您会将结果展示给视图,因此可以使用 liveData
协程生成器 它将启动一个协程,并允许您通过不可变的LiveData公开结果。 你用 emit()
发送更新。
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
class MyViewModel : ViewModel() {
val result = liveData {
emit(doComputation())
}
}
带switchMap的LiveData Coroutine构建器 (LiveData Coroutine builder with a switchMap)
In some cases you want to start a coroutine whenever the value of a LiveData changes. For example when you need an ID before starting a data load operation. There’s a handy pattern for that using Transformations.switchMap:
在某些情况下,只要LiveData的值发生更改,您都希望启动协程。 例如,当您在开始数据加载操作之前需要ID时。 使用Transformations.switchMap有一个方便的模式:
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
private val itemId = MutableLiveData<String>()
val result = itemId.switchMap {
liveData { emit(fetchItem(it)) }
}
result
is an immutable LiveData that will be updated with the result from calling the fetchItem
suspend function, whenever itemId
has a new value.
result
是一个不变的LiveData,只要itemId
具有新值,它将通过调用fetchItem
暂停函数的结果进行更新。
发射另一个LiveData中的所有项目 (Emit all items from another LiveData)
This feature is less common but can also save some boilerplate: you can use emitSource
passing a LiveData source. Useful when you want to emit an initial value first and a succession of values later.
此功能不太常用,但也可以节省一些样板:您可以使用emitSource
LiveData源的emitSource。 当您要先发出初始值,然后再发出一系列值时很有用。
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
liveData(Dispatchers.IO) {
emit(LOADING_STRING)
emitSource(dataSource.fetchWeather())
}
取消协程 (Cancelling coroutines)
If you use any of the patterns above you don’t have to explicitly cancel jobs. However, there’s an important thing to remember: coroutine cancellation is cooperative.
如果您使用上述任何一种模式, 则不必显式取消作业 。 但是,有一点要记住: 协程取消是合作的 。
This means that you have to help Kotlin stop a job if the calling coroutine is cancelled. Let’s say you have a suspend function that starts an infinite loop. Kotlin has no way of stopping that loop for you, so you need to cooperate, checking if the job is active regularly. You can do that by checking the isActive
property.
这意味着如果调用协程被取消,您必须帮助Kotlin停止工作。 假设您有一个开始无限循环的暂挂函数。 Kotlin无法为您停止该循环,因此您需要合作 ,定期检查工作是否活跃。 您可以通过检查isActive
属性来实现。
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
suspend fun printPrimes() {
while(isActive) {
// Compute
}
}
By the way if you use any of the functions in kotlinx.coroutines
(like delay
), you should know they’re all cancellable, meaning that they do that check for you.
顺便说一下,如果您使用kotlinx.coroutines
任何功能(例如delay
),您应该知道它们都是可取消的 ,这意味着它们会为您进行检查。
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
suspend fun printPrimes() {
while(true) { // Ok-ish because we call delay inside
// Compute
delay(1000)
}
}
That said, I recommend you add the check regardless, since it could happen that someone removes that delay call in the future, introducing a subtle bug in your code.
就是说,我建议您无论如何都添加检查,因为将来可能会有人删除该延迟调用,从而在您的代码中引入一个细微的错误。
一拍多值 (One-shot vs multiple values)
To understand coroutines (and reactive UIs for that matter) we need to make an important distinction between:
要了解协程(以及与此相关的React式UI),我们需要在以下方面做出重要区分:
- One-shot operations: They run once and can return a result 一键式操作:它们运行一次即可返回结果
- Operations that return multiple values: Subscriptions to a source of data that can emit multiple values over time. 返回多个值的操作:订阅数据源,该数据源会随着时间的推移而发出多个值。
用协同程序进行一次操作 (One-shot operations with coroutines)
Using suspend functions and calling them with viewModelScope
or liveData{}
is a very convenient way to run non-blocking operations.
使用挂起函数并使用viewModelScope
或liveData{}
调用它们是运行非阻塞操作的一种非常方便的方法。
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
class MyViewModel {
val result = liveData {
emit(repository.fetchData())
}
}
However, things get a bit more complicated when we’re listening to changes.
但是,当我们聆听变化时,事情会变得更加复杂。
使用LiveData接收多个值 (Receiving multiple values with LiveData)
I covered this topic in LiveData beyond the ViewModel (2018), where I talked about patterns you could use to work around the fact that LiveData was never designed as a fully-featured streams builder.
除了ViewModel (2018)之外,我还在LiveData中讨论了这个主题,其中我讨论了可以用来解决LiveData从未被设计为功能齐全的流生成器的事实的模式。
Nowadays, a better approach is to use Kotlin’s Flow (warning: some parts are still experimental). Flow is similar to the reactive streams features within RxJava.
如今,一种更好的方法是使用Kotlin's Flow (警告:某些部件仍在试验中)。 流类似于RxJava中的React流功能。
However, while coroutines make non-blocking one-shot operations way easier, this is not the same case for Flow. Streams are still hard to grasp. Still, if you want to create fast and solid reactive UIs, I’d say it’s worth the time investment. Since it’s part of the language and a small dependency, many libraries are starting to add Flow support (such as Room).
但是,尽管协程使非阻塞式单发操作更容易,但对于Flow来说情况并非如此。 流仍然很难掌握。 不过,如果您想创建快速而可靠的React式UI,我认为值得花时间进行投资。 由于它是语言的一部分,并且依赖性很小,因此许多库开始增加对Flow的支持( 例如Room )。
So, instead of LiveData, we can expose Flows from the Data Source and the Repository but the ViewModel still exposes LiveData because it’s lifecycle-aware.
因此,除了LiveData,我们还可以公开来自数据源和存储库的流,但是ViewModel仍然公开LiveData,因为它具有生命周期意识。
livedata使用详解