Kotlin常用的Flow 操作符整理

22 阅读6分钟

只需要先记住这几类核心操作符:

创建 Flow
↓
转换数据
↓
过滤数据
↓
合并数据
↓
控制线程/异常/生命周期
↓
终结收集

一、创建 Flow:数据从哪里来

这类操作符负责 创建 Flow

操作符作用
flow {}最基础的 Flow 构建器
flowOf()直接发出固定几个值
asFlow()把集合、数组、序列转成 Flow
callbackFlow {}把回调 API 转成 Flow
channelFlow {}允许多协程发送数据

常用记忆

flow {
    emit(1)
    emit(2)
}
flowOf(1, 2, 3)
listOf(1, 2, 3).asFlow()

最常用的是:

flow {}
flowOf()
asFlow()
callbackFlow {}

二、转换操作符:把数据变成另一种数据

这类就是 对每个元素做加工

操作符作用
map一进一出
transform一进多出/零出/多出
mapLatest新元素来了,取消旧元素的转换
onEach对每个元素做额外动作,一般不改变值

1. map

flowOf(1, 2, 3)
    .map { it * 2 }
    .collect {
        println(it)
    }

输出: 2 4 6

记住:

map:一个元素转成另一个元素

2. transform

flowOf(1, 2, 3)
    .transform {
        emit("开始 $it")
        emit("结束 $it")
    }
    .collect {
        println(it)
    }

输出:

开始 1
结束 1
开始 2
结束 2
开始 3
结束 3

记住:

map:只能返回一个
transform:可以 emit 多个

3. onEach

flowOf(1, 2, 3)
    .onEach {
        println("准备发送 $it")
    }
    .collect {
        println("收到 $it")
    }

onEach 通常用于:

打印日志
更新状态
做副作用
加 delay

三、过滤操作符:哪些数据要,哪些不要

操作符作用
filter保留满足条件的元素
filterNot过滤掉满足条件的元素
take只取前几个
drop跳过前几个
distinctUntilChanged相邻重复值只保留一个

1. filter

flowOf(1, 2, 3, 4)
    .filter { it % 2 == 0 }
    .collect {
        println(it)
    }

输出:2 4


2. take

flowOf(1, 2, 3, 4)
    .take(2)
    .collect {
        println(it)
    }

输出: 1 2

take(2) 收到两个元素后,会取消上游。


3. distinctUntilChanged

flowOf(1, 1, 2, 2, 1)
    .distinctUntilChanged()
    .collect {
        println(it)
    }

输出: 1 2 1

注意它只去掉 相邻重复,不是全局去重。


四、合并操作符:多个 Flow 怎么合成一个

操作符记忆方式
merge多个事件流混在一起,谁先来发谁
combine多个状态流取最新值组合
zip两个流一一配对
flattenConcatFlow<Flow<T>> 串行展开
flattenMergeFlow<Flow<T>> 并发展开
flatMapConcatmap + flattenConcat
flatMapMergemap + flattenMerge
flatMapLatest新 Flow 来了,取消旧 Flow
combineTransformcombine 后可以自己 emit 多个值

最重要的 3 个:mergecombinezip

merge

多个 Flow 混合,谁先发谁先到
merge(flow1, flow2)

适合:

多个事件源混合
多个不相关数据源同时监听

combine

多个 Flow 都有值后,任意一个更新,就拿最新值重新组合
flow1.combine(flow2) { a, b ->
    "$a-$b"
}

适合:

UIState 组合
多个状态源组合
表单状态组合

zip

第一个配第一个,第二个配第二个
flow1.zip(flow2) { a, b ->
    "$a-$b"
}

适合:

一一配对
两个数据源按位置对应

最重要的 3 个 flatMap

操作符作用
flatMapConcat一个一个执行,保证顺序
flatMapMerge并发执行,不保证整体顺序
flatMapLatest只保留最新,取消旧任务

记忆:

Concat:排队
Merge:并发
Latest:最新

五、线程和上下文操作符

操作符作用
flowOn切换上游执行线程
buffer上游和下游之间加缓冲
conflate下游慢时,只保留最新值
debounce防抖,一段时间内只取最后一个
sample采样,固定时间取一个最新值

1. flowOn

flow {
    emit(loadData())
}
.flowOn(Dispatchers.IO)
.collect {
    render(it)
}

记住:

flowOn 影响的是它上游的执行线程

也就是:

