Saya datang ke Tinkoff beberapa tahun yang lalu, pada sebuah proyek baru, Pelanggan dan Proyek , yang baru saja dimulai saat itu.
Sekarang saya tidak ingat perasaan saya dari arsitektur baru itu untuk saya. Tapi saya ingat pasti: itu tidak biasa bahwa Rx digunakan di tempat lain, di luar perjalanan biasa ke jaringan dan ke pangkalan. Sekarang setelah arsitektur ini telah melewati beberapa jalur perkembangan evolusi, saya ingin akhirnya berbicara tentang apa yang terjadi dan apa yang terjadi.

Menurut pendapat saya, semua arsitektur yang saat ini populer - MVP, MVVM, dan bahkan MVI - telah lama berada di arena dan tidak selalu layak. Bukankah mereka memiliki kekurangan? Saya melihat banyak dari mereka. Kami memutuskan di tempat kami bahwa itu cukup untuk bertahan, dan (kembali) menemukan arsitektur baru yang tidak sinkron.
Saya akan menjelaskan secara singkat apa yang tidak saya sukai tentang arsitektur saat ini. Beberapa poin mungkin kontroversial. Mungkin Anda belum pernah mengalami ini, Anda menulis pemrograman Jedi yang sempurna dan umumnya. Lalu maafkan aku, orang berdosa.
Jadi rasa sakit saya adalah:
- Presenter Besar / ViewModel.
- Sejumlah besar switch-case di MVI.
- Ketidakmampuan untuk menggunakan kembali bagian Presenter / ViewModel dan, sebagai akibatnya, kebutuhan untuk menduplikasi kode.
- Tumpukan variabel yang bisa berubah yang dapat dimodifikasi dari mana saja. Karenanya, kode tersebut sulit dipertahankan dan dimodifikasi.
- Pembaruan layar tidak terurai.
- Sulit untuk menulis tes.
Masalah
Setiap saat, aplikasi memiliki status tertentu yang menentukan perilakunya dan apa yang dilihat pengguna. Keadaan ini mencakup semua nilai variabel - dari bendera sederhana hingga objek individual. Masing-masing variabel hidup sendiri dan dikendalikan oleh bagian kode yang berbeda. Anda dapat menentukan kondisi aplikasi saat ini hanya dengan memeriksa semuanya, satu demi satu.
Artikel tentang arsitektur Kotlin MVI modern
Bab 1. Evolusi adalah segalanya bagi kita
Awalnya, kami menulis di MVP, tetapi sedikit bermutasi. Itu campuran MVP dan MVI. Ada entitas dari MVP dalam bentuk presenter dan tampilan antarmuka:
interface NewTaskView { val newTaskAction: Observable<NewTaskAction> val taskNameChangeAction: Observable<String> val onChangeState: Consumer<SomeViewState> }
Sudah di sini Anda dapat melihat tangkapan: Lihat di sini sangat jauh dari kanon MVP. Ada metode dalam presenter:
fun bind(view: SomeView): Disposable
Di luar, implementasi antarmuka disahkan yang secara reaktif berlangganan perubahan UI. Dan itu sudah menampar MVI!
Lebih banyak lebih. Di Presenter, berbagai interaksi dibuat dan berlangganan perubahan Lihat, tetapi mereka tidak memanggil metode UI secara langsung, tetapi mengembalikan beberapa Negara global, di mana ada semua kemungkinan kondisi layar:
compositeDisposable.add( Observable.merge(firstAction, secondAction) .observeOn(AndroidSchedulers.mainThread()) .subscribe(view.onChangeState)) return compositeDisposable
class SomeViewState(val progress: Boolean? = null, val error: Throwable? = null, val errorMessage: String? = error?.message, val result: TaskUi? = null)
Aktivitas adalah turunan dari antarmuka SomeViewStateMachine:
interface SomeViewStateMachine { fun toSuccess(task: SomeUiModel) fun toError(error: String?) fun toProgress() fun changeSomeButton(buttonEnabled: Boolean) }
Ketika pengguna mengklik sesuatu di layar, sebuah peristiwa datang ke presenter dan ia menciptakan model baru, yang digambar oleh kelas khusus:
class SomeViewStateResolver(private val stateMachine: SomeViewStateMachine) : Consumer<SomeViewState> { override fun accept(stateUpdate: SomeViewState) { if (stateUpdate.result != null) { stateMachine.toSuccess(stateUpdate.result) } else if (stateUpdate.error != null && stateUpdate.progress == false) { stateMachine.toError(stateUpdate.errorMessage) } else if (stateUpdate.progress == true) { stateMachine.toProgress() } else if (stateUpdate.someButtonEnabled != null) { stateMachine.changeSomeButton(stateUpdate.someButtonEnabled) } } }
Setuju, beberapa MVP aneh, dan bahkan jauh dari MVI. Mencari inspirasi.
Bab 2. Redux

