
Halo semuanya! Nama saya Anatoly Varivonchik, saya adalah pengembang Android di Badoo. Hari ini saya akan berbagi dengan Anda terjemahan bagian kedua artikel oleh rekan saya Zsolt Kocsi tentang implementasi MVI, yang kami gunakan setiap hari dalam proses pengembangan. Bagian pertama ada di
sini .
Apa yang kita inginkan dan bagaimana kita melakukannya
Di bagian pertama artikel, kami memperkenalkan
Fitur , elemen utama
MVICore yang dapat digunakan kembali. Mereka dapat memiliki struktur paling sederhana dan hanya menyertakan satu
Peredam , atau mereka dapat menjadi alat yang berfungsi penuh untuk mengelola tugas, peristiwa, dan banyak lagi yang tidak sinkron.
Setiap Fitur dapat dilacak - ada peluang untuk berlangganan perubahan dalam statusnya dan menerima pemberitahuan tentang hal itu. Namun, Fitur dapat berlangganan ke sumber input. Dan ini masuk akal, karena dengan dimasukkannya Rx dalam basis kode, kita sudah memiliki banyak objek dan langganan yang dapat diamati di berbagai tingkatan.
Sehubungan dengan peningkatan jumlah komponen reaktif inilah saatnya untuk merenungkan apa yang kita miliki dan apakah mungkin untuk membuat sistem lebih baik.
Kami harus menjawab tiga pertanyaan:
- Elemen apa yang harus digunakan ketika menambahkan komponen reaktif baru?
- Apa cara termudah untuk mengelola langganan Anda?
- Apakah mungkin untuk mengabaikan manajemen siklus hidup / kebutuhan untuk menghapus langganan untuk menghindari kebocoran memori? Dengan kata lain, dapatkah kita memisahkan ikatan komponen dari manajemen berlangganan?
Pada bagian artikel ini, kita akan melihat dasar-dasar dan manfaat membangun sistem menggunakan komponen reaktif dan melihat bagaimana Kotlin membantu dalam hal ini.
Elemen utama
Pada saat kami mulai mengerjakan desain dan standardisasi
Fitur kami, kami telah mencoba berbagai pendekatan dan memutuskan bahwa
Fitur tersebut akan dalam bentuk komponen reaktif. Pertama, kami fokus pada antarmuka utama. Pertama-tama, kami perlu menentukan jenis input dan output data.
Kami beralasan sebagai berikut:
- Mari kita tidak menciptakan kembali roda - mari kita lihat antarmuka apa yang sudah ada.
- Karena kita sudah menggunakan pustaka RxJava, masuk akal untuk merujuk ke antarmuka dasarnya.
- Jumlah antarmuka harus diminimalkan.
Akibatnya, kami memutuskan untuk menggunakan
ObservableSource <T> untuk output dan
Konsumen <T> untuk input. Mengapa tidak
Teramati / Pengamat , Anda bertanya.
Observable adalah kelas abstrak yang harus Anda warisi, dan
ObservableSource adalah antarmuka yang Anda implementasikan yang sepenuhnya memenuhi kebutuhan untuk mengimplementasikan protokol reaktif.
package io.reactivex; import io.reactivex.annotations.*; public interface ObservableSource<T> { void subscribe(@NonNull Observer<? super T> observer); }
Pengamat , antarmuka pertama yang terlintas dalam pikiran, menerapkan empat metode: onSubscribe, onNext, onError, dan onComplete. Dalam upaya menyederhanakan protokol sebanyak mungkin, kami lebih suka
Konsumen <T> , yang menerima elemen baru menggunakan metode tunggal. Jika kami memilih
Pengamat , maka metode yang tersisa akan paling sering menjadi berlebihan atau bekerja secara berbeda (misalnya, kami ingin menyajikan kesalahan sebagai bagian dari
Negara , dan bukan sebagai pengecualian, dan tentu saja tidak mengganggu aliran).
public interface Consumer<T> { void accept(T t) throws Exception; }
Jadi, kami memiliki dua antarmuka, yang masing-masing berisi satu metode. Sekarang kita dapat mengikat mereka dengan menandatangani
Konsumen <T> ke
ObservableSource <T> . Yang terakhir hanya menerima instance dari
Pengamat <T> , tetapi kami dapat membungkusnya dengan
Observable <T> , yang berlangganan
Pelanggan <T> :
val output: ObservableSource<String> = Observable.just("item1", "item2", "item3") val input: Consumer<String> = Consumer { System.out.println(it) } val disposable = Observable.wrap(output).subscribe(input)
(Untungnya, fungsi
.wrap (output) tidak membuat objek baru jika
output sudah menjadi
Observable <T> ).
Anda mungkin ingat bahwa komponen
Fitur dari bagian pertama artikel menggunakan input data tipe
Wish (sesuai dengan Intent dari Model-View-Intent) dan output dari tipe
Negara , dan karena itu bisa di kedua sisi bundel:
Tautan antara
Konsumen dan
Produsen ini sudah terlihat cukup sederhana, tetapi ada cara yang bahkan lebih mudah di mana Anda tidak perlu membuat langganan secara manual atau membatalkannya.
Memperkenalkan
Binder .
Mengikat steroid
MVICore berisi kelas yang disebut
Binder yang menyediakan API sederhana untuk mengelola langganan Rx dan memiliki sejumlah fitur keren.
Mengapa itu dibutuhkan?
- Buat ikatan dengan berlangganan input ke akhir pekan.
- Kemampuan untuk berhenti berlangganan pada akhir siklus hidup (ketika itu adalah konsep abstrak dan tidak ada hubungannya dengan Android).
- Bonus: Binder memungkinkan Anda untuk menambahkan objek perantara, misalnya, untuk logging atau debugging perjalanan waktu.
Alih-alih menandatangani secara manual, Anda dapat menulis ulang contoh di atas sebagai berikut:
val binder = Binder() binder.bind(wishes to feature) binder.bind(feature to logger)
Berkat Kotlin, semuanya terlihat sangat sederhana.
Contoh-contoh ini berfungsi jika jenis input dan output sama. Tetapi bagaimana jika tidak? Dengan menerapkan fungsi ekstensi, kita dapat membuat transformasi otomatis:
val output: ObservableSource<A> = TODO() val input: Consumer<B> = TODO() val transformer: (A) -> B = TODO() binder.bind(output to input using transformer)
Perhatikan sintaks: bunyinya hampir seperti kalimat normal (dan ini adalah alasan lain mengapa saya suka Kotlin). Tetapi
Binder tidak hanya digunakan sebagai gula sintaksis - juga bermanfaat bagi kita untuk menyelesaikan masalah dengan siklus hidup.
Buat Binder
Membuat instance terlihat lebih mudah:
val binder = Binder()
Tetapi dalam hal ini, Anda harus berhenti berlangganan secara manual, dan Anda harus menelepon
binder.dispose()
kapan pun diperlukan untuk menghapus langganan. Ada cara lain: menyuntikkan instance siklus hidup ke dalam konstruktor. Seperti ini:
val binder = Binder(lifecycle)
Sekarang Anda tidak perlu khawatir tentang langganan - mereka akan dihapus pada akhir siklus hidup. Pada saat yang sama, siklus hidup dapat diulang berkali-kali (seperti siklus mulai dan berhenti di Android UI) - dan
Binder akan membuat dan menghapus langganan untuk Anda setiap waktu.
Dan apa itu siklus hidup?
Sebagian besar pengembang Android, melihat frasa "siklus hidup", mewakili siklus Aktivitas dan Fragmen. Ya,
Binder dapat bekerja dengan mereka, berhenti berlangganan di akhir siklus.
Tapi ini hanya permulaan, karena Anda tidak menggunakan antarmuka Android
LifecycleOwner dengan cara apa pun -
Binder memiliki sendiri, lebih universal. Ini pada dasarnya adalah aliran sinyal BEGIN / END:
interface Lifecycle : ObservableSource<Lifecycle.Event> { enum class Event { BEGIN, END }
Anda bisa mengimplementasikan aliran ini menggunakan Observable (by mapping), atau cukup menggunakan kelas
ManualLifecycle dari perpustakaan untuk lingkungan non-Rx (lihat persis di bawah).
Bagaimana cara
kerja binder ? Menerima sinyal BEGIN, itu membuat langganan untuk komponen yang sebelumnya Anda konfigurasi (
input / output ), dan menerima sinyal AKHIR, menghapusnya. Yang paling menarik adalah Anda bisa mulai dari awal lagi:
val output: PublishSubject<String> = PublishSubject.create() val input: Consumer<String> = Consumer { System.out.println(it) } val lifecycle = ManualLifecycle() val binder = Binder(lifecycle) binder.bind(output to input) output.onNext("1") lifecycle.begin() output.onNext("2") output.onNext("3") lifecycle.end() output.onNext("4") lifecycle.begin() output.onNext("5") output.onNext("6") lifecycle.end() output.onNext("7")
Fleksibilitas dalam menetapkan kembali langganan ini sangat berguna ketika bekerja dengan Android, ketika ada beberapa siklus Start-Stop dan Resume-Pause, di samping Buat-Hancurkan yang biasa.
Android Binder Lifecycles
Ada tiga kelas di perpustakaan:
- BuatDestroyBinderLifecycle ( androidLifecycle )
- StartStopBinderLifecycle ( androidLifecycle )
- ResumePauseBinderLifecycl e ( androidLifecycle )
androidLifecycle
adalah nilai yang dikembalikan oleh metode
getLifecycle()
, yaitu,
AppCompatActivity ,
AppCompatDialogFragment , dll. Semuanya sangat sederhana:
fun createBinderForActivity(activity: AppCompatActivity) = Binder( CreateDestroyBinderLifecycle(activity.lifecycle) )
Siklus hidup individu
Jangan berhenti di situ, karena kita tidak terikat dengan Android dengan cara apa pun. Apa siklus hidup
pengikat ? Secara harfiah apa saja: misalnya, waktu pemutaran dialog atau waktu eksekusi beberapa tugas yang tidak sinkron. Anda dapat, misalnya, ikat ke lingkup DI - dan kemudian langganan apa pun akan dihapus bersamanya. Kebebasan penuh untuk bertindak.
- Ingin langganan disimpan sebelum Observable mengirim item? Konversi objek ini ke Lifecycle dan berikan ke Binder . Terapkan kode berikut dalam fungsi ekstensi dan gunakan nanti:
fun Observable<T>.toBinderLifecycle() = Lifecycle.wrap(this .first() .map { END } .startWith(BEGIN) )
- Ingin menjaga ikatan Anda sampai Completable selesai? Tidak ada masalah - ini dilakukan dengan analogi dengan paragraf sebelumnya:
fun Completable.toBinderLifecycle() = Lifecycle.wrap( Observable.concat( Observable.just(BEGIN), this.andThen(Observable.just(END)) ) )
- Ingin beberapa kode non-Rx lainnya memutuskan kapan harus menghapus langganan? Gunakan ManualLifecycle seperti dijelaskan di atas.
Bagaimanapun, Anda dapat meletakkan aliran reaktif ke aliran elemen
Siklus Hidup. Bahkan , atau menggunakan
ManualLifecycle jika Anda bekerja dengan kode non-Rx.
Tinjauan Umum Sistem
Binder menyembunyikan detail membuat dan mengelola langganan Rx. Yang tersisa adalah gambaran umum singkat dan umum: "Komponen A berinteraksi dengan komponen B dalam lingkup C".
Misalkan kita memiliki komponen reaktif berikut untuk layar saat ini:

Kami ingin komponen yang akan terhubung dalam layar saat ini, dan kami tahu bahwa:
- UIEvent dapat diumpankan langsung ke AnalyticsTracker ;
- UIEvent dapat diubah menjadi Wish for Feature ;
- Negara dapat diubah menjadi Model View untuk Tampilan .
Ini dapat diungkapkan dalam beberapa baris:
with(binder) { bind(feature to view using stateToViewModelTransformer) bind(view to feature using uiEventToWishTransformer) bind(view to analyticsTracker) }
Kami membuat meremas tersebut untuk menunjukkan interkoneksi komponen. Dan karena kami pengembang menghabiskan lebih banyak waktu membaca kode daripada menulisnya, ikhtisar singkat seperti itu sangat berguna, terutama ketika jumlah komponen bertambah.
Kesimpulan
Kami melihat bagaimana
Binder membantu dalam mengelola langganan Rx dan bagaimana
Binder membantu Anda mendapatkan gambaran umum tentang sistem yang dibangun dari komponen reaktif.
Dalam artikel berikut, kami akan menjelaskan bagaimana kami memisahkan komponen UI reaktif dari logika bisnis dan cara menambahkan objek perantara menggunakan
Binder (untuk logging dan debugging perjalanan waktu). Jangan beralih!
Sementara itu, periksa perpustakaan di
GitHub .