Menerapkan pencarian instan di Android menggunakan RxJava

Menerapkan pencarian instan di Android menggunakan RxJava


Saya sedang mengerjakan aplikasi baru yang, seperti biasanya terjadi, berkomunikasi dengan layanan backend untuk menerima data melalui API. Dalam contoh ini, saya akan mengembangkan fungsi pencarian, salah satu fitur yang akan menjadi pencarian instan saat memasukkan teks.


Pencarian instan


Tidak ada yang rumit, menurut Anda. Anda hanya perlu menempatkan komponen pencarian di halaman (kemungkinan besar, di toolbar), hubungkan onTextChange event onTextChange dan lakukan pencarian. Jadi inilah yang saya lakukan:


 override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.menu_main, menu) val searchView = menu?.findItem(R.id.action_search)?.actionView as SearchView // Set up the query listener that executes the search searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String?): Boolean { Log.d(TAG, "onQueryTextSubmit: $query") return false } override fun onQueryTextChange(newText: String?): Boolean { Log.d(TAG, "onQueryTextChange: $newText") return false } }) return super.onCreateOptionsMenu(menu) } 

Tapi ini masalahnya. Karena saya perlu menerapkan pencarian saat input, setiap kali event handler onQueryTextChange() , saya beralih ke API untuk mendapatkan set pertama hasil. Log adalah sebagai berikut:


 D/MainActivity: onQueryTextChange: T D/MainActivity: onQueryTextChange: TE D/MainActivity: onQueryTextChange: TES D/MainActivity: onQueryTextChange: TEST D/MainActivity: onQueryTextSubmit: TEST 

Terlepas dari kenyataan bahwa saya hanya mencetak permintaan saya, ada lima panggilan API, yang masing-masing melakukan pencarian. Misalnya, di cloud, Anda harus membayar untuk setiap panggilan ke API. Jadi, ketika saya memasukkan permintaan saya, saya perlu sedikit penundaan sebelum mengirimnya, sehingga hanya satu panggilan API yang dibuat pada akhirnya.


Sekarang, anggaplah saya ingin mencari sesuatu yang lain. Saya menghapus TEST dan memasukkan karakter lain:


 D/MainActivity: onQueryTextChange: TES D/MainActivity: onQueryTextChange: TE D/MainActivity: onQueryTextChange: T D/MainActivity: onQueryTextChange: D/MainActivity: onQueryTextChange: S D/MainActivity: onQueryTextChange: SO D/MainActivity: onQueryTextChange: SOM D/MainActivity: onQueryTextChange: SOME D/MainActivity: onQueryTextChange: SOMET D/MainActivity: onQueryTextChange: SOMETH D/MainActivity: onQueryTextChange: SOMETHI D/MainActivity: onQueryTextChange: SOMETHIN D/MainActivity: onQueryTextChange: SOMETHING D/MainActivity: onQueryTextChange: SOMETHING D/MainActivity: onQueryTextChange: SOMETHING E D/MainActivity: onQueryTextChange: SOMETHING EL D/MainActivity: onQueryTextChange: SOMETHING ELS D/MainActivity: onQueryTextChange: SOMETHING ELSE D/MainActivity: onQueryTextChange: SOMETHING ELSE D/MainActivity: onQueryTextSubmit: SOMETHING ELSE 

Ada 20 panggilan API! Penundaan kecil akan mengurangi jumlah panggilan ini. Saya juga ingin menyingkirkan duplikat sehingga teks yang dipotong tidak menyebabkan permintaan berulang. Saya juga mungkin ingin menyaring beberapa elemen. Misalnya, apakah Anda memerlukan kemampuan untuk mencari tanpa karakter yang dimasukkan atau mencari dengan satu karakter?


Pemrograman reaktif


Ada beberapa opsi untuk apa yang harus dilakukan selanjutnya, tetapi saat ini saya ingin fokus pada teknik yang umumnya dikenal sebagai pemrograman reaktif dan perpustakaan RxJava. Ketika saya pertama kali menemukan pemrograman reaktif, saya melihat deskripsi berikut:


ReactiveX adalah API yang bekerja dengan struktur asinkron dan memanipulasi aliran data atau peristiwa menggunakan kombinasi pola Observer dan Iterator, serta fitur pemrograman fungsional.

Definisi ini tidak sepenuhnya menjelaskan sifat dan kekuatan ReactiveX. Dan jika itu menjelaskan, maka hanya untuk mereka yang sudah terbiasa dengan prinsip-prinsip kerangka kerja ini. Saya juga melihat grafik seperti ini:


Keterlambatan Bagan Operator


Diagram menjelaskan peran operator, tetapi tidak sepenuhnya memahami esensinya. Jadi, mari kita lihat apakah saya dapat lebih jelas menjelaskan diagram ini dengan contoh sederhana.


Mari kita siapkan proyek kita terlebih dahulu. Anda akan membutuhkan perpustakaan baru di file build.gradle aplikasi Anda:


 implementation "io.reactivex.rxjava2:rxjava:2.1.14" 

Ingatlah untuk menyinkronkan dependensi proyek untuk memuat perpustakaan.


