Android i Kotlin. Prosta blokada wieloklików. (gotowy kod do użycia).

Pokażę Ci prosty sposób na zablokowanie wielkokrotnych kliknięć użytkownika w element UI na platformie Android.

Android i Kotlin. Prosta blokada wieloklików. (gotowy kod do użycia).

Jeśli jesteś programistą Android to mam coś dla Ciebie. W tym wpisie przedstawię Ci prostą klasę pomocniczą, dzięki której pozbędziesz się problemu, który spędza sen z powiek wielu programistom. Mowa tu o szybkim klikaniu przez użytkowników w dany element interfejsu użytkownika, co  w konsekwencji może wywołać akcję wielokrotnie np. nawigacja do jakiegoś ekranu w aplikacji.

Temat ten już poruszałem na blogu, ale rozwiązanie, które zaproponowałem, bazuje na RxKotlin. Możesz przeczytać o tym tutaj:

Rozwiązanie

Poniżej przedstawiam Ci gotowe rozwiązanie do użycia.

class ActionDebouncer {
    private val minimumIntervalMillis: Long = 1000
    private var previousClickTimestamp: Long = 0

    fun <T> onAction(defaultValue: T, listener: (() -> T)): T {
        val currentTimestamp = SystemClock.uptimeMillis()
        val isSuccessed =
            abs(currentTimestamp - previousClickTimestamp) > minimumIntervalMillis

        previousClickTimestamp = currentTimestamp
        if (isSuccessed)
            return listener()

        return defaultValue
    }
}

Kod jest bardzo prosty. Metoda sprawdza, czy od jej ostatniego wywołania minęła odpowiednia ilość czasu, na tej podstawie wywołuje funkcję anonimową lub nie. Dodatkowo mamy możliwość zwrócenia dowolnego typu oraz podanie jego wartości domyślnej, w przypadku niewywołania funkcji anonimowej.

W jaki sposób możesz z tego skorzystać np. w kontekście przycisków? Najłatwiej jest stworzyć swoją kontrolkę opartą np. na MaterialButton. To pozwoli Ci na zablokowanie wieloklików w całej aplikacji. Wystarczy, że będziesz wykorzystywał tak przygotowany przycisk podczas budowy widoków w swojej aplikacji Android.

class MyMaterialButton : MaterialButton {

    private val actionDebouncer = ActionDebouncer()

    constructor(context: Context) : super(context) {
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ) {
    }

    override fun performClick(): Boolean {
        return actionDebouncer.onAction(false) {
            super.performClick()
        }
    }
}
Common buttons – Material Design 3
Buttons help people take action, such as sending an email, sharing a document, or liking a comment.

Zwróć uwagę, że bazowe wywołanie metody performClick() znajduję się wewnątrz anonimowej funkcji. Wywoła się ona tylko wtedy kiedy od ostatniego kliknięcia minęło więcej czasu niż ten ustalony  w klasie ActionDebouncer. Kiedy akcja się nie powiedzie (użytkownik kliknął kilkukrotnie) zostanie zwrócony false (domyślna wartość). Co w kontekście przycisku oznacza, że kilknięcię się nie powiodło.

Jak będzie wyglądało użycie tej klasy w najprostszym przypadku np. w jednym konkretnym widoku? Dokładnie tak samo. Spójrz na kod poniżej.

private val actionDebouncer = ActionDebouncer()

private fun prepareView() {
    loginButton.setOnClickListener {
        actionDebouncer.onAction(Unit) {
            //Any action
        }
    }
}
thumbs brent GIF

Wykorzystuję tę klasę w swoich projektach, dzięki temu nie muszę martwić się o to, że użytkownik mnie zaskoczy i wykona jakąś akcję dwukrotnie lub (oby nie!) więcej razy.

Pozdrawiam! 😶