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

忙里偷闲,写个小文章,一直拖更了一年的【思货】协程网络请求的最终章等过年再写吧android

引言

用户搜索时,为了不产生无心义的搜索请求,一般会进行搜索数据限流。熟悉RxJava的同窗,必定会知道怎么作,各类天花乱坠的操做符让你眼花缭乱。markdown

那么用上了kotlin的小伙伴,彻底能够没必要使用RxJava,由于kotlin中自带的Flow就能够作到。不废话,直接开始。网络

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

错在了哪

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

// 错误代码🙅

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)
    }
复制代码

仔细看上面的代码,大家本身想一想错在了哪里?oop

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

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

// 文章错误代码🙅的简化

for (i in 0..100) {
// 模拟生成数据

    flow<Int> {
        emit(i)
    }.debounce(500) // 这里是无效的 限流,由于 flow 的 emit 只执行了一次啊……
     .collect {
          println("----------------->>> $it")
     }
}

复制代码

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

flow<Int> {
    for (i in 0..100) {
    // 模拟生成数据

        emit(i)
    }
}.debounce(500) // 这里是有效的 限流
 .collect {
      println("----------------->>> $it")
  }
复制代码

好了,同窗们能够再对比以上两段代码,for循环就是咱们模拟的输入数据,这个for循环在里面和在外面是两个彻底不同的逻辑~再也不多作解释了spa

正确的用法

你觉得的正确写法

对于输入框的这类业务逻辑,单纯使用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()方法限流)。

相关文章
相关标签/搜索