Berbicara tentang masalahnya dengan pengembang lain, pemimpin kami (yang masih) Sergey Boishtyan belajar tentang Redux .
Setelah menonton pembicaraan Dorfman tentang semua arsitektur dan bermain dengan Redux , kami memutuskan untuk menggunakannya untuk meningkatkan arsitektur kami.
Tapi pertama-tama, mari kita melihat lebih dekat pada arsitektur dan melihat pro dan kontra.
Aksi
Menjelaskan tindakannya.
Actioncreator
Dia seperti analis sistem: format, melengkapi spesifikasi kebutuhan pelanggan sehingga pemrogram memahaminya.
Ketika pengguna mengklik pada layar, ActionsCreator membentuk Tindakan yang masuk ke middleware (semacam logika bisnis). Logika bisnis memberi kita data baru yang diterima dan digambar Reducer tertentu.
Jika Anda melihat gambar lagi, Anda mungkin melihat objek seperti Store. Toko toko Reduksi. Yaitu, kita melihat bahwa saudara-saudara front-end - saudara yang malang - telah menebak bahwa satu benda besar dapat digergaji menjadi banyak benda kecil, yang masing-masing akan bertanggung jawab atas bagian layarnya. Dan ini hanya pemikiran yang luar biasa!
Kode contoh untuk ActionCreators sederhana (hati-hati, JavaScript!):
export function addTodo(text) { return { type: ADD_TODO, text } } export function toggleTodo(index) { return { type: TOGGLE_TODO, index } } export function setVisibilityFilter(filter) { return { type: SET_VISIBILITY_FILTER, filter } }
Peredam
Tindakan menggambarkan fakta bahwa sesuatu terjadi, tetapi tidak menunjukkan bagaimana keadaan aplikasi harus berubah sebagai respons, ini berfungsi untuk Peredam.
Singkatnya, Reducer tahu bagaimana cara mendekomposisi layar / view secara terurai.
Pro:
- Pembaruan layar terurai.
- Aliran data searah.
Cons:
- Beralih favorit lagi.
function todoApp(state = initialState, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return Object.assign({}, state, { visibilityFilter: action.filter }) case ADD_TODO: return Object.assign({}, state, { todos: [ ...state.todos, { text: action.text, completed: false } ] }) default: return state }
- Banyak objek negara.
- Pemisahan logika menjadi ActionCreator dan Reducer.
Ya, bagi kami sepertinya pemisahan ActionCreator dan Reducer bukanlah pilihan terbaik untuk menghubungkan model dan layar, karena menulis instanceof adalah pendekatan yang buruk. Dan di sini kami menemukan arsitektur KAMI!
Bab 3. EBA

