Day24:Hot flow - State Flow (part II)

前一篇文章中,我们介绍了 State Flow 以及它的使用方式,本篇将继续讨论 State Flow。

我们知道 SharedFlow 有提供一个函式能够让一般的 Flow 转成 SharedFlow,无独有偶,State Flow 也有类似的函式,它的名称你应该不难猜到,它就是 stateIn

stateIn

public fun <T> Flow<T>.stateIn(
    scope: CoroutineScope,
    started: SharingStarted,
    initialValue: T
): StateFlow<T>

如同 sharedIn ,需要传入 CoroutineScope 来确定这个 Flow 执行的 Scope ,以及 SharingStarted 何时才会启动 Flow。不一样的是第三个参数 initialValue ,因为 state flow 是包含初始值的,所以用 stateIn 建立时,也必须要带入。

使用范例:

class Day24 {
    val scope = CoroutineScope(Job())
    fun stateFlow(): Flow<Int> = flow {
        emit(2)
        emit(3)
    }.stateIn(
        scope,
        SharingStarted.WhileSubscribed(),
        1
    )
}

我们利用 stateIn 将 flow 转成 StateFlow ,不过因为在 flow 当中也有发射一些值,我们看看会是怎麽样的结果。

fun main() = runBlocking {
    val day24 = Day24()
    day24.stateFlow().collect { println(it) }
}
1
3

在这边因为 StateFlow 只会记得最後一个值,所以中间的 2 就被抛弃了。

Flow 可为空

前面的 stateIn 是让 Flow 转成 StateFlow,其中需要带给他一个初始值,假如 Flow 为空时,最少还是会有一个初始值可供使用。

譬如:

val stateFlow3: Flow<Int> = emptyFlow<Int>().stateIn(
        scope,
        SharingStarted.Lazily,
        1
)

那麽我们取用这个 StateFlow 的时候,就只会出现初始值。(因为StateFlow 是唯独的)

fun main() = runBlocking{
	day24.stateFlow3.collect { println(it) }
}
1

另外一种 stateIn

还有另外一种 stateIn,它本身是不需要初始值的,所以它的初始值就必须要从 Flow 来取得,那麽我们知道 Flow 里面的 FlowCollector 是一个 suspend 函式,也就是说 Flow 在产生数值的时候有可能会花费比较多的时间,所以如果 stateIn 要依赖 Flow 的值时,stateIn 就必须要是 suspend 函式才行。

它的定义如下:

public suspend fun <T> Flow<T>.stateIn(scope: CoroutineScope): StateFlow<T>

可以发现,他只有一个参数: CoroutineScope ,而且它还是一个 suspend 函式。

使用看看:

suspend fun stateFlow2(): Flow<Int> = flow {
        delay(1000)
        emit(2)
    }.stateIn(scope)

我们在这边延迟一秒钟模拟产生数值所花费的时间,如果按照上面的介绍,当我们调用 collect 时,StateFlow 在一秒内应该不会有任何的值,当一秒之後 Flow 产生一个值後,collect 才会有值出现。

fun main() = runBlocking {
    val day24 = Day24()
    day24.stateFlow2().collect { println(it) }
}

stateFlow


StateFlow 以及 MutableStateFlow 介面

StateFlow 的介面

public interface StateFlow<out T> : SharedFlow<T> {
    /**
     * The current value of this state flow.
     */
    public val value: T
}

MutableStateFlow 的介面

public interface MutableStateFlow<T> : StateFlow<T>, MutableSharedFlow<T> {
    /**
     * The current value of this state flow.
     *
     * Setting a value that is [equal][Any.equals] to the previous one does nothing.
     *
     * This property is **thread-safe** and can be safely updated from concurrent coroutines without
     * external synchronization.
     */
    public override var value: T

    /**
     * Atomically compares the current [value] with [expect] and sets it to [update] if it is equal to [expect].
     * The result is `true` if the [value] was set to [update] and `false` otherwise.
     *
     * This function use a regular comparison using [Any.equals]. If both [expect] and [update] are equal to the
     * current [value], this function returns `true`, but it does not actually change the reference that is
     * stored in the [value].
     *
     * This method is **thread-safe** and can be safely invoked from concurrent coroutines without
     * external synchronization.
     */
    public fun compareAndSet(expect: T, update: T): Boolean
}

先从继承的项目来看, StateFlow 是继承 SharedFlow,而 MutableStateFlow 除了继承 StateFlow 外,还多继承了 MutableSharedFlow。

所以 StateFlow 是 SharedFlow 的特例,而 MutableSharedFlow 则是能够与 MutableSharedFlow 一样能够呼叫 emit 来更新数值。不过由於 StateFlow 只有包含一个数值,所以呼叫 emit 之後,就会直接更新原本 State 的 value。

而里面的内容则是 StateFlow 的 value 是 val 的,而 MutableStateFlow 则是 var的,且多了一个 compareAndSet()

还记得我们在上一篇中怎麽去建立 StateFlow 以及 MutableStateFlow 的吗?

private val _state = MutableStateFlow(false)
val state = _state.asStateFlow()

StateFlow.kt 提供了两个函式,让我们直接建立 StateFlow 、MutableStateFlow。

public fun <T> MutableStateFlow(value: T): MutableStateFlow<T> = StateFlowImpl(value ?: NULL)
public fun <T> MutableStateFlow<T>.asStateFlow(): StateFlow<T> = ReadonlyStateFlow(this, null)

这边就不讲太多细节的内容了。

小结

StateFlow 是 SharedFlow 的特别用法,它只存一个值,所以适合使用在状态通知上,所以称之为 StateFlow。

我们在建立类别时,可以让更新资料的方法隐藏在类别内,只把读取的部分暴露给外面使用者,所以我们可以使用 MutableStateFlow 建立一个可以修改的 StateFlow,并且使用 asStateFlow 让它转成不可变的 StateFlow 才暴露给外面的人,在对外暴露一个可以更新 MutableStateFlow 的函式即可。

如果想要直接从 Flow 建立 StateFlow ,我们可以使用 stateIn 来将 cold flow 转成 hot flow,要记得 stateIn 有两种格式,一种是有初始值的,另一种则是从 Flow 的 emit 取得。

特别感谢

Kotlin Taiwan User Group

Kotlin 读书会


<<:  props

>>:  【Day 14】Google Apps Script - API 篇 - Document Service - 文件服务介绍

Day30:终於要进去新手村了-结语

今天终於要结束这30天的铁人赛了, 我们就来回顾看看这30天到底都写了些什麽? 虽然说主题是JS, ...

Windows 10 环境 MINGW64 找不到 PGP Keys 的问题 (Gpg4win)

How to reproduction 在 Windows 10 环境安装 Gpg4win 使用 K...

资安学习路上-picoCTF 解题(crypto)2

3.RSA(非对称式加密)-公钥e给太小 RSA加密可参考这位大大写的 https://ithelp...

Jetpack Compose intro

Jetpack Compose 是 Google 开发的现代 Declarative UI fram...

伸缩自如的Flask [day 8] ajax with jquery

在[day 7]使用form tag 来进行submit的时候,不知道大家心里会不会有个疑问? 我要...