flow操作符

145 阅读5分钟

创建操作符

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

入参countint类型,作用是丢弃掉前 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

连续发射数据时,如果每两两发射数据时间间隔小于设置的时间,则每发射一次数据都会重置倒计时,只有操作停止,不再发射数据,当倒计时结束,会收到最后一条数据。如果倒计时结束还没有新的数据发射,则该条数据也会被收到。可用于搜索联想场景

16873408298058.png

/**
 * 输入框限流(搜索联想场景适用)
 * @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

区别于debouncesample倒计时在时间轴上是固定的,发射数据不会影响到计时器,每次倒计时结束都会收到该倒计时时间段内的最后一条数据。可用于大量请求场景限流,防过度刷新,如每秒获取一条弹幕场景

1687341492395.png

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
    }