Debounce Pencarian Kata Kunci dengan RxJava 2

Dalam aplikasi mobile, sering kali kita temukan fitur menampilkan hasil pencarian ketika pengguna sedang mengetik kata kunci. Penerapan paling sederhana fitur ini adalah mengirimkan request pencarian setiap kali pengguna mengubah kata kunci.

Misalkan pengguna mencari “motogp”, maka:

…dan seterusnya.

Namun, dapat dibayangkan aplikasi akan mengirim banyak request yang tidak dibutuhkan karena pencarian sebelum “motogp” sebenarnya tidak dibutuhkan oleh pengguna. Server pun dirugikan karena harus memproses request yang tidak perlu.

Oleh karena itu, seringkali pengembang aplikasi akan menunda request pencarian selama beberapa waktu , dengan asumsi kata kunci muncul belakangan lebih mendekati atau persis sama dengan kata kunci yang diinginkan pengguna. Hal ini dapat dirumuskan:

waktu_aksi_input_terakhir + x < waktu_sekarang

di mana nilai x ini adalah nilai yang ditentukan oleh pengembang aplikasi(Umumnya berkisar 200ms-300ms).

Teknik di atas biasa diterapkan dengan debounce dan mudah ditemukan lewat mesin pencari. Dengan debounce, langkah-langkah di atas berubah seperti ini:

(Contoh di atas adalah penyederhaan dari kasus nyata. Seringkali pengguna berhenti sebentar di tengah-tengah mengetik dan itu lumrah terjadi.)

Implementasi dengan RxJava 2

RxJava 2 memiliki operator debounce yang sesuai dengan apa yang dibutuhkan.Implementasi paling sederhana dalam bahasa pemograman kotlin seperti ini:

val keywords = // observable untuk kata kunci yang tengah diketik

Namun, implementasi ini memiliki kekurangan ketika request pencarian ke server mengalami error, maka pencarian berikutnya tidak akan terjadi akibat subscriber akan berhenti subscribe ketika error terjadi.

Kita dapat memperbaikinya dengan subscribe kembali ketika error terjadi dengan retry operator.

val keywords = // observable untuk kata kunci yang tengah diketik

Dengan versi terbaru, setelah error terjadi, pencarian masih akan berlangsung. Akan tetapi, beberapa kali percobaan memperlihatkan hal yang tidak diharapkan, yaitu setelah pengguna mengetikkan kata kunci dengan sempurna, tidak tampak pencarian dengan kata kunci tersebut. Misalkan pengguna selesai mengetik “motogp”, tapi permintaan pencarian terakhir adalah “motog”.

Keanehan ini selalu terjadi setelah error terlihat sebelumnya. Oleh karena itu, muncul kecurigaan ketika error, maka beberapa kata kunci yang dihasilkan bersamaan dengan error tidak terproses. Untuk menguji hipotesis ini, maka dibuatlah contoh kode ini:


class DebounceTest {

Output

onSubsribe 0 at time: 1536684717184
error : java.lang.RuntimeException: Faking error at time: 1536684717454
onSubsribe 0 at time: 1536684717454
onDispose at time: 1536684717454

Output yang diharapkan

InputCase(input=b, shouldError=false)

Akan tetapi, implementasi di atas tidak menghasilkan output tersebut. Bila dilihat, onDispose dipanggil pada waktu yang sama dengan onSubscribe kedua (akibat retry operator). Muncul hipotesis ketika dispose terjadi, di waktu yang sama pengguna selesai mengetik kata kunci, sehingga kata kunci tersebut tidak pernah diterima oleh subscriber. Untuk itu, kita perlu menghilangkan error dengan menulis ulang seperti ini:

@Test
   fun correct_implementation_debounce_click() {
     val inputs = listOf<InputCase>(
       InputCase(“a”, true),
       InputCase(“b”, false)
     )
     val fakeUserInput = PublishSubject.create<InputCase>()
     val observeInput : Observable<InputCase> = fakeUserInput
       .debounce(200, TimeUnit.MILLISECONDS)
       .switchMap {
          if (it.shouldError) {
           Observable.just(1)
             .delay(50, TimeUnit.MILLISECONDS)
             .flatMap { Observable.error<InputCase>            (RuntimeException(“Faking error”)) }
          } else {
            Observable.just(it)
             .delay(50, TimeUnit.MILLISECONDS)
          }
          .onErrorResumeNext { _: Throwable  ->
           // replace error with replacement usually default value
           Observable.just(InputCase("error replacement", false))
          }
      }
      .log()

Output


onSubsribe 0 at time: 1536685689006
next : InputCase(input=error replacement, shouldError=false) at time: 1536685689262
InputCase(input=error replacement, shouldError=false)
next : InputCase(input=b, shouldError=false) at time: 1536685689520
InputCase(input=b, shouldError=false)

Dapat dilihat output yang diharapkan muncul.

Dengan implementasi yang tepat, RxJava 2 akan memudahkan kita dalam menerapkan debounce. Dan bila ada kesalahan yang terjadi, maka akan sangat membantu bila kita dapat mencoba membuat model sederhana untuk menguji hipotesis.

Kode-kode di atas hanyalah ilustrasi dan tidak ditujukan sebagai rujukan.

Beberapa sumber yang dipakai dalam penulisan:1. Survei reaksi klik manusia

2. Ilustrasi interaktif debounce di RxMarbles

3. RxJava 2 Debounce operator dapat ditemukan di sini

4. Test Scheduler untuk menggantikan thread sleep

5. RxJava 2 adalah salah satu implementasi Reactive programming dalam bahasa java