web-dev-qa-db-fra.com

Kotlin Android debounce

Existe-t-il un moyen sophistiqué d'implémenter la logique debounce avec Kotlin Android?

Je n'utilise pas Rx dans le projet.

Il y a un moyen dans Java , mais c'est trop grand pour moi ici.

10
Kirill Zotov

Vous pouvez utiliser coroutines kotlin pour y parvenir. Voici un exemple .

Sachez que les coroutines sont expérimentales sur kotlin 1.1 + et qu'elles peuvent être modifiées dans les prochaines versions de kotlin.

MISE À JOUR

Depuis Kotlin 1. release, coroutines sont maintenant stables.

6
Diego Malone

Merci à https://medium.com/@pro100svitlo/edittext-debounce-with-kotlin-coroutines-fd134d54f4e9 et https://stackoverflow.com/a/50007453/291414 J'ai écrit ce code:

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()
}

Je n'ai pas inclus coroutineContext ici, donc cela ne fonctionnera probablement pas, s'il n'est pas défini. Pour plus d'informations, voir Migrer vers les coroutines Kotlin dans Android avec Kotlin 1. .

8
CoolMind

Une solution plus simple et générique consiste à utiliser une fonction qui renvoie une fonction qui fait la logique anti-rebond et à la stocker dans une 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)
            }
        }
    }
}

Maintenant, il peut être utilisé avec:

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

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

J'ai créé un Gist avec trois opérateurs anti-rebond inspirés de cette élégante solution de Patrick où j'ai ajouté deux autres cas similaires: throttleFirst et throttleLatest. Ces deux sont très similaires à leurs analogues RxJava ( throttleFirst , throttleLatest ).

throttleLatest fonctionne de manière similaire à debounce mais il fonctionne sur des intervalles de temps et renvoie les dernières données pour chacun, ce qui vous permet d'obtenir et de traiter des données intermédiaires si vous en avez besoin.

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 est utile lorsque vous devez traiter le premier appel immédiatement, puis ignorer les appels suivants pendant un certain temps pour éviter un comportement indésirable (évitez de démarrer deux activités identiques sur Android, par exemple).

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 aide à détecter l'état lorsqu'aucune nouvelle donnée n'est soumise pendant un certain temps, vous permettant efficacement de traiter une donnée lorsque la saisie est terminée.

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)
        }
    }
}

Tous ces opérateurs peuvent être utilisés comme suit:

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

J'ai créé une seule fonction d'extension à partir des anciennes réponses de débordement de pile:

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()
        }
    })
}

Affichez onClick à l'aide du code ci-dessous:

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