美文网首页程序员
[正确]的使用Kotlin Flow进行搜索优化

[正确]的使用Kotlin Flow进行搜索优化

作者: Android阿南 | 来源:发表于2021-02-05 09:44 被阅读0次

    引言

    用户搜索时,为了避免产生无意义的搜索请求,通常会进行搜索数据限流。熟悉RxJava的同学,一定会知道怎么做,各种天花乱坠的操作符让你眼花缭乱。

    那么用上了kotlin的小伙伴,完全可以不必使用RxJava,因为kotlin中自带的Flow就可以做到。不废话,直接开始。

    激起我写作的原因是这一篇文章Kotlin Flow 开发应用实践之搜索优化,这篇文章错误的使用了Flow,目前作者已经对文章内部的错误进行了更改,但是我还是想借机来说明一下。随后给出正确的解释及正确的代码。

    错在了哪

    我先把那篇文章中错误的代码贴上来:

    // 错误代码🙅
    
    binding.etSearch.doOnTextChanged { text, _, _, _ ->
        searchFilter(text.toString())
    }
    
    private fun searchFilter(str:String){
        flow { emit(str) }
           .debounce(400)
           .filter {
                it.isNotEmpty()
            }
            .catch { LogUtils.d(it.message) }
            .flowOn(Dispatchers.Default)
            .onEach {
                LogUtils.d("输出:$it")
                binding.tvShow.text = it.toString()
            }.flowOn(Dispatchers.Main)
            .launchIn(lifecycleScope)
        }
    复制代码
    

    仔细看上面的代码,你们自己想想错在了哪里?

    文本输入框etSearch每次文本的变化都会回掉searchFilter()方法,而方法里面每次都去实例化了一个flow,然后又使用了debounce()限流,那么这个debounce()限流的意义何在?毫无意义啊,对吧。因为每次都是新创建的flow啊。

    还不懂的话,那就看下面简化的代码:

    // 文章错误代码🙅的简化
    
    for (i in 0..100) {
    // 模拟生成数据
    
        flow<Int> {
            emit(i)
        }.debounce(500) // 这里是无效的 限流,因为 flow 的 emit 只执行了一次啊……
         .collect {
              println("----------------->>> $it")
         }
    }
    
    复制代码
    

    那么我们理想中的正确代码逻辑是这样的:

    flow<Int> {
        for (i in 0..100) {
        // 模拟生成数据
    
            emit(i)
        }
    }.debounce(500) // 这里是有效的 限流
     .collect {
          println("----------------->>> $it")
      }
    复制代码
    

    好了,同学们可以再对比以上两段代码,for循环就是我们模拟的输入数据,这个for循环在里面和在外面是两个完全不一样的逻辑~不再多做解释了

    正确的用法

    你以为的正确写法

    对于输入框的这类业务逻辑,单纯使用flow是无法到达目的的,因为写不出来。有的小朋友要站起来高喊了,“怎么写不出来,你瞎说,我来写”,一顿操作写出了下面的代码:

    // 小朋友写的错误代码🙅
    
    flow<Editable> {
    
        editText.doAfterTextChanged { text ->
            emit(text) // 这里是错误的,emit不可以写在内部类中
        }
    }.debounce(500)
      .collect {
          println("----------------->>> $it")
       }
    
    复制代码
    

    错误的地方我写上了注释,emit是一个suspend挂起函数,是不可以写在内部类里的,代码直接编译不通过。

    真正的正确写法

    首先我提一个知识点,大家回忆一下。RxJava中的流,是分为冷流热流(即:cold Observable和 hot Observable)对吧。如果你说啥,流还分冷热?亲,那这里建议你炒个回锅肉呢。

    RxJava的使用中,不注意区分冷热流,是导致RxJava错用、滥用的原因之一!

    这里我只用两句话简单解释冷热流,不展开讲RxJava

    • 冷流:只有观察者进行订阅了,上游才开始执行发射数据

    • 热流:无论有没有观察者,上游的数据都会发射

    flow是冷流

    直接用注释说明:

    flow {
        // 发射数据
    }.collect {
        /*
        只有执行了 collect 或者 collectLast 订阅了流,
        上游 flow 里面的代码块才会执行!
        */
    }
    复制代码
    

    现在这位小朋友,冷静一下,想想输入框的业务场景是什么样子的?即:不管有没有订阅者,只要EditText文本变化了,都会发送数据。那我们就应该用热流来解决问题。

    StateFlow热流登场

    各位大佬直接看代码吧:

    // 定义一个全局的 StateFlow
    private val _etState = MutableStateFlow("")
    
    override fun onCreate(savedInstanceState: Bundle?) {
        et.doAfterTextChanged { text ->
            // 往流里写数据
            _etState.value = (text ?: "").toString()
        }
    
        lifecycleScope.launch {
            _etState
            .sample(500) // 限流,500毫秒
            .filter {
                // 空文本过滤掉
                it.isNotBlank()
            }.collect {
                // 订阅数据
                println("----------------->>> $it")
            }
        }
    }
    
    复制代码
    

    好了,收工了。对于 StateFlow ,Google是这么解释的:“与使用 flow 构建器构建的冷数据流不同,StateFlow 是热数据流:从数据流收集数据不会触发任何提供方代码。StateFlow 始终处于活跃状态并存于内存中,而且只有在垃圾回收根中未涉及对它的其他引用时,它才符合垃圾回收条件。”

    文档链接:StateFlow 和 SharedFlow

    课后作业

    最后的代码中,我是用了sample()方法来作为限流,而没有使用debounce()方法限流。那么这两种限流的方法有什么区别呢?有兴趣的同学自己在代码里尝试一下吧,会得出自己的结论的。

    (对于此场景,我更倾向于sample()方法限流)。

    作者:limuyang2
    链接:https://juejin.cn/post/6925304772383735822

    相关文章

      网友评论

        本文标题:[正确]的使用Kotlin Flow进行搜索优化

        本文链接:https://www.haomeiwen.com/subject/rbjwtltx.html