Sekarang mari kita lihat solusi baru. Menggunakan metode lama, saya mengakses API saat saya memasukkan setiap karakter baru. Menggunakan metode baru, saya akan membuat Observable :


 override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.menu_main, menu) val searchView = menu?.findItem(R.id.action_search)?.actionView as SearchView // Set up the query listener that executes the search Observable.create(ObservableOnSubscribe<String> { subscriber -> searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextChange(newText: String?): Boolean { subscriber.onNext(newText!!) return false } override fun onQueryTextSubmit(query: String?): Boolean { subscriber.onNext(query!!) return false } }) }) .subscribe { text -> Log.d(TAG, "subscriber: $text") } return super.onCreateOptionsMenu(menu) } 

Kode ini melakukan hal yang persis sama dengan kode lama. Log adalah sebagai berikut:


 D/MainActivity: subscriber: T D/MainActivity: subscriber: TE D/MainActivity: subscriber: TES D/MainActivity: subscriber: TEST D/MainActivity: subscriber: TEST 

Namun, perbedaan utama antara menggunakan teknik baru adalah adanya aliran reaktif - Observable . Penangan teks (atau penangan permintaan dalam hal ini) mengirimkan elemen ke aliran menggunakan metode onNext() . Dan Observable memiliki pelanggan yang memproses elemen-elemen ini.


Kami dapat membuat rangkaian metode sebelum berlangganan untuk mengurangi daftar baris untuk diproses. Untuk memulainya, teks yang dikirim akan selalu berupa huruf kecil dan tidak ada spasi di awal dan akhir baris:


 Observable.create(ObservableOnSubscribe<String> { ... }) .map { text -> text.toLowerCase().trim() } .subscribe { text -> Log.d(TAG, "subscriber: $text" } 

Saya memotong metode untuk menunjukkan bagian yang paling signifikan. Sekarang log yang sama adalah sebagai berikut:


 D/MainActivity: subscriber: t D/MainActivity: subscriber: te D/MainActivity: subscriber: tes D/MainActivity: subscriber: test D/MainActivity: subscriber: test 

Sekarang mari kita terapkan penundaan 250 ms, mengharapkan lebih banyak konten:


 Observable.create(ObservableOnSubscribe<String> { ... }) .map { text -> text.toLowerCase().trim() } .debounce(250, TimeUnit.MILLISECONDS) .subscribe { text -> Log.d(TAG, "subscriber: $text" } 

Dan akhirnya, hapus aliran duplikat sehingga hanya permintaan unik pertama yang diproses. Permintaan identik selanjutnya akan diabaikan:


 Observable.create(ObservableOnSubscribe<String> { ... }) .map { text -> text.toLowerCase().trim() } .debounce(100, TimeUnit.MILLISECONDS) .distinct() .subscribe { text -> Log.d(TAG, "subscriber: $text" } 

Catatan perev. Dalam hal ini, lebih masuk akal untuk menggunakan operator distinctUntilChanged() , karena jika tidak, dalam kasus pencarian berulang pada string apa pun, kueri hanya akan diabaikan. Dan ketika menerapkan pencarian seperti itu, masuk akal untuk hanya memperhatikan permintaan terakhir yang berhasil dan mengabaikan yang baru jika identik dengan yang sebelumnya.

Pada akhirnya, kami memfilter kueri kosong:


 Observable.create(ObservableOnSubscribe<String> { ... }) .map { text -> text.toLowerCase().trim() } .debounce(100, TimeUnit.MILLISECONDS) .distinct() .filter { text -> text.isNotBlank() } .subscribe { text -> Log.d(TAG, "subscriber: $text" } 

Pada titik ini, Anda akan melihat bahwa hanya satu (atau mungkin dua) pesan yang ditampilkan dalam log, yang menunjukkan lebih sedikit panggilan API. Dalam hal ini, aplikasi akan terus berfungsi dengan baik. Selain itu, kasus ketika Anda memasukkan sesuatu, tetapi kemudian menghapus dan memasukkan lagi, juga akan menyebabkan lebih sedikit panggilan ke API.


Ada banyak lagi operator berbeda yang dapat Anda tambahkan ke saluran pipa ini, tergantung pada tujuan Anda. Saya percaya bahwa mereka sangat berguna untuk bekerja dengan bidang input yang berinteraksi dengan API. Kode lengkapnya adalah sebagai berikut:


 // Set up the query listener that executes the search Observable.create(ObservableOnSubscribe<String> { subscriber -> searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextChange(newText: String?): Boolean { subscriber.onNext(newText!!) return false } override fun onQueryTextSubmit(query: String?): Boolean { subscriber.onNext(query!!) return false } }) }) .map { text -> text.toLowerCase().trim() } .debounce(250, TimeUnit.MILLISECONDS) .distinct() .filter { text -> text.isNotBlank() } .subscribe { text -> Log.d(TAG, "subscriber: $text") } 

Sekarang saya dapat mengganti pesan log dengan panggilan ke ViewModel untuk memulai panggilan API. Namun, ini adalah topik untuk artikel lain.


Kesimpulan


Dengan menggunakan teknik sederhana ini untuk membungkus elemen teks dalam Observable dan menggunakan RxJava, Anda dapat mengurangi jumlah panggilan API yang diperlukan untuk melakukan operasi server, serta meningkatkan daya tanggap aplikasi Anda. Dalam artikel ini, kami hanya membahas sebagian kecil dari seluruh dunia RxJava, jadi saya meninggalkan Anda tautan untuk bacaan tambahan tentang topik ini:


  • Dan Lew Grokking RxJava (ini adalah situs yang membantu saya bergerak ke arah yang benar).
  • Situs ReactiveX (saya sering merujuk ke situs ini ketika membangun saluran pipa).

Source: https://habr.com/ru/post/id430394/


All Articles