创建操作符
flow
创建Flow的基本方法.
使用 emit 发射单个值
使用 emitAll 发射一个流 ,类似list.addAll(anotherList)
lifecycleScope.launch {
flow<Int> {
emit(1)
emitAll(flowOf(2,3))
}.collect{
println("collect:$it") // 1 2 3
}
}
flowOf
快速创建 flow
,类比 listOf()
lifecycleScope.launch {
flowOf(1,2,3).collect{
println("collect:$it") // 1 2 3
}
}
asFlow
将其他数据转换成 普通的flow
,一般是集合向Flow
的转换
lifecycleScope.launch {
listOf(1,2,3).asFlow().collect{
println("collect:$it") // 1 2 3
}
}
callbackFlow
将回调方法改造成flow
/**
* 点击防抖
* @receiver View
* @param thresholdMillis Long 防抖间隔,默认500ms
* @param dispatcher CoroutineDispatcher 事件执行线程,默认主线程
* @param scope CoroutineScope 作用域
* @param block Function0<Unit> 执行体
* @return Job
*/
@OptIn(FlowPreview::class)
@JvmOverloads
inline fun View.clickFlow(
thresholdMillis: Long = 500L,
dispatcher: CoroutineDispatcher = Dispatchers.Main,
scope: CoroutineScope,
crossinline block: () -> Unit,
) = callbackFlow {
setOnClickListener { trySend(Unit) }
awaitClose { setOnClickListener(null) }
}.throttleFirst(thresholdMillis)
.onEach { block() }
.flowOn(dispatcher)
.launchIn(scope)
@FlowPreview
fun <T> Flow<T>.throttleFirst(thresholdMillis: Long): Flow<T> = flow {
var lastTime = 0L
collect {
val currentTime = System.currentTimeMillis()
if (currentTime - lastTime > thresholdMillis) {
lastTime = currentTime
emit(it)
}
}
}
末端操作符
collect
触发flow的运行 。 通常的监听方式
lifecycleScope.launch {
flowOf(1,2,3).collect{
println("collect:$it") // 1 2 3
}
}
collectIndexed
带下标的收集操作
lifecycleScope.launch {
flowOf(1,2,3).collectIndexed { index, value ->
println("collect:$index $value") // (0,1),(1,2),(2,3)
}
}
collectLatest
有新值发出时,如果此时上个收集尚未完成,则会取消掉上个值的收集操作。只想要最新的数据,中间值可以丢弃时可以使用此方式
lifecycleScope.launch {
flow<Int> {
emit(1)
delay(100)
emit(2)
}.collectLatest {
delay(200)
println("collect:$it") // 2
}
}
launchIn
直接触发流的执行,入参为coroutineScope
。一般不会直接调用,会搭配别的操作符一起使用,如onEach
,onCompletion
。返回值是Job
flow<Int> {
emit(1)
}.onEach {
println("collect:$it") // 1
}.launchIn(lifecycleScope)
toList
将结果转换为List
lifecycleScope.launch {
flow<Int> {
emit(1)
emit(2)
emit(3)
}.toList().forEach {
println("collect:$it")
}
}
回调操作符
onStart
在上游流开始之前被调用。 可以发出额外元素,也可以处理其他事情,比如发埋点,showloading
onCompletion
在流取消或者结束时调用。可以执行发送元素,发埋点等操作
onEmpty
当流完成却没有发出任何元素时回调。 可以用来兜底
onEach
在上游向下游发出元素之前调用,一般与launchIn
搭配使用
lifecycleScope.launch {
flowOf(1, 2, 3)
.onStart { println("onStart") }
.onCompletion { println("onCompletion") }
.onEmpty { println("onEmpty") }
.onEach { println("onEach:$it") }
.collect {
println("collect:$it")
}
}
输出:
onStart
onEach:1
collect:1
onEach:2
collect:2
onEach:3
collect:3
onCompletion
变换操作符
map
将发出的值 进行变换 ,lambda
的返回值为最终发送的值
lifecycleScope.launch {
flowOf(1, 2, 3)
.map {
"a$it"
}
.collect {
println("collect:$it") // a1 a2 a3
}
}
mapLatest
类比 collectLatest
,当有新值发送时如果上个变换还没结束,会先取消掉
lifecycleScope.launch {
flow<Int> {
emit(1)
delay(100)
emit(2)
}.mapLatest {
delay(200)
"a$it"
}
.collect {
println("collect:$it") // a2
}
}
mapNotNull
仅发送 map
后不为空的值
lifecycleScope.launch {
flow<Int> {
emit(1)
emit(2)
}.mapNotNull {
if (it == 1) null else "a$it"
}
.collect {
println("collect:$it") // a2
}
}
flatMapConcat
transform
对发出的值进行变换 。区别于map
, transform
的接收者是FlowCollector
,因此它非常灵活,可以变换、跳过它或多次发送。必须调用emit()
进行发送
lifecycleScope.launch {
flow<Int> {
emit(1)
emit(2)
}
.transform<Int, String> {
emit("a$it")
}
.collect {
println("collect:$it") // a1 a2
}
}
transformLatest
类比mapLatest
,当有新值发送时如果上个变换还没结束,会先取消掉
lifecycleScope.launch {
flow<Int> {
emit(1)
delay(100)
emit(2)
}
.transformLatest<Int, String> {
delay(200)
emit("a$it")
}
.collect {
println("collect:$it") // a2
}
}
transformWhile
这个变换的lambda
返回值是 Boolean
,如果为 false
则不再进行后续变换, 为 true
则继续执行
asStateFlow
将 MutableStateFlow
转换为 StateFlow
,就是变成不可变的。常用在对外暴露属性时使用
private val _uiState = MutableStateFlow<UIState>(Loading)
val uiState = _uiState.asStateFlow()
asSharedFlow
将 MutableSharedFlow
转换为 SharedFlow
,就是变成不可变的。常用在对外暴露属性时使用
private val _uiState = MutableSharedFlow()
val uiState = _uiState.asSharedFlow()
stateIn
将普通flow
转化为 StateFlow
三个参数:
scope
- 开始共享的协程范围
started
- 控制何时开始和停止共享的策略
initialValue
- 状态流的初始值
lifecycleScope.launch {
flow<Int> {
emit(1)
emit(2)
}.stateIn(lifecycleScope)
}
shareIn
将普通flow
转化为 SharedFlow
三个参数:
scope
- 开始共享的协程范围
started
- 控制何时开始和停止共享的策略
replay
- 发给新的订阅者的旧值数量
其中
started
有一些可选项:
Eagerly
: 共享立即开始,永不停止
Lazily
: 当第一个订阅者出现时,永不停止
WhileSubscribed
: 在第一个订阅者出现时开始共享,在最后一个订阅者消失时立即停止(默认情况下),永久保留重播缓存(默认情况下)
WhileSubscribed
具有以下可选参数:
stopTimeoutMillis
— 配置最后一个订阅者消失到协程停止共享之间的延迟(以毫秒为单位)。 默认为零(立即停止)。
replayExpirationMillis
- 共享的协程从停止到重新激活,这期间缓存的时效
lifecycleScope.launch {
flow<Int> {
emit(1)
emit(2)
}.shareIn(lifecycleScope, SharingStarted.Eagerly)
}
过滤操作符
filter
筛选出符合条件的值
flow {
emit("a")
emit("b")
}.filter { value ->
value == "a"
}.collect { value->
println(value) // a
}
filterNot
筛选不符合条件相反的值,相当于filter
取反
flow {
emit("a")
emit("b")
}.filterNot { it == "a" } .collect { value ->
println(value) // b
}
filterNotNull
筛选不为空的值
flow {
emit("a")
emit(null)
emit("b")
}.filterNotNull().collect { value->
println(value) // a b
}
filterIsInstance
筛选对应类型的值
flow {
emit("a")
emit("b")
emit(1)
}.filterIsInstance<String>().collect { value->
println(value) // a b
}
drop
入参count
为int
类型,作用是丢弃掉前 n 个的值
flow {
emit(1)
emit(2)
emit(3)
}.drop(2).collect { value ->
println(value) // 3
}
dropWhile
找到第一个不满足条件的,返回其和其之后的值。如果首项就不满足条件,则是全部返回。
flow {
emit(3)
emit(1) //从此项开始不满足条件
emit(2)
emit(4)
}. dropWhile { it == 3 } .collect { value ->
println(value) 1 2 4
}
flow {
emit(1) //从首项开始就不满足条件
emit(2)
emit(3)
emit(4)
}. dropWhile { it == 3 } .collect { value ->
println(value) // 1 2 3 4
}
take
返回前 n个 元素
flow {
emit(1)
emit(2)
emit(3)
} .take(2) .collect { value ->
println(value) // 1 2
}
takeWhile
找第一个不满足条件的项,但是取其之前的值 ,和dropWhile
相反。如果首项就不满足,则为空流
flow {
emit(1)
emit(2)
emit(3) //从此项开始不满足条件
emit(4)
} .takeWhile { it <3 } .collect { value ->
println(value) // 1 2
}
flow {
emit(3) //从此项开始不满足条件
emit(1)
emit(2)
emit(4)
} .takeWhile { it <3 } .onEmpty {
print( "empty")
}.collect { value ->
println(value) // empty
}
debounce
连续发射数据时,如果每两两发射数据时间间隔小于设置的时间,则每发射一次数据都会重置倒计时,只有操作停止,不再发射数据,当倒计时结束,会收到最后一条数据。如果倒计时结束还没有新的数据发射,则该条数据也会被收到。可用于搜索联想场景
/**
* 输入框限流(搜索联想场景适用)
* @receiver EditText
* @param timeoutMillis Long 指定时间内的值只接收最新的一个,其他的过滤掉,默认400ms
* @param scope CoroutineScope 作用域
* @param textChange Function1<String, Unit> 文本改变状态,包含空状态,适用于输入框清空按钮显示逻辑
* @param fetch Function1<String, Flow<T>> 当文本不为空该处理的逻辑,如网络请求进行搜索联想
* @param result Function1<T, Unit> fetch执行后的结果
* @return Job
*/
@JvmOverloads
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
inline fun <T> EditText.textChangeFlow(
timeoutMillis: Long = 400L,
scope: CoroutineScope,
crossinline textChange: (String) -> Unit = {},
crossinline fetch: (String) -> Flow<T>,
crossinline result: (T) -> Unit,
) = callbackFlow<String> {
val watcher = object : TextWatcher by noOpDelegate() {
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
s?.let { trySend(it.toString()) }
}
}
addTextChangedListener(watcher)
awaitClose { removeTextChangedListener(watcher) }
}.debounce(timeoutMillis)
.onEach { textChange(it) }
.flowOn(Dispatchers.Main)
.filter { it.isNotEmpty() }
.flatMapLatest { fetch(it) }
.flowOn(Dispatchers.IO)
.onEach { result(it) }
.launchIn(scope)
使用
binding.et.textChangeFlow(
scope = lifecycleScope,
fetch = {
//模拟网络请求加载数据并发射
flow { emit(1) }
},
result = {
}
)
sample
区别于debounce
,sample
倒计时在时间轴上是固定的,发射数据不会影响到计时器,每次倒计时结束都会收到该倒计时时间段内的最后一条数据。可用于大量请求场景限流,防过度刷新,如每秒获取一条弹幕场景
lifecycleScope.launch {
flow {
while (true) {
emit("发送一条弹幕")
}
}
.sample(1000)
.flowOn(Dispatchers.IO)
.collect {
println(it)
}
}
结论:
每秒钟只会打印出一条弹幕
distinctUntilChangedBy
判断连续的两个值是否重复,keySelector: (T) -> Any?
,指定用来比较的 key
。(有点类似 Recyclerview 的 DiffUtil 机制)
flowOf(
Funny(name = "Tom", age = 8),
Funny(name = "Tom", age = 12),
Funny(name = "Tom", age = 12)
).distinctUntilChangedBy { it.name } .collect { value ->
println(value.toString()) // Funny(name=Tom, age=8)
}
distinctUntilChanged
distinctUntilChangedBy
的简化调用 。连续两个值一样,则跳过发送
flowOf(1, 1, 3 , 1)
.distinctUntilChanged()
.collect { value ->
println(value) // 1 3 1
}