flow {
    emit(loadData())
}
.flowOn(Dispatchers.IO)

这里 flow {} 在 IO 线程执行。


2. buffer

默认 Flow 是上下游串行背压的。

flow
    .buffer()
    .collect {
        delay(1000)
        println(it)
    }

buffer() 可以让上游提前生产,下游慢慢消费。


3. conflate

flow
    .conflate()
    .collect {
        delay(1000)
        println(it)
    }

下游处理慢时,中间旧值可能被跳过,只处理最新值。

适合:

进度条
位置更新
传感器数据

4. debounce

queryFlow
    .debounce(300)
    .flatMapLatest {
        search(it)
    }

适合搜索框:

用户停止输入 300ms 后,再发起搜索

六、异常处理操作符

操作符作用
catch捕获上游异常
retry失败后重试
retryWhen自定义重试条件
onCompletionFlow 完成时回调,成功失败都会走

1. catch

flow {
    emit(1)
    throw RuntimeException("error")
}
.catch { e ->
    emit(-1)
}
.collect {
    println(it)
}

输出: 1 -1

记住:

catch 捕获的是它上游的异常

2. onCompletion

flowOf(1, 2, 3)
    .onCompletion {
        println("Flow 结束了")
    }
    .collect {
        println(it)
    }

输出:

1
2
3
Flow 结束了

七、生命周期/启动前后操作符

操作符作用
onStart收集开始前执行
onEach每个元素发给下游前执行
onCompletion收集结束时执行
launchIn在指定 scope 中启动收集

常见链路

flow
    .onStart {
        emit(Loading)
    }
    .catch {
        emit(Error)
    }
    .onEach {
        render(it)
    }
    .launchIn(viewModelScope)

这在 Android 里很常见。


八、终结性操作符

终结性操作符负责 真正开始收集 Flow

操作符作用
collect收集每个元素
collectLatest新元素来了,取消旧元素处理
first取第一个
firstOrNull取第一个,没有返回 null
last取最后一个
single只有一个元素才返回
count统计数量
toList收集成 List
toSet收集成 Set
reduce无初始值聚合
fold有初始值聚合
launchIn启动异步收集,返回 Job
produceIn转成 Channel

九、真正需要优先记住的 20 个

不用一口气记所有,先记这 20 个就够用了。

第一层:必须熟

类别操作符
创建flow {}flowOf()asFlow()
转换maptransformonEach
过滤filtertakedistinctUntilChanged
合并mergecombinezip
flatMapflatMapConcatflatMapMergeflatMapLatest
异常catchonCompletion
线程flowOn
终结collectfirsttoList

第二层:知道什么时候查

场景操作符
搜索框防抖debounce
下游慢,只要最新值conflate
上下游解耦buffer
失败重试retryretryWhen
只处理最新收集逻辑collectLatest
转成 ChannelproduceIn
Android 中启动收集launchIn

十、最简单的记忆口诀

可以按这个背:

创建:flow、flowOf、asFlow

转换:map、transform、onEach

过滤:filter、take、distinct

合并:merge、combine、zip

展开:concat、merge、latest

线程:flowOn、buffer、conflate

异常:catch、retry、completion

终结:collect、first、list、count、reduce、fold

十一、Android 中最常用的一套组合

实际项目里最常见的是这种:

repository.getDataFlow()
    .onStart {
        emit(UiState.Loading)
    }
    .map { data ->
        UiState.Success(data)
    }
    .catch { e ->
        emit(UiState.Error(e.message ?: "unknown error"))
    }
    .flowOn(Dispatchers.IO)
    .collect { state ->
        render(state)
    }

搜索框常见:

queryFlow
    .debounce(300)
    .distinctUntilChanged()
    .flatMapLatest { query ->
        searchRepository.search(query)
    }
    .catch {
        emit(emptyList())
    }
    .collect {
        showSearchResult(it)
    }

多个状态组合常见:

combine(userFlow, messageFlow, networkFlow) { user, message, network ->
    UiState(user, message, network)
}.collect {
    render(it)
}

最后总结

不用记所有 Flow 操作符。

优先记这几个核心:

map:转换
filter:过滤
onEach:副作用
flowOn:切线程
catch:异常
collect:收集

merge:混合多个流
combine:组合最新状态
zip:一一配对

flatMapConcat:顺序任务
flatMapMerge:并发任务
flatMapLatest:最新任务

把这几个吃透,Flow 的大部分业务场景就够用了。