Apa itu Action dan ActionCreator dalam konteks EBA:
typealias Action = () -> Unit typealias ActionMapper<T> = (T) -> Action interface ActionCreator<T> : (T) -> (Observable<Action>)
Ya, setengah dari arsitekturnya adalah typealias dan antarmuka. Kesederhanaan sama dengan keanggunan!
Diperlukan tindakan untuk memanggil sesuatu tanpa mengirimkan data apa pun. Karena ActionCreator mengembalikan Observable, kami harus membungkus Action dengan lambda lain untuk mengirimkan beberapa data. Dan ternyata ActionMapper - Aksi yang diketik di mana kita bisa melewati apa yang kita butuhkan untuk memperbarui layar / tampilan.
Postulat dasar:
One ActionCreator - satu bagian dari layarDengan paragraf pertama, semuanya jelas: sehingga tidak ada neraka dari pembaruan lintas yang tidak dapat dipahami, kami sepakat bahwa satu ActionCreator dapat memperbarui hanya bagian layarnya. Jika itu daftar, itu hanya memperbarui daftar, jika tombol hanya itu.
Belati tidak dibutuhkanTapi, orang bertanya-tanya, mengapa belati tidak menyenangkan kita? Aku memberitahumu.
Sebuah kisah yang khas adalah ketika seorang Sergey abstrak alias master belati alias "Apa yang dilakukan abstrak ini?" Ada di proyek.
Ternyata jika Anda bereksperimen dengan belati, Anda harus menjelaskan setiap kali kepada setiap pengembang baru (dan bukan hanya baru). Atau mungkin Anda sendiri sudah lupa apa yang dilakukan anotasi ini, dan Anda membuka google.
Semua ini sangat menyulitkan proses pembuatan fitur tanpa memperkenalkan banyak kenyamanan. Oleh karena itu, kami memutuskan bahwa kami akan menciptakan hal-hal yang kami butuhkan dengan tangan kami, sehingga akan lebih cepat untuk berkumpul, karena tidak ada pembuatan kode. Ya, kami akan menghabiskan lima menit ekstra untuk menulis semua dependensi dengan tangan kami, tetapi kami akan menghemat banyak waktu untuk kompilasi. Ya, di mana-mana kita tidak meninggalkan belati, itu digunakan pada tingkat global, itu menciptakan beberapa hal umum, tetapi kita menulisnya di Jawa untuk optimasi yang lebih baik, agar tidak menarik kapt.
Skema arsitektur :

Komponen adalah analog dari komponen yang sama dari Dagger, hanya tanpa Dagger. Tugasnya adalah membuat binder. Binder mengikat ActionCreator bersama. Dari Lihat ke Acara Binder muncul tentang apa yang terjadi, dan dari Binder ke Lihat, Tindakan dikirimkan untuk memperbarui layar.
Actioncreator

