web-dev-qa-db-ja.com

Kotlin Androidデバウンス

Kotlin Androidでdebounceロジックを実装するための素晴らしい方法はありますか?

プロジェクトでRxを使用していません。

Java には方法がありますが、ここでは私にとっては大きすぎます。

10
Kirill Zotov

kotlinコルーチンを使用してそれを達成できます。 ここに例があります

coroutineskotlin 1.1+で実験的 であり、今後のkotlinバージョンでは変更される可能性があることに注意してください。

更新

Kotlin 1. リリースなので、コルーチンは安定しています。

6
Diego Malone

https://medium.com/@pro100svitlo/edittext-debounce-with-kotlin-coroutines-fd134d54f4e9 および https://stackoverflow.com/に感謝します。 a/50007453/2914140 私はこのコードを書きました:

private var textChangedJob: Job? = null
private lateinit var textListener: TextWatcher

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View? {

    textListener = object : TextWatcher {
        private var searchFor = "" // Or view.editText.text.toString()

        override fun afterTextChanged(s: Editable?) {}

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            val searchText = s.toString().trim()
            if (searchText != searchFor) {
                searchFor = searchText

                textChangedJob?.cancel()
                textChangedJob = launch(Dispatchers.Main) {
                    delay(500L)
                    if (searchText == searchFor) {
                        loadList(searchText)
                    }
                }
            }
        }
    }
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    editText.setText("")
    loadList("")
}


override fun onResume() {
    super.onResume()
    editText.addTextChangedListener(textListener)
}

override fun onPause() {
    editText.removeTextChangedListener(textListener)
    super.onPause()
}


override fun onDestroy() {
    textChangedJob?.cancel()
    super.onDestroy()
}

ここにcoroutineContextを含めなかったので、設定しないとおそらく動作しません。詳細については、 Android with Kotlin 1.3 )のKotlinコルーチンへの移行を参照してください。

8
CoolMind

より単純で一般的な解決策は、デバウンスロジックを実行する関数を返す関数を使用し、それをvalに格納することです。

fun <T> debounce(delayMs: Long = 500L,
                   coroutineContext: CoroutineContext,
                   f: (T) -> Unit): (T) -> Unit {
    var debounceJob: Job? = null
    return { param: T ->
        if (debounceJob?.isCompleted != false) {
            debounceJob = CoroutineScope(coroutineContext).launch {
                delay(delayMs)
                f(param)
            }
        }
    }
}

現在、以下で使用できます。

val handleClickEventsDebounced = debounce<Unit>(500, coroutineContext) {
    doStuff()
}

fun initViews() {
   myButton.setOnClickListener { handleClickEventsDebounced(Unit) }
}
6
Patrick

このエレガントなソリューション from Patrick に触発された3つのデバウンス演算子で Gist を作成しました。ここで、さらに2つの類似したケースを追加しました:throttleFirstおよびthrottleLatest。これらは両方とも、RxJavaの類似物( throttleFirstthrottleLatest )と非常によく似ています。

throttleLatestdebounceと同様に機能しますが、時間間隔で動作し、それぞれの最新データを返します。これにより、必要に応じて中間データを取得および処理できます。

fun <T> throttleLatest(
    intervalMs: Long = 300L,
    coroutineScope: CoroutineScope,
    destinationFunction: (T) -> Unit
): (T) -> Unit {
    var throttleJob: Job? = null
    var latestParam: T
    return { param: T ->
        latestParam = param
        if (throttleJob?.isCompleted != false) {
            throttleJob = coroutineScope.launch {
                delay(intervalMs)
                latestParam.let(destinationFunction)
            }
        }
    }
}

throttleFirstは、最初の呼び出しをすぐに処理し、その後の呼び出しをしばらくスキップして望ましくない動作を避ける必要がある場合に便利です(たとえば、Androidで2つの同一のアクティビティを開始しないでください)。

fun <T> throttleFirst(
    skipMs: Long = 300L,
    coroutineScope: CoroutineScope,
    destinationFunction: (T) -> Unit
): (T) -> Unit {
    var throttleJob: Job? = null
    return { param: T ->
        if (throttleJob?.isCompleted != false) {
            throttleJob = coroutineScope.launch {
                destinationFunction(param)
                delay(skipMs)
            }
        }
    }
}

debounceは、新しいデータがしばらく送信されていない状態を検出するのに役立ち、入力が完了したときにデータを効果的に処理できます。

fun <T> debounce(
    waitMs: Long = 300L,
    coroutineScope: CoroutineScope,
    destinationFunction: (T) -> Unit
): (T) -> Unit {
    var debounceJob: Job? = null
    return { param: T ->
        debounceJob?.cancel()
        debounceJob = coroutineScope.launch {
            delay(waitMs)
            destinationFunction(param)
        }
    }
}

これらの演算子はすべて、次のように使用できます。

val onEmailChange: (String) -> Unit = throttleLatest(
            300L, 
            viewLifecycleOwner.lifecycleScope, 
            viewModel::onEmailChanged
        )
emailView.onTextChanged(onEmailChange)
3
Terenfear

スタックオーバーフローの古い回答から単一の拡張関数を作成しました。

fun View.clickWithDebounce(debounceTime: Long = 600L, action: () -> Unit) {
    this.setOnClickListener(object : View.OnClickListener {
        private var lastClickTime: Long = 0

        override fun onClick(v: View) {
            if (SystemClock.elapsedRealtime() - lastClickTime < debounceTime) return
            else action()

            lastClickTime = SystemClock.elapsedRealtime()
        }
    })
}

以下のコードを使用してonClickを表示します。

buttonShare.clickWithDebounce { 
   // Do anything you want
}
1
SANAT