Android 利用 Kotlin callbackFlow优雅

2022-08-26  本文已影响0人  云飞扬1

1. 需求场景

几乎大部分人都使用过下面的场景:使用百度等搜索引擎时,输入一个字符后会自动联想出相关的搜索关键字;在淘宝、京东等APP搜索商品时,会自动帮你联想出相关的商品。这在很大程度上提高了用户体验,快速引导用户到达搜索结果页。

所以我们总结出同类型的需求,有一个搜索输入框,用户可以随意输入任何字符,然后前端根据用户的输入给出联想提示信息。

2. 实现方案

一般前端实现起来也不复杂,前端监听输入框文本的变化,然后根据输入内容从本地或者调用接口从服务端查询相关联数据,然后在前端展示结果即可。虽然看起来很简单,但是要比较完美地实现,还是有很多需要考虑的:

  1. 用户输入文本的速度是随机的,如果监听文本变化之后立马去查询,可能会过多地发起查询,浪费系统资源。例如用户本意要输入"abcde",那么每输入一个字符都会触发查询,会触发 5 次,其实用户可能只想得到 "abcde" 的查询结果;
  2. 查询是异步的,异步返回的时机是不确定的,可能造成当前返回的异步查询结果与当前输入框里的文本不一致。例如用户输入"ab",先后触发关键字 "a"、"ab" 的查询,结果 "a" 的查询结果后返回,最后导致显示的是 "a" 的查询结果;

所以一个优雅的方案我觉得应该包含:

  1. 防抖限流;
  2. 自动取消无效查询;

以前要实现这些效果,监听输入框变化时,会通过计时以及延时任务等,在限定时间段内只触发一次,写起来也很麻烦。

3. 使用 Kotlin callbackFlow

Kotlin Flow 支持防抖、背压等特性,利用 callbackFlow 可以将输入框的文本变化包装成一个 Flow,然后来实现这个需求。

将 EditText 的输入文本变化包装成 Flow:

private fun textChangeFlow(editText: EditText): Flow<String>  {
    return callbackFlow {
        val watcher = object : TextWatcher {
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
            }

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            }

            override fun afterTextChanged(s: Editable?) {
                s?.let {
                    trySend(s.toString())
                }
            }
        }
        editText.addTextChangedListener(watcher)
        //在 flow 被 close 时调用,可以清理资源,一般必须要有
        awaitClose {
            editText.removeTextChangedListener(watcher)
        }
    }
}

根据输入框文本内容,我们去查询相关信息:

fun testCallbackFlow(editText: EditText) {
    viewModelScope.launch {
        textChangeFlow(editText)
            .debounce(300)  //防抖处理,间隔 300ms 响应一次
            .flatMapLatest { keyword ->     //flatMapLatest 操作符,只处理最新的关键字,并且老的查询如果没有完成会自动 cancel 掉
                println("keyword = $keyword")
                flow { 
                    //根据输入关键字查询信息
                    var result = queryByKeyword(keyword)
                    emit(result)
                }
            }.catch {
                println("exception: $it")
            }
            .collect {
                //查询到相关结果
            }
    }
}

如上代码所示,可以很优雅地实现我们想要的效果。

上一篇下一篇

猜你喜欢

热点阅读