Tujuan artikel ini
Saya tidak akan menyelidiki bagaimana MVI diimplementasikan secara teknis (ada lebih dari satu cara dan masing-masing memiliki pro dan kontra). Tujuan utama saya dalam artikel singkat adalah untuk menarik minat Anda mempelajari topik ini di masa mendatang dan mungkin mendorong Anda untuk menerapkan pola ini pada proyek pertempuran Anda, atau setidaknya memeriksanya di pekerjaan rumah Anda.
Masalah apa yang bisa Anda hadapi
Teman saya, bayangkan situasi ini, kami memiliki tampilan antarmuka yang
dapat digunakan:
interface ComplexView {
fun showLoading()
fun hideLoading()
fun showBanner()
fun hideBanner()
fun dataLoaded(names: List<String>)
fun showTakeCreditDialog()
fun hideTakeCreditDialog()
}
Sekilas, tidak ada yang rumit. Anda cukup memilih entitas terpisah untuk bekerja dengan tampilan ini, menyebutnya presenter (voila, inilah MVP), dan ini adalah
Dan inilah presenternya sendiri:
interface Presenter {
fun onLoadData(dataKey: String)
fun onLoadCredit()
}
Sederhana saja, tampilan menarik metode penyaji ketika diperlukan untuk memuat data, penyaji, pada gilirannya, memiliki hak untuk menarik tampilan untuk menampilkan informasi yang dimuat, serta menampilkan kemajuan. Tetapi di sini
Misalnya, kami ingin menampilkan dialog yang menawarkan
view.hideTakeCreditDialog ()
Tetapi pada saat yang sama, Anda tidak boleh lupa bahwa saat menampilkan dialog, Anda perlu menyembunyikan pemuatan dan tidak menampilkannya saat Anda memiliki dialog di layar. Selain itu, ada metode yang menampilkan spanduk, yang tidak boleh kita panggil saat dialog ditampilkan (atau menutup dialog dan hanya setelah itu menampilkan spanduk, semuanya tergantung pada persyaratan). Anda memiliki gambar berikut.
Anda tidak boleh menelepon:
view.showBanner ()
view.showLoading ()
Saat dialog ditampilkan. Jika tidak,
Sekarang mari kita pikirkan bersama Anda dan misalkan Anda masih ingin menunjukkan spanduk (persyaratan seperti itu dari sebuah bisnis). Apa yang perlu Anda ingat?
Faktanya adalah ketika Anda memanggil metode ini:
view.showBanner ()
Pastikan untuk menelepon:
view.hideLoading ()
view.hideTakeCreditDialog ()
Sekali lagi, sehingga tidak ada yang melompat di atas elemen lainnya di layar, konsistensi yang terkenal buruk.
Jadi pertanyaannya muncul, siapa yang akan memukul tangan Anda jika Anda melakukan sesuatu yang salah? Jawabannya sederhana - TIDAK ADA . Dalam kesadaran seperti itu, Anda sama sekali tidak memiliki kendali.
Mungkin di masa mendatang Anda perlu menambahkan beberapa fungsionalitas lagi ke tampilan, yang juga akan terkait dengan apa yang sudah ada. Apa kerugian yang kita dapatkan dari ini?
- Mie dari ketergantungan negara elemen yuan
- Logika transisi dari satu kondisi tampilan ke yang lain akan tercoreng di
penyaji - Menambahkan status layar baru cukup sulit, karena ada risiko tinggi
lupa menyembunyikan sesuatu sebelum menampilkan banner atau dialog baru.
Dan Anda dan saya yang menganalisis kasus ketika hanya ada 7 metode dalam tampilan. Dan bahkan di sini ternyata menjadi masalah.
Tapi ada pandangan seperti itu:
interface ChatView : IView<ChatPresenter> {
fun setMessage(message: String)
fun showFullScreenProgressBar()
fun updateExistingMessage(model: ChatMessageModel)
fun hideFullScreenProgressBar()
fun addNewMessage(localMessage: ChatMessageModel)
fun showErrorFromLoading(message: String)
fun moveChatToStart()
fun containsMessage(message: ChatMessageModel): Boolean
fun getChatMessagesSize(): Int fun getLastMessage(): ChatMessageModel?
fun updateMessageStatus(messageId: String, status: ChatMessageStatus)
fun setAutoLoading(autoLoadingEnabled: Boolean)
fun initImageInChat(needImageInChat: Boolean)
fun enableNavigationButton()
fun hideKeyboard()
fun scrollToFirstMessage()
fun setTitle(@StringRes titleRes: Int)
fun setVisibleSendingError(isVisible: Boolean)
fun removeMessage(localId: String)
fun setBottomPadding(hasPadding: Boolean)
fun initMessagesList(pageSize: Int)
fun showToast(@StringRes textRes: Int)
fun openMessageDialog(message: String)
fun showSuccessRating()
fun setRatingAvailability(isEnabled: Boolean)
fun showSuccessRatingWithResult(ratingValue: String)
}
Akan sangat sulit untuk menambahkan sesuatu yang baru di sini atau mengedit yang lama, Anda harus melihat apa yang terhubung dan bagaimana, dan kemudian mulai
MVI
Intinya
Intinya adalah kita memiliki entitas yang disebut negara. Berdasarkan keadaan ini, tampilan akan menampilkan tampilannya. Saya tidak akan mendalami, jadi tugas saya adalah membangkitkan minat Anda, jadi saya akan langsung memberi contoh. Dan di akhir artikel akan ada daftar sumber yang sangat berguna, jika Anda tertarik.
Mari kita ingat posisi kita di awal artikel, kita memiliki tampilan di mana kita menampilkan dialog, spanduk,
data class UIState(
val loading: Boolean = false,
val names: List<String>? = null,
val isBannerShowing: Boolean = false,
val isCreditDialogShowing: Boolean = false
)
Mari tetapkan aturannya, Anda dan saya dapat mengubah tampilan hanya dengan bantuan status ini, akan ada antarmuka seperti itu:
interface ComplexView {
fun renderState(state: UIState)
}
Sekarang mari kita buat satu aturan lagi. Kami dapat menghubungi pemilik negara (dalam kasus kami itu akan menjadi presenter) hanya melalui satu titik masuk. Dengan mengirimkan acara kepadanya. Ada baiknya menyebut peristiwa ini sebagai tindakan.
sealed class UIAction {
class LoadNamesAction(dataKey: String) : UIAction()
object LoadBannerAction : UIAction()
object LoadCreditDialogInfo : UIAction()
}
Hanya saja jangan melempar tomat ke saya untuk kelas tertutup, mereka menyederhanakan kehidupan dalam situasi saat ini, menghilangkan kasta tambahan saat memproses tindakan di presenter, contohnya di bawah ini. Antarmuka presenter akan terlihat seperti ini:
interface Presenter {
fun processAction(action: UIAction)
}
Sekarang mari kita pikirkan bagaimana menghubungkan semuanya:
fun processAction(action: UiAction): UIState {
return when (action) {
is UiAction.LoadNamesAction -> state.copy(
loading = true,
isBannerShowing = false,
isCreditDialogShowing = false
)
is UiAction.LoadBannerAction -> state.copy(
loading = false,
isBannerShowing = true,
isCreditDialogShowing = false
)
is UiAction.LoadCreditDialogInfo -> state.copy(
loading = false,
isBannerShowing = false,
isCreditDialogShowing = true
)
}
}
Jika Anda memperhatikan, aliran dari satu kondisi tampilan ke tampilan lainnya sekarang terjadi di satu tempat dan sudah lebih mudah untuk mengumpulkan gambaran tentang bagaimana semuanya bekerja di kepala Anda.
Ini tidak super mudah, tetapi hidup Anda seharusnya lebih mudah. Plus, dalam contoh saya, ini tidak terlihat, tetapi kita dapat memutuskan bagaimana memproses keadaan baru kita berdasarkan keadaan sebelumnya (ada juga beberapa gagasan untuk mengimplementasikan ini). Belum lagi reusabilitas gila yang dicapai orang-orang di Badoo, salah satu asisten mereka dalam mencapai tujuan ini adalah MVI.
Namun, Anda tidak boleh bersukacita lebih awal, segala sesuatu di dunia ini memiliki pro dan kontra, dan inilah dia
- Pertunjukan roti panggang biasa membuat kita terhibur
- Saat Anda memperbarui satu kotak centang, seluruh status akan disalin lagi dan dikirim ke
tampilan, yaitu, gambar ulang yang tidak perlu akan terjadi jika tidak ada yang dilakukan.
Misalkan kita ingin menampilkan toast android normal, menurut logika saat ini, kita akan menyetel sebuah bendera dalam status kita untuk menampilkan toast kita.
data class UIState(
val showToast: Boolean = false,
)
Pertama
Kami mengambil dan mengubah status di presenter, mengatur showToast = true dan hal paling sederhana yang dapat terjadi adalah rotasi layar. Semuanya hancur,
Nah, yang kedua
Ini sudah menjadi masalah rendering yang tidak perlu dalam tampilan, yang akan terjadi setiap saat bahkan saat hanya satu bidang dalam status berubah. Dan masalah ini diselesaikan dengan beberapa cara yang terkadang bukan yang paling indah (terkadang dengan pemeriksaan yang membosankan sebelum mengeluh tentang arti baru, bahwa ini berbeda dari yang sebelumnya). Tetapi dengan dirilisnya compose dalam versi stabil, masalah ini akan terpecahkan, kemudian teman saya akan tinggal bersama Anda di dunia yang berubah dan bahagia!
Waktu untuk pro:
- Satu pintu masuk ke tampilan
- Kami selalu memiliki kondisi layar saat ini
- Bahkan pada tahap implementasi, Anda harus memikirkan bagaimana satu keadaan akan mengalir
ke yang lain dan apa hubungan di antara mereka - Aliran Data Searah
Cintai android dan jangan pernah kehilangan motivasi Anda!
Daftar inspirasi saya
- www.youtube.com/watch?v=VsStyq4Lzxo&t=592s - Pola UI Deklaratif (Google
I / O'19) - www.youtube.com/watch?v=pXw6r2kAvq8&t=2s - Perjalanan arsitektural oleh Zsolt
Kocsi, Badoo EN
- www.youtube.com/watch?v=hBkQkjWnAjg&t=318s - Cara memasak
MVI yang matang untuk Android - www.youtube.com/watch?v=0IKHxjkgop4 - Mengelola Negara dengan RxJava oleh Jake
Wharton - hannesdorfmann.com/android/model-view-intent - Artikel oleh Hannes Doorfmann