Sekarang mari kita lihat seperti apa ini - ActionCreator. Dalam kasus yang paling sederhana, ia hanya memproses tindakan secara tidak langsung. Misalkan ada skenario seperti itu: pengguna mengklik tombol "Buat tugas". Layar lain harus terbuka, di mana kami akan menggambarkannya, tanpa permintaan tambahan.
Untuk melakukan ini, kita cukup berlangganan tombol menggunakan RxBinding dari Jake yang kita cintai dan menunggu pengguna mengkliknya. Segera setelah klik terjadi, Binder akan mengirimkan Acara ke ActionCreator tertentu, yang akan memanggil Aksi kami, yang akan membuka layar baru untuk kami. Perhatikan bahwa tidak ada sakelar. Selanjutnya saya akan tunjukkan dalam kode mengapa demikian.
Jika kami tiba-tiba perlu pergi ke jaringan atau database, kami membuat permintaan ini di sana, tetapi melalui interaktor yang kami berikan ke konstruktor ActionCreator melalui antarmuka untuk memanggil mereka:
Penafian: pemformatan kode tidak cukup di sini, saya punya aturan untuk artikel sehingga kode dibaca dengan baik.
class LoadItemsActionCreator( private val getItems: () -> Observable<List<ViewTyped>>, private val showLoadedItems: ActionMapper<DiffResult<ViewTyped>>, private val diffCalculator: DiffCalculator<ViewTyped>, private val errorItem: ErrorView, private val emptyItem: ViewTyped? = null) : ActionOnEvent
Dengan kata-kata "oleh antarmuka panggilan mereka", saya maksudkan persis bagaimana getItems dideklarasikan (di sini ViewTyped adalah antarmuka kami untuk bekerja dengan daftar). Ngomong-ngomong, kami telah menggunakan kembali ActionCreator ini di delapan bagian aplikasi yang berbeda, karena ini ditulis seserbuk mungkin.
Karena peristiwa bersifat reaktif, kita dapat merakit rantai dengan menambahkan operator lain di sana, misalnya, mulai dengan (showLoadingAction) untuk menampilkan pemuatan, dan onErrorReturn (errorAction) untuk menunjukkan keadaan layar dengan kesalahan.
Dan semua ini reaktif!
Contoh
class AboutFragment : CompositionFragment(R.layout.fragment_about) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val component = AboutComponent( setVersionName = { { appVersion.text = it } }, openPdfAction = { (url, name) -> { openPdf(url, name) } }) val events = AboutEventsImpl( bindEvent = bindEvent, openPolicyPrivacyEvent = confidentialityPolicy.clicks(), openProcessingPersDataEvent = personalDataProtection.clicks(), unbindEvent = unBindEvent) component.binder().bind(events) }
Mari kita lihat arsitektur menggunakan kode sebagai contoh. Untuk memulai, saya memilih salah satu layar paling sederhana - tentang aplikasi, karena ini adalah layar statis.
Pertimbangkan untuk membuat komponen:
val component = AboutComponent( setVersionName = { { appVersion.text = it } }, openPdfAction = { (url, name) -> { openPdf(url, name) } } )
Argumen komponen - Actions / ActionMappers - membantu mengaitkan View dengan ActionCreators. Di ActionMapper'e setVersionName kami melewati versi proyek dan memberikan nilai ini ke teks di layar. Di openPdfAction, sepasang tautan ke dokumen dan nama untuk membuka layar berikutnya di mana pengguna dapat membaca dokumen ini.
Berikut adalah komponen itu sendiri:
class AboutComponent( private val setVersionName: ActionMapper<String>, private val openPdfAction: ActionMapper<Pair<String, String>>) { fun binder(): AboutEventsBinder { val openPolicyPrivacy = OpenPdfActionCreator(openPdfAction, someUrlString) val openProcessingPersonalData = OpenPdfActionCreator(openPdfAction, anotherUrlString) val setVersionName = setVersionName.toSimpleActionCreator( moreComponent::currentVersionName ) return AboutEventsBinder(setVersionName, openPolicyPrivacy, openProcessingPersonalData) } }
Biarkan saya mengingatkan Anda bahwa:
typealias Action = () -> Unit typealias ActionMapper<T> = (T) -> Action
Oke, mari kita lanjutkan.
fun binder(): AboutEventsBinder
Mari kita lihat AboutEventsBinder lebih terinci.
class AboutEventsBinder(private val setVersionName: ActionOnEvent, private val openPolicyPrivacy: ActionOnEvent, private val openProcessingPersonalData: ActionOnEvent) : BaseEventsBinder<AboutEvents>() { override fun bindInternal(events: AboutEvents): Observable<Action> { return Observable.merge( setVersionName(events.bindEvent), openPolicyPrivacy(events.openPolicyPrivacyEvent), openProcessingPersonalData(events.openProcessingPersDataEvent)) } }
ActionOnEvent adalah typealias lain, agar tidak menulis setiap waktu.
ActionCreator<Observable<*>>
Di AboutEventsBinder, kami melewati ActionCreators dan, memohonnya, mengikat ke acara tertentu. Tetapi untuk memahami bagaimana semua ini terhubung, mari kita lihat kelas dasar - BaseEventsBinder.
abstract class BaseEventsBinder<in EVENTS : BaseEvents>( private val uiScheduler: Scheduler = AndroidSchedulers.mainThread() ) { fun bind(events: EVENTS) { bindInternal(events).observeOn(uiScheduler) .takeUntil(events.unbindEvent) .subscribe(Action::invoke) } protected abstract fun bindInternal(events: EVENTS): Observable<Action> }
Kita melihat metode bindInternal yang kita kenal, yang kita definisikan ulang sebagai penerusnya. Sekarang pertimbangkan metode bind. Semua keajaiban ada di sini. Kami menerima pewaris antarmuka BaseEvents, meneruskannya ke bindInternal untuk menghubungkan Acara dan Tindakan. Begitu kami mengatakan bahwa apa pun yang datang, kami mengeksekusi di ui-stream dan berlangganan. Kami juga melihat peretasan yang menarik - takeUntil.
interface BaseEvents { val unbindEvent: EventObservable }
Setelah mendefinisikan bidang unbindEvent di BaseEvents untuk mengontrol berhenti berlangganan, kita harus mengimplementasikannya di semua ahli waris. Bidang luar biasa ini memungkinkan Anda untuk berhenti berlangganan secara otomatis segera setelah acara ini selesai. Bagus sekali! Sekarang Anda tidak bisa mengikuti dan tidak khawatir tentang siklus hidup dan tidur dengan tenang.
val openPolicyPrivacy = OpenPdfActionCreator(openPdfAction, policyPrivacyUrl) val openProcessingPersonalData = OpenPdfActionCreator(openPdfAction, personalDataUrl)
Kembali ke komponen. Dan di sini Anda sudah dapat melihat metode penggunaan kembali. Kami menulis satu kelas yang dapat membuka layar tampilan pdf, dan tidak masalah bagi kami apa urlnya. Tidak ada duplikasi kode.
class OpenPdfActionCreator( private val openPdfAction: ActionMapper<Pair<String, String>>, private val pdfUrl: String) : ActionOnEvent { override fun invoke(event: EventObservable): Observable<Action> { return event.map { openPdfAction(pdfUrl to pdfUrl.substringAfterLast(FILE_NAME_DELIMITER)) } } }
Kode ActionCreator juga sesederhana mungkin, di sini kita hanya melakukan beberapa manipulasi string.
Mari kita kembali ke komponen dan pertimbangkan ActionCreator berikut:
setVersionName.toSimpleActionCreator(moreComponent::currentVersionName)
Suatu kali kami menjadi terlalu malas untuk menulis ActionCreators yang sama dan inheren sederhana. Kami menggunakan kekuatan Kotlin dan menulis ekstensi'y. Misalnya, dalam hal ini, kami hanya perlu memberikan string statis ke ActionMapper.
fun <R> ActionMapper<R>.toSimpleActionCreator( mapper: () -> R): ActionCreator<Observable<*>> { return object : ActionCreator<Observable<*>> { override fun invoke(event: Observable<*>): Observable<Action> { return event.map { this@toSimpleActionCreator(mapper()) } } } }
Ada saat-saat ketika kita tidak perlu mengirimkan apa pun, tetapi hanya memanggil beberapa Tindakan - misalnya, untuk membuka layar berikut:
fun Action.toActionCreator(): ActionOnEvent { return object : ActionOnEvent { override fun invoke(event: EventObservable): Observable<Action> { return event.map { this@toActionCreator } } } }
Jadi, dengan komponen selesai, kembali ke fragmen:
val events = AboutEventsImpl( bindEvent = bindEvent, openPolicyPrivacyEvent = confidentialityPolicy.throttleFirstClicks(), openProcessingPersDataEvent = personalDataProtection.throttleFirstClicks(), unbindEvent = unBindEvent)
Di sini kita melihat pembuatan kelas yang bertanggung jawab untuk menerima acara dari pengguna. Dan unbind dan bind hanya menyaring peristiwa siklus hidup yang kami ambil menggunakan perpustakaan Navi Trello.
fun <T> NaviComponent.observe(event: Event<T>): Observable<T> = RxNavi.observe(this, event) val unBindEvent: Observable<*> = observe(Event.DESTROY_VIEW) val bindEvent: Observable<*> = Observable.just(true) val bindEvent = observe(Event.POST_CREATE)
Antarmuka Acara menggambarkan peristiwa layar tertentu, plus itu harus mewarisi BaseEvents. Berikut ini selalu merupakan implementasi dari antarmuka. Dalam hal ini, peristiwa-peristiwa tersebut ternyata merupakan satu-satu dengan yang datang dari layar, tetapi Anda harus menyimpan dua acara secara bersamaan.
Misalnya, peristiwa pemuatan layar saat membuka dan memuat ulang jika terjadi kesalahan harus digabungkan menjadi satu - hanya memuat layar.
interface AboutEvents : BaseEvents { val bindEvent: EventObservable val openPolicyPrivacyEvent: EventObservable val openProcessingPersDataEvent: EventObservable } class AboutEventsImpl(override val bindEvent: EventObservable, override val openPolicyPrivacyEvent: EventObservable, override val openProcessingPersDataEvent: EventObservable, override val unbindEvent: EventObservable) : AboutEvents
Kami kembali ke fragmen dan menggabungkan semuanya! Kami meminta komponen untuk membuat dan mengembalikan binder kepada kami, lalu kami memanggil metode bind di atasnya, di mana kami melewati objek yang menonton acara layar.
component.binder().bind(events)
Kami telah menulis proyek tentang arsitektur ini selama sekitar dua tahun sekarang. Dan tidak ada batasan untuk kebahagiaan manajer dalam kecepatan berbagi fitur! Mereka tidak punya waktu untuk membuat yang baru, karena kita sudah menyelesaikan yang lama. Arsitekturnya sangat fleksibel dan memungkinkan Anda menggunakan kembali banyak kode.
Kerugian dari arsitektur ini bisa disebut non-konservasi negara. Kami tidak memiliki keseluruhan model yang menggambarkan keadaan layar, seperti pada MVI, tetapi kami dapat mengatasinya. Seperti - lihat di bawah.
Bab 4. Bonus
Saya pikir semua orang tahu masalah analitik: tidak ada yang suka menulisnya, karena ia merayapi semua lapisan dan menghilangkan tantangan. Beberapa waktu lalu, dan kami harus menghadapinya. Namun berkat arsitektur kami, implementasi yang sangat indah diperoleh.
Jadi, apa ide saya: analytics biasanya memberikan respons terhadap tindakan pengguna. Dan kami hanya memiliki kelas yang mengakumulasi tindakan pengguna. Ok, mari kita mulai.
Langkah 1 Kami sedikit mengubah kelas dasar BaseEventsBinder dengan membungkus peristiwa di trackAnalytics:
abstract class BaseEventsBinder<in EVENTS : BaseEvents>( private val trackAnalytics: TrackAnalytics<EVENTS> = EmptyAnalyticsTracker(), private val uiScheduler: Scheduler = AndroidSchedulers.mainThread()) { @SuppressLint("CheckResult") fun bind(events: EVENTS) { bindInternal(trackAnalytics(events)).observeOn(uiScheduler) .takeUntil(events.unbindEvent) .subscribe(Action::invoke) } protected abstract fun bindInternal(events: EVENTS): Observable<Action> }
Langkah 2 Kami membuat implementasi yang stabil dari variabel trackAnalytics untuk menjaga kompatibilitas ke belakang dan tidak mematahkan ahli waris yang belum membutuhkan analitik:
interface TrackAnalytics<EVENTS : BaseEvents> { operator fun invoke(events: EVENTS): EVENTS } class EmptyAnalyticsTracker<EVENTS : BaseEvents> : TrackAnalytics<EVENTS> { override fun invoke(events: EVENTS): EVENTS = events }
Langkah 3 Kami menulis implementasi antarmuka TrackAnalytics untuk layar yang diinginkan - misalnya, untuk layar daftar proyek:
class TrackProjectsEvents : TrackAnalytics<ProjectsEvents> { override fun invoke(events: ProjectsEvents): ProjectsEvents { return object : ProjectsEvents by events { override val boardClickEvent = events.boardClickEvent.trackTypedEvent { allProjectsProjectClick(it.title) } override val openBoardCreationEvent = events.openBoardCreationEvent.trackEvent { allProjectsAddProjectClick() } override val openCardsSearchEvent = events.openCardsSearchEvent.trackEvent { allProjectsSearchBarClick() } } } }
Di sini kita kembali menggunakan kekuatan Kotlin dalam bentuk delegasi. Kami sudah memiliki pewaris antarmuka yang dibuat oleh kami - dalam hal ini ProjectsEvents. Tetapi untuk beberapa acara, Anda perlu mendefinisikan kembali bagaimana acara berjalan dan menambahkan ikatan di sekitarnya dengan mengirim analitik. Faktanya, trackEvent hanya doOnNext:
inline fun <T> Observable<T>.trackEvent(crossinline event: AnalyticsSpec.() -> Unit): Observable<T> = doOnNext { event(analyticsSpec) } inline fun <T> Observable<T>.trackTypedEvent(crossinline event: AnalyticsSpec.(T) -> Unit): Observable<T> = doOnNext { event(analyticsSpec, it) }
Langkah 4 Masih mentransfer ini ke Binder. Karena kami membangunnya dalam komponen, kami memiliki kesempatan, jika Anda tiba-tiba perlu, untuk menambahkan dependensi tambahan ke konstruktor. Sekarang konstruktor ProjectsEventsBinder akan terlihat seperti ini:
class ProjectsEventsBinder( private val loadItems: LoadItemsActionCreator, private val refreshBoards: ActionOnEvent, private val openBoard: ActionCreator<Observable<BoardId>>, private val openScreen: ActionOnEvent, private val openCardSearch: ActionOnEvent, trackAnalytics: TrackAnalytics<ProjectsEvents>) : BaseEventsBinder<ProjectsEvents>(trackAnalytics)
Anda dapat melihat contoh lain di GitHub .
Tanya Jawab
Bagaimana Anda menjaga status layar?Tidak mungkin. Kami memblokir orientasi. Tetapi kami juga menggunakan argumen / maksud dan menyimpan variabel OPENED_FROM_BACKSTACK di sana. Dan ketika merancang Binder, kami melihatnya. Jika salah memuat data dari jaringan. Jika benar - dari cache. Ini memungkinkan Anda untuk dengan cepat membuat ulang layar.
Untuk semua orang yang menentang pemblokiran orientasi: cobalah untuk menguji dan menyimpan analitik pada seberapa sering pengguna Anda membalikkan ponsel dan berapa banyak yang berada dalam orientasi yang berbeda. Hasilnya mungkin mengejutkan.
Saya tidak ingin menulis komponen, bagaimana saya bisa berteman dengan belati?Saya tidak menyarankan, tetapi jika Anda tidak keberatan menyusun waktu, Anda dapat membuat Komponen melalui belati juga. Tetapi kami tidak mencoba.
Saya tidak menulis di kotlin, apa kesulitan dengan implementasi di Jawa?Semua sama bisa ditulis di Jawa, hanya saja tidak akan terlihat begitu indah.
Jika Anda menyukai artikel ini, bagian selanjutnya adalah tentang cara menulis tes pada arsitektur seperti itu (maka akan menjadi jelas mengapa ada begitu banyak antarmuka). Spoiler - menulis itu mudah dan Anda dapat menulis di semua lapisan kecuali komponennya, tetapi Anda tidak perlu mengujinya, itu hanya membuat objek pengikat.
Terima kasih kepada rekan-rekan dari tim pengembangan ponsel Tinkoff Business untuk bantuan mereka menulis artikel ini.