kotlin flow 使用

1 创建flow

方式1

通过携程扩展函数FlowKt中的flow扩展函数可以直接构建flow,只需要传递FlowCollector收集器实现类就可以了

private fun create1(){
        val intFlow = createFlow()
        println("创建int flow: $intFlow")
        runBlocking {
            println("开始收集")
            intFlow.collect {
                println(it)
            }
            println("收集结束")
        }
    }

    /**
     * 创建flow 方式1
     */
    private fun createFlow(): Flow<Int> = flow {
        delay(1000)
        emit(1)
        delay(1000)
        emit(2)
        delay(1000)
        emit(3)
    }

通过该flow扩展函数可以了解到如下信息
函数定义泛型时需要将泛型放到fun 和函数名中间
例如

public fun <T> flow(@kotlin.BuilderInference block: suspend kotlinx.coroutines.flow.FlowCollector<T>.() -> kotlin.Unit)

定义好之后,函数中传参也可以使用该泛型继续向下传递,例如这里传递给了FlowCollector接口,用于规定flow收集器应该发送什么数据给调用方

方式2

private fun create2() {
        // 创建flow 方式2
        println("创建flow 方式2")
        val flow2 = flowOf(1, 2, 3).onEach {
            delay(1000)
        }
        runBlocking {
            flow2.collect {
                println(it)
            }
        }
    }

flowof 扩展函数其实内部也是调用flow扩展函数,只不过flowof是将传递进来的可变参数,遍历了一遍,并且调用flow收集器的emit方法发送取出而已

方式3

 private fun create3() {
        println("创建flow 方式3")
        runBlocking {
            val flow3 = listOf(1,2,3).asFlow().onEach {
                delay(1000)
            }
            flow3.collect {
                println(it)
            }
        }
    }

调用list顶级接口类Iterable的asFlow方法,其实内部还是调用了flow扩展函数,将元素遍历之后emit出去的

2 取消或中断flow

   // 测试Flow的取消 取消Flow只需要取消它所在的携程即可
    private fun testCancelFlow() {
        runBlocking {
            withTimeoutOrNull(250) { // 在 250 毫秒后超时
                simple().collect { value -> println(value) }
            }
            println("Done")
        }
    }

    fun simple(): Flow<Int> = flow {
        for (i in 1..3) {
            delay(100)
            println("Emitting $i")
            emit(i)
        }
    }

通过withTimeoutOrNull 函数可以实现取消flow携程功能, 通过该功能可以处理类似于某些场景下的超时机制,兜底逻辑等,例如:
在30秒内处理不完数据状态的转换,就直接结束当前处理,只展示处理过后的数据就可以了。因为有些场景下,数据量较大,用户等待时间太长的情况下,一般用户无法接受,所以只能采用妥协的方式来规定一个最大等待时间,来处理该组数据。一般会用在对数据做某种优化处理,但这种处理即使处理不完的情况下,产品也能接受的情况下,才会采用该方案。否则不建议执行兜底方案处理。

3 Flow 操作符

collect 是Flow中最常用,最基础的末端操作符,主要用于Flow启动流的挂起函数。除了collect还有一些其他的操作符,例如:

  • 转换为各种集合的操作符: toList/toSet/toCollection
  • 用于流元素计算的操作符:reduce / fold
  • 计算数量的操作符:count
  • 用于切换协程作用域线程的操作符:launcherIn/productIn/broadcastIn, 这里的productIn以及broadcastIn 暂时没有了解清楚其用法和作用,暂时放在这里

collect 操作符使用

collect 是Flow最基础的操作符,大多数场景下都会使用它来收集Flow中生产的数据信息

runBlocking {
            listOf(1,2,3,4,5).asFlow().collect { value -> println(value) }
        }

reduce 操作符使用

作用1: 用于数据求和

 private fun testReduce() {
        runBlocking {
            val num = listOf(1,2,3,4,5).asFlow().reduce { a, b -> a + b }
            println("求和的结果:$num")
        }
    }

作用2:用于求最大值

private fun testReduce1() {
        runBlocking {
            val maxNum = listOf(1, 5, 2, 4, 5, 6, 3).asFlow().reduce { a, b ->
                if (a > b) a else b
            }
            println("求集合中的最大值:${maxNum}")
        }
    }

fold 操作符使用

fold 也是用于计算的操作符,只不过fold可以设置累加器的初始值,与reduce相比 多一个设置初始值的操作

