创建Flow:三种方式,一个选择
创建Flow这件事,说起来很简单,但实际项目里选哪种方式,还是有点讲究的。我个人习惯把Flow的创建分为三类:全程手动控制的 flow builder、一把梭的 flowOf、以及无脑转换的 asFlow。
你想想看,我们写异步代码的时候,数据源可能是网络、数据库、或者用户输入。怎么把这些数据塞进Flow呢?下面我给你拆开揉碎了讲。
1. flow builder:最自由的姿势
flow { ... } 这个构建器,是我用得最多的。它可以让你在协程环境下,手动发射(emit)任意数量的值。你需要的就是在 lambda 里调用 emit() 方法。
举个例子,假如你要监听一个数据库游标的变化,或者轮询一个网络状态,用 flow builder 就可以精确控制何时发数据、发多少数据。
val sensorFlow: Flow<Int> = flow {
for (i in 1..5) {
delay(100) // 模拟传输延迟
emit(i * 10) // 发送一个数据
}
}
这里有个细节,flow builder 是冷流。什么意思?就是没人在末端 collect,它根本就不跑。我在项目中遇到过有人把 flow builder 当热流用,结果一直不执行,查了半天才发现是没人 collect。
- 可以发射任意数量元素
- 内部支持挂起函数(比如 delay、网络请求)
- 天然支持异常处理(try-catch 包裹 emit)
2. flowOf:极简主义者专用
说白了,flowOf 就是给你一个固定值的 Flow。你传几个参数进去,它就发射几个值。
val nameFlow: Flow<String> = flowOf("Kotlin", "Flow", "Async")
这段代码会依次发射 "Kotlin"、"Flow"、"Async" 三个字符串,然后自动结束。它本质上就是对 flow builder 的一个语法糖包装。
什么时候用? 我一般在单元测试里大量使用 flowOf。比如写一个方法返回 Flow,但在测试里我不想真的走网络,直接 flowOf(mockResult) 搞定。
另外还有一个场景,就是在静态配置或者常量数据里。比如 App 的版本号列表,或者一些枚举配置,用 flowOf 非常干净。
3. asFlow:万物皆可 Flow
这个扩展函数可以让现有的集合、Sequence、甚至某些特定类型,快速转换成 Flow。最常用的就是 .asFlow() 放在 List 或者 Set 后面。
val intList = listOf(1, 2, 3, 4, 5)
val flowFromList: Flow<Int> = intList.asFlow()
这段代码会依次发射 1, 2, 3, 4, 5。效果与 flowOf 类似,但它更灵活——你可以基于已有的集合数据来创建 Flow。
我记得有一次在调试一个 ViewPager 的分页加载时,后台返回的是一个 List,我需要把它转成 Flow 然后做前端缓存合并。直接用 .asFlow() 就完成了,不用再写一个额外的 flow builder,代码量少了一半。
除了集合,Sequence 也可以用 asFlow:
val seq = sequence {
yield("A"); yield("B"); yield("C")
}
val flowFromSeq = seq.asFlow()
三种方式对比
| 方式 | 适用场景 | 数据量 | 可否挂起 |
|---|---|---|---|
flow { ... } |
动态、异步、可变数据 | 无限/有限 | ✔ 是 |
flowOf(...) |
固定值、测试数据、常量 | 有限 | ✘ 否 |
.asFlow() |
集合/序列转Flow、数据迁移 | 有限 | ✘ 否(视来源) |
选型原则:一句话总结
如果你要控制每个数据的发射时机,或者想在里面做挂起操作,就用 flow builder。其他情况,只要数据是现成的有限集合,flowOf 或者 asFlow 会更简洁。嗯,其实没有的对错,关键看你在哪个上下文中写代码。
- 单元测试里:我用 flowOf 构造预期结果,简单明了。
- 网络库封装:一定用 flow builder,因为要处理 retry、timeout。
- 数据库 DAO 返回 List:我会用 .asFlow() 快速过渡,如果后续需要加合并逻辑,再考虑改成 flow builder。
三种创建方式,说白了就是三条腿走路。你掌握了它们,后续学 Flow 的各种操作符(map、filter、flatMapMerge 等等)才能跑得稳。我记得有的初学者容易在这上面纠结,其实你随便选一种开始写,写着写着自然就知道该换哪种了。