livedata_具有协程和流程的LiveData —

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上的演讲摘要的第三部分。

LiveData with Coroutines and Flow (ADS 2019)
具有协程和流程的LiveData(ADS 2019)

Part I: Reactive UIs

第一部分:React式用户界面

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):

我们可以使用liveDataliveData器的组合,并在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

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

数据源中的一键式操作 (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.

在这种情况下,您可以使用suspendCoroutinesuspendCancellableCoroutine

(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.resumecontinuation.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:

一些链接,如果您想了解更多信息:

My other blog posts about LiveData

我的其他有关LiveData的博客文章

翻译自: https://medium.com/androiddevelopers/livedata-with-coroutines-and-flow-part-iii-livedata-and-coroutines-patterns-592485a4a85a

livedata

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值