private fun testFold() {
        runBlocking {
            val sum = (1..5).asFlow().fold(100) { a, b -> a + b }
            println("设置初始值后的累加结果:$sum")
        }
    }

launchIn 操作符使用

launchIn用于指定协程作用域,如果单独看如下这块代码可能觉得launchIn好像没有什么作用,但是一旦多个流想要并发执行时,launchIn就可以起到它该有的作用,为流指定了协程作用域之后,相当于每个流开启了一个协程,每个流都在自己协程中运行,所以可以使用launchIn来实现并发的功能。

 val mDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
    private fun testLaunchIn() {
        val scope = CoroutineScope(mDispatcher)
        (1..5).asFlow().onEach { println(it) }.onCompletion { mDispatcher.close() }.launchIn(scope)
    }

launchIn 可以让流并发执行,例如下边这段代码,我们发现正常的流操作是一个流完成后才会执行另一个流,是串行执行的。

private fun testLaunchIn2() {
        runBlocking {
            val cosTime = measureTimeMillis {
                (1..5).asFlow()
                    .onEach { delay(200) }
                    .flowOn(Dispatchers.IO)
                    .collect{ println(it) }

                flowOf("one", "two", "three", "four", "five")
                    .onEach { delay(200) }
                    .flowOn(Dispatchers.IO)
                    .collect{ println(it) }
            }
            println("cosTime: $cosTime")
        }
    }

运行结果
在这里插入图片描述
很明显,这里的流没有并发执行,如果我们想要节省时间,提高效率,可以使用launchIn来优化这段代码,如下:

private fun testLaunchIn3() {
        runBlocking {
            val cosTime = measureTimeMillis {
                (1..5).asFlow()
                    .onEach { delay(200) }
                    .flowOn(Dispatchers.IO)
                    .onEach { println(it) }
                    .launchIn(this)

                flowOf("one", "two", "three", "four", "five")
                    .onEach { delay(200) }
                    .flowOn(Dispatchers.IO)
                    .onEach { println(it) }
                    .launchIn(this)

            }
            println("cosTime: ${cosTime}")
        }
    }

在这里插入图片描述
通过launchIn执行之后,我们发现这段代码竟然从耗时2082直接降到了10毫秒,而且这里流的执行,也被运行在被指定的IO线程的协程当中了,不再阻塞当前线程的执行,也就是我们通常说的,异步并发执行了。

4 Flow的连续性测试

private fun testFlowContinuity() {
        runBlocking {
            (1..5).asFlow()
                .filter {
                    println("Filter $it")
                    it % 2 == 0
                }
                .map {
                    println("Map $it")
                    "string $it"
                }
                .collect {
                    println("Collect $it")
                }
        }
    }

在这里插入图片描述
通过结果我们会发现流的每一个环节执行的时候都是自上而下的,并且是每个元素都会执行完流的全部流程之后,才会执行下一个元素。

5 流的状态监听

流的状态监听,主要有以下几个
onStart 监听流开始执行
onCompletion 监听流执行完成
try … finally 也可以监听整个流事件执行结束

private fun testStatusListener() {
        runBlocking {
            try {
                (1..5).asFlow()
                    .onEach { delay(200) }
                    .onStart { println("Start...") }
                    .onCompletion { println("Completion...") }
                    .collect {
                        println(it)
                    }
            }finally {
                println("执行结束")
            }
        }
    }

执行结果

Start…
1
2
3
4
5
Completion…
执行结束

6 Flow缓存策略

Flow在设置buffer的时候有三种策略,可以使用BufferOverflow枚举类参数来设置。如下:

  • suspend 挂起
  • drop_oldest 删除老的
  • drop_latest 删除最新的

** 1 设置BufferOverflow.SUSPEND 挂起**

private fun testFlowBuffer() {
        runBlocking {
            (1..5).asFlow()
                .onEach {
                    delay(100)
                    println("product data $it")
                }.buffer(2, BufferOverflow.SUSPEND)
                .collect {
                    delay(700)
                    println("collect: $it")
                }
        }
    }

运行结果

product data 1
product data 2
product data 3
product data 4
collect: 1
product data 5
collect: 2
collect: 3
collect: 4
collect: 5

