[正確]的使用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()方法限流)。

相關文章
相關標籤/搜索