该方式可以让生产和收集并发执行,当没有触发收集时,会一直生产,知道buffer区装满之后,才会停止生产,等到收集被执行完毕后,才会继续生产下一个,这种方式不会丢失数据,能保证每一个元素都会被生产和收集。效率上要比不设置缓冲区要快一些,因为有了缓冲区就可以在未触发收集函数时一直生产来提高效率。

2 设置BufferOverflow.DROP_OLDEST 删除老的数据模式

 private fun testFlowBufferOverflow2() {
        runBlocking {
            val cosTime = measureTimeMillis {
                (1..5).asFlow()
                    .onEach {
                        delay(100)
                        println("product data: $it")
                    }.buffer(2, BufferOverflow.DROP_OLDEST)
                    .collect {
                        delay(700)
                        println("collect: $it")
                    }
            }
            println("耗时:${cosTime}")
        }
    }

运行结果

product data: 1
product data: 2
product data: 3
product data: 4
product data: 5
collect: 1
collect: 4
collect: 5
耗时:2264

可以看到,这里收集器只收集到了1、4、5三个元素
原因分析:
生产完1之后,发现收集器还未到触发时机,因为生产完1只需要耗费100毫秒,但是收集一个数据需要700毫秒,所以生产完一个数据之后还有600毫秒是剩余时间,也不会触发收集,这时flow发现设置了缓存区,缓存区可缓存数量为2,则开始生产2、3,等缓存区也被生产满了之后,这时发现收集器还不需要触发,继续生产,因为缓存策略设置为删除老的数据,这时程序发现还有新的数据待生产,则继续生产新数据,而将缓存中的数据清除,所以这里看到又生产4 、5,将4、5又放入缓存区等待收集,最后1、2、3、4、5都生产完毕之后,等待700毫秒收集器开始收集,将未放在缓存区内的数据1收集后,又开始收集4、5,这时现象就能看到只收集了1、4、5了。

3 设置缓冲区为BufferOverflow.DROP_LATEST 丢弃最后产生的数据 也就是丢弃新数据 保留老的数据

 private fun testFlowDropLatest() {
        runBlocking {
            (1..5).asFlow()
                .onEach {
                    delay(100)
                    println("product data: $it")
                }
                .buffer(2, BufferOverflow.DROP_LATEST)
                .collect {
                    delay(700)
                    println("collect: $it")
                }
        }
    }

运行结果

product data: 1
product data: 2
product data: 3
product data: 4
product data: 5
collect: 1
collect: 2
collect: 3

这段代码明显和上一段代码有很大区别,在同样缓存区数量为2的情况下,这次最终收集结果中却包含了2、3数据 丢弃了4、5 数据。这就是drop_latest的作用,在生产过程中,会在缓存区中缓存指定大小的数据条目,如果还有新数据在不断地生产,则会丢弃掉最新生产的数据,直到收集器收集结束。

7 conflate 合并功能

在某些场景下,流的执行,其实用户只想关心开始和结束的数据,并不关心中间的过程数据,这个时候就可以利用conflate合并功能

private fun testFlowConflate() {
        runBlocking {
            val cosTime = measureTimeMillis {
                (1..5).asFlow().onEach {
                    delay(100)
                    println("product data: $it")
                }.conflate()
                    .collect{
                        delay(700)
                        println("collect: $it")
                    }
            }
            println("cosTime: $cosTime")
        }
    }

运算结果

product data: 1
product data: 2
product data: 3
product data: 4
product data: 5
collect: 1
collect: 5
cosTime: 1557

会发现上述运算结果,收集器中只收集了开始和结尾的数据。
该模式其实类似于给Flow设置缓存区策略为BufferOverflow.DROP_OLDEST,且缓存区尺寸为0,和设置合并功能最终结果是一致的。

8 Flow 异常处理

未捕获异常时

private fun testCatchException() {
        runBlocking {
            (1..5).asFlow().onEach {
                if (it == 4){
                    throw Exception("test exception")
                }
                delay(100)
                println("product data: $it")
            }
                .onCompletion {
                    println("onCompletion")
                }
                .collect {
                    println("collect: $it")
                }
        }
    }

运行结果

product data: 1
collect: 1
product data: 2
collect: 2
product data: 3
collect: 3
onCompletion
Exception in thread “main” java.lang.Exception: test exception

不进行任何异常处理的情况,在执行了onCompletion之后 会抛出相应的异常信息

onCompletion 中也可以捕获到异常信息

private fun testCatchException() {
        runBlocking {
            (1..5).asFlow().onEach {
                if (it == 4){
                    throw Exception("test exception")
                }
                delay(100)
                println("product data: $it")
            }
                .onCompletion { cause ->
                    if (cause != null) {
                        println("flow completed exception")
                        println("异常信息:${cause.message}")
                    }else {
                        println("onCompletion")
                    }
                }
                .collect {
                    println("collect: $it")
                }
        }
    }

运行结果

product data: 1
collect: 1
product data: 2
collect: 2
product data: 3
collect: 3
flow completed exception
异常信息:test exception

如果需要捕获异常可以使用catch

private fun testCatchException() {
        runBlocking {
            (1..5).asFlow().onEach {
                if (it == 4){
                    throw Exception("test exception")
                }
                delay(100)
                println("product data: $it")
            }
                .onCompletion { cause ->
                    if (cause != null) {
                        println("flow completed exception")
                        println("异常信息:${cause.message}")
                    }else {
                        println("onCompletion")
                    }
                }.catch { ex ->
                    println("catch exception: ${ex.message}")
                }
                .collect {
                    println("collect: $it")
                }
        }
    }

运行结果

product data: 1
collect: 1
product data: 2
collect: 2
product data: 3
collect: 3
flow completed exception
异常信息:test exception
catch exception: test exception

使用catch之后,就不会报异常了

如果将catch和onCompletion更换下位置,把catch放在前面,则不会影响下游的onCompletion

private fun testCatchException() {
        runBlocking {
            (1..5).asFlow().onEach {
                if (it == 4) {
                    throw Exception("test exception")
                }
                delay(100)
                println("product data: $it")
            }.catch { ex ->
                println("catch exception: ${ex.message}")
            }.onCompletion { cause ->
                if (cause != null) {
                    println("flow completed exception")
                    println("异常信息:${cause.message}")
                } else {
                    println("onCompletion")
                }
            }.collect {
                println("collect: $it")
            }
        }
    }

执行结果

product data: 1
collect: 1
product data: 2
collect: 2
product data: 3
collect: 3
catch exception: test exception
onCompletion

retry/ retryWhen 异常重试机器

retry 可以设置异常重试次数,根据异常信息可以判断是否要触发异常重试

private fun testCatchException() {
        runBlocking {
            (1..5).asFlow().onEach {
                if (it == 4) {
                    throw Exception("test exception")
                }
                delay(100)
                println("product data: $it")
            }.retry(2) {
                it.message == "test exception"
            }.catch { ex ->
                println("catch exception: ${ex.message}")
            }.onCompletion { cause ->
                if (cause != null) {
                    println("flow completed exception")
                    println("异常信息:${cause.message}")
                } else {
                    println("onCompletion")
                }
            }.collect {
                println("collect: $it")
            }
        }
    }

运行结果

product data: 1
collect: 1
product data: 2
collect: 2
product data: 3
collect: 3
product data: 1
collect: 1
product data: 2
collect: 2
product data: 3
collect: 3
product data: 1
collect: 1
product data: 2
collect: 2
product data: 3
collect: 3
catch exception: test exception
onCompletion

retryWhen用法

 private fun testCatchException() {
        runBlocking {
            (1..5).asFlow().onEach {
                if (it == 4) {
                    throw Exception("test exception")
                }
                delay(100)
                println("product data: $it")
            }.retryWhen { cause, attempt ->
                cause.message == "test exception" && attempt < 2
            }.catch { ex ->
                println("catch exception: ${ex.message}")
            }.onCompletion { cause ->
                if (cause != null) {
                    println("flow completed exception")
                    println("异常信息:${cause.message}")
                } else {
                    println("onCompletion")
                }
            }.collect {
                println("collect: $it")
            }
        }
    }

执行结果

product data: 1
collect: 1
product data: 2
collect: 2
product data: 3
collect: 3
product data: 1
collect: 1
product data: 2
collect: 2
product data: 3
collect: 3
product data: 1
collect: 1
product data: 2
collect: 2
product data: 3
collect: 3
catch exception: test exception
onCompletion

retry 和 retryWhen区别目前看只是设置重试条件的方式不同,在作用上目前看是一致的。

9 Flow的线程切换

flowOn 可以实现流生产或转换等环节的线程切换功能,例如:

private fun testFlowOn() {
        runBlocking {
            (1..5).asFlow()
                .onEach {
                    printWithThreadInfo("product data: $it")
                }.flowOn(Dispatchers.IO)
                .map {
                    printWithThreadInfo("$it to string")
                    "String: $it"
                }.flowOn(mDispatcher)
                .onCompletion {
                    mDispatcher.close()
                }
                .collect{
                    printWithThreadInfo("collect: $it")
                }
        }
    }

运行结果

DefaultDispatcher-worker-1 : product data: 1
DefaultDispatcher-worker-1 : product data: 2
DefaultDispatcher-worker-1 : product data: 3
DefaultDispatcher-worker-1 : product data: 4
DefaultDispatcher-worker-1 : product data: 5
pool-1-thread-1 : 1 to string
pool-1-thread-1 : 2 to string
pool-1-thread-1 : 3 to string
pool-1-thread-1 : 4 to string
pool-1-thread-1 : 5 to string
main : collect: String: 1
main : collect: String: 2
main : collect: String: 3
main : collect: String: 4
main : collect: String: 5

上述结果中可以看出,流在生产环节是受到第一个flowOn影响,将整个生产环节运行在了默认的io线程当中,而map转换时又受到了第二个flowOn的影响,运行在了线程池分配的工作线程中,最后的收集工作还是运行在主线程中。

10 Flow 中间转换操作符

中间操作符这块,flow跟其他java流类似,其中相同用法的操作符有很多,例如我们经常会用到的map转换功能,filter 过滤, take 提取等等,这里我们罗列下flow的操作符把,如下:

  • map
  • transform
  • onEach
  • filter
  • drop/ dropWhile
  • take
  • zip
  • combine
  • flattenConcat / flattenMerge
  • flatMapMerge / flatMapContact
  • flatMapLatest

map 转换操作符

map 让流或集合中的元素从一种类型转换成一种最新的类型输出到下游

 private fun testMap() {
        runBlocking {
            (1..5).asFlow().map { "string $it" }
                .collect{
                    println(it)
                }
        }
    }

运行结果

string 1
string 2
string 3
string 4
string 5

transform 转换操作符

transform 也是转换,与map转换不同,transform转换是通过调用emit来完成转换的,并且一次转换过程中,可以调用多次emit来转换出多个结果。

private fun testTransform() {
        runBlocking {
            (1..5).asFlow()
                .transform {
                    emit(it * 2)
                    delay(100)
                    emit("String: $it")
                }
                .collect {
                    println(it)
                }
        }
    }

运行结果

2
String: 1
4
String: 2
6
String: 3
8
String: 4
10
String: 5

onEach 遍历操作符

遍历流中的元素

 private fun testOnEach() {
        runBlocking {
            (1..5).asFlow()
                .onEach {
                    println("onEach: $it")
                }
                .collect {
                    println(it)
                }
        }
    }

运行结果

onEach: 1
1
onEach: 2
2
onEach: 3
3
onEach: 4
4
onEach: 5
5

filter 过滤操作符

过滤出流中想要的元素

  private fun testFilter() {
        runBlocking {
            (1..5).asFlow()
                .filter { it % 2 ==0 }
                .collect{
                    println(it)
                }
        }
    }

运行结果

2
4

drop / dropWhile 过滤操作符

过滤掉前几个元素

 private fun testDrop() {
        runBlocking {
            (1..5).asFlow()
                .drop(2) // 删除前两个元素
                .collect{
                    println(it)
                }
        }
    }

运行结果

3
4
5

dropWhile 根据设置的条件删除元素

 runBlocking {
            (1..5).asFlow()
                .dropWhile { it < 4 } // 删除小于4的元素
                .collect{
                    println(it)
                }
        }

运行结果

4
5

take 过滤操作符

取多少个emit发射的元素,也就是说设置需要获取流中元素的数量

 private fun testTake() {
        runBlocking {
            listOf(1,2,3,4,5).asFlow().take(3).collect{
                println(it)
            }
        }
    }

运行结果

1
2
3

zip 合并操作符

zip 是可以将2个flow进行合并的操作符, 合并完最终结果会以数量少的流元素个数作为最终合并后流的个数

 private fun testZip() {
        // 合并完最终结果会以数量少的流元素个数作为最终合并后流的个数
        runBlocking {
            val flowA = (1..6).asFlow()
            val flowB = flowOf("one", "two", "three", "four", "five").onEach { delay(200) }
            flowA.zip(flowB) { a, b -> "$a and $b"}
                .collect {
                    println(it)
                }
        }
        println("----------------------------------")

        runBlocking {
            val flowA = (1..6).asFlow()
            val flowB = flowOf("one", "two", "three", "four", "five","six", "seven").onEach { delay(200) }
            flowA.zip(flowB) { a, b -> "$a and $b"}
                .collect {
                    println(it)
                }
        }
    }

运行结果

1 and one
2 and two
3 and three
4 and four
5 and five
----------------------------------
1 and one
2 and two
3 and three
4 and four
5 and five
6 and six

combine 合并操作符

这个合并和前面的zip不太一样,zip会每个去对应两个流内的元素,而combine则不一样,combine会每次发射一个自己最新的元素来和flowB的元素进行合并,发射节奏不取决于flowB的节奏,而是取决于flowA自身的发射节奏。
该模式会多出很多冗余配对,目前没有想到该合并方式的使用场景。如果后续想到了,或者大家知道的话可以留言或私信我。

private fun testCombine() {
        runBlocking {
            // 每次合并时flowA 会发出新的item来和flowB进行合并
            val flowA = (1..5).asFlow().onEach { delay(100) }
            val flowB = flowOf("one", "two", "three", "four", "five").onEach { delay(200) }
            flowA.combine(flowB) {a, b -> "$a and $b"}
                .collect{
                    println(it)
                }
        }
    }

运行结果

1 and one
2 and one
3 and one
4 and two
5 and two
5 and three
5 and four
5 and five

flattenConcat 合并操作符

flattenConcat 可以将多个流拼接后,将其内部元素扁平拆解为类似一个流的效果,并且按照流的顺序,相当于流与流的简单拼接,例如:

private fun testFlattenConcat() {
        runBlocking {
            val flowA = (1..5).asFlow()
            val flowB = flowOf("one", "two", "three", "four", "five").onEach { delay(1000) }
            flowOf(flowA,flowB)
                .flattenConcat() // 拆解每个流中的元素,使flowA flowB中的元素扁平的展开
                .collect{ println(it) }
        }
    }

运行结果

1
2
3
4
5
one
two
three
four
five

flattenMerge 合并操作符

flattenMerge 对于合并流的对收集流规则的设置,主要是对并发路数的控制,内部可以设置并发路数,如果并发路数为1,则和flattenConcat没有任何区别。例子如下:

private fun testFlattenMarge() {
        runBlocking {
            val flowA = (1..5).asFlow().onEach { delay(100) }
            val flowB = flowOf("one", "two", "three", "four", "five").onEach { delay(200) }
            flowOf(flowA,flowB)
                .flattenMerge(2)
                .collect{ println(it) }
        }
    }

运行结果

1
one
2
3
two
4
5
three
four
five

flatMapMerge 转换合并操作符

flatMapMerge是先执行map转换,后直接合并处理,例:

private fun testFlatMapMerge() {
        runBlocking {
            (1..5).asFlow()
                .flatMapMerge {
                    flow {
                        emit(it)
                        delay(1000)
                        emit("string: $it")
                    }
                }.collect{ println(it) }
        }
    }

运行结果

1
2
3
4
5
string: 1
string: 2
string: 3
string: 4
string: 5

flatMapConcat 转换合并操作符

flatMapConcat 是map之后直接拼接,对比flatMapMerge来说,flatMapConcat是针对流的拼接,flatMapMerge是针对流元素的合并。从运行结果上就能看出来区别。

private fun testFlatMapConcat() {
        runBlocking {
            (1..5).asFlow()
                .flatMapConcat {
                    flow {
                        emit(it)
                        delay(1000)
                        emit("string: $it")
                    }
                }.collect{ println(it) }
        }
    }

运行结果

1
string: 1
2
string: 2
3
string: 3
4
string: 4
5
string: 5

flatMapLatest 合并操作符

当发射了新值以后,上一个flow会被自动取消掉。

  private fun testFlatMapLatest() {
        runBlocking {
            (1..5).asFlow().onEach { delay(100) }
                .flatMapLatest {
                    flow {
                        println("begin flatMapLatest $it")
                        delay(200)
                        emit("string: $it")
                        println("end flatMapLatest $it")
                    }
                }.collect {
                    println(it)
                }
        }
    }

运行结果

begin flatMapLatest 1
begin flatMapLatest 2
begin flatMapLatest 3
begin flatMapLatest 4
begin flatMapLatest 5
end flatMapLatest 5
string: 5

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值