Panduan lapisan UI membahas aliran data searah (UDF) sebagai sarana untuk memproduksi dan mengelola Status UI untuk lapisan UI.
Bagian ini juga menyoroti manfaat mendelegasikan pengelolaan UDF ke class khusus
yang disebut holder status. Anda dapat menerapkan holder status melalui
ViewModel
atau class biasa. Dokumen ini membahas lebih lanjut holder status
dan peran yang dilakukannya dalam lapisan UI.
Di akhir dokumen ini, Anda akan memiliki pemahaman tentang cara mengelola status aplikasi di lapisan UI; yaitu pipeline produksi status UI. Anda akan dapat memahami dan mengetahui hal-hal berikut:
- Memahami jenis status UI yang ada di lapisan UI.
- Memahami jenis logika yang beroperasi pada status UI tersebut di lapisan UI.
- Mengetahui cara memilih implementasi yang sesuai dari holder status, seperti
ViewModel
atau class sederhana.
Elemen pipeline produksi status UI
Status UI dan logika yang menghasilkannya menentukan lapisan UI.
Status UI
Status UI adalah properti yang mendeskripsikan UI. Ada dua jenis status UI:
- Status UI Layar adalah apa yang perlu Anda tampilkan di layar. Misalnya, class
NewsUiState
dapat berisi artikel berita dan informasi lainnya yang diperlukan untuk merender UI. Status ini biasanya terhubung dengan lapisan hierarki lain karena berisi data aplikasi. - Status elemen UI mengacu pada properti yang bersifat intrinsik pada elemen UI yang
memengaruhi cara renderingnya. Elemen UI dapat ditampilkan atau disembunyikan dan dapat
memiliki font, ukuran font, atau warna font tertentu. Dalam Android View, View
mengelola status ini sendiri karena stateful secara inheren, dengan mengekspos metode untuk
mengubah atau mengkueri statusnya. Contohnya adalah metode
get
danset
dari classTextView
untuk teksnya. Di Jetpack Compose, status berada di luar composable, dan Anda bahkan dapat mengangkatnya dari area sekitar composable ke dalam fungsi composable panggilan atau holder status. Contohnya adalahScaffoldState
untuk composableScaffold
.
Logika
Status UI bukan properti statis, karena data aplikasi dan peristiwa pengguna menyebabkan status UI berubah dari waktu ke waktu. Logika menentukan detail perubahan, termasuk bagian status UI apa yang berubah, alasan UI berubah, dan waktu perubahannya.
Logika dalam aplikasi dapat berupa logika bisnis atau logika UI:
- Logika bisnis adalah penerapan persyaratan produk untuk data aplikasi. Misalnya, mem-bookmark artikel di aplikasi pembaca berita saat pengguna mengetuk tombol. Logika untuk menyimpan bookmark ke file atau database ini biasanya ditempatkan di lapisan domain atau data. Holder status biasanya mendelegasikan logika ini ke lapisan tersebut dengan memanggil metode yang diekspos.
- Logika UI berkaitan dengan cara menampilkan status UI di layar. Misalnya, mendapatkan petunjuk kotak penelusuran yang tepat saat pengguna memilih kategori, men-scroll ke item tertentu dalam daftar, atau logika navigasi ke layar tertentu saat pengguna mengklik tombol.
Siklus proses Android serta jenis status dan logika UI
Lapisan UI memiliki dua bagian: satu dependen dan lainnya tidak bergantung pada siklus proses UI. Pemisahan ini menentukan sumber data yang tersedia untuk setiap bagian, sehingga memerlukan berbagai jenis status dan logika UI.
- Siklus proses UI independen: Bagian lapisan UI ini berhubungan dengan lapisan
penghasil data aplikasi (lapisan data atau domain) dan ditentukan oleh logika
bisnis. Siklus proses, perubahan konfigurasi, dan pembuatan ulang
Activity
di UI dapat memengaruhi apakah pipeline produksi status UI aktif, tetapi tidak memengaruhi validitas data yang dihasilkan. - Siklus proses UI dependen: Bagian lapisan UI ini berhubungan dengan logika UI, dan terpengaruh secara langsung oleh perubahan siklus proses atau konfigurasi. Perubahan tersebut secara langsung memengaruhi validitas sumber data yang dibaca di dalamnya, dan akibatnya statusnya hanya dapat berubah jika siklus prosesnya aktif. Contohnya mencakup izin runtime dan mendapatkan resource yang bergantung pada konfigurasi seperti string yang dilokalkan.
Penjelasan di atas dapat diringkas dengan tabel di bawah:
Siklus Proses UI independen | Siklus Proses UI dependen |
---|---|
Logika bisnis | Logika UI |
Status UI Layar |
Pipeline produksi status UI
Pipeline produksi status UI mengacu pada langkah-langkah yang dilakukan untuk menghasilkan status UI. Langkah-langkah ini mencakup penerapan jenis logika yang ditentukan sebelumnya, dan sepenuhnya bergantung pada kebutuhan UI Anda. Beberapa UI mungkin memanfaatkan bagian pipeline Siklus Proses UI independen dan Siklus Proses UI dependen, atau tidak keduanya.
Sehingga, permutasi berikut dari pipeline lapisan UI akan valid:
Status UI yang dihasilkan dan dikelola oleh UI itu sendiri. Misalnya, penghitung dasar yang sederhana dan dapat digunakan kembali:
@Composable fun Counter() { // The UI state is managed by the UI itself var count by remember { mutableStateOf(0) } Row { Button(onClick = { ++count }) { Text(text = "Increment") } Button(onClick = { --count }) { Text(text = "Decrement") } } }
Logika UI → UI. Misalnya, menampilkan atau menyembunyikan tombol yang memungkinkan pengguna langsung menuju ke bagian atas daftar.
@Composable fun ContactsList(contacts: List<Contact>) { val listState = rememberLazyListState() val isAtTopOfList by remember { derivedStateOf { listState.firstVisibleItemIndex < 3 } } // Create the LazyColumn with the lazyListState ... // Show or hide the button (UI logic) based on the list scroll position AnimatedVisibility(visible = !isAtTopOfList) { ScrollToTopButton() } }
Logika bisnis → UI. Elemen UI yang menampilkan foto pengguna saat ini di layar.
@Composable fun UserProfileScreen(viewModel: UserProfileViewModel = hiltViewModel()) { // Read screen UI state from the business logic state holder val uiState by viewModel.uiState.collectAsStateWithLifecycle() // Call on the UserAvatar Composable to display the photo UserAvatar(picture = uiState.profilePicture) }
Logika bisnis → Logika UI → UI. Elemen UI yang men-scroll untuk menampilkan informasi yang tepat di layar untuk status UI tertentu.
@Composable fun ContactsList(viewModel: ContactsViewModel = hiltViewModel()) { // Read screen UI state from the business logic state holder val uiState by viewModel.uiState.collectAsStateWithLifecycle() val contacts = uiState.contacts val deepLinkedContact = uiState.deepLinkedContact val listState = rememberLazyListState() // Create the LazyColumn with the lazyListState ... // Perform UI logic that depends on information from business logic if (deepLinkedContact != null && contacts.isNotEmpty()) { LaunchedEffect(listState, deepLinkedContact, contacts) { val deepLinkedContactIndex = contacts.indexOf(deepLinkedContact) if (deepLinkedContactIndex >= 0) { // Scroll to deep linked item listState.animateScrollToItem(deepLinkedContactIndex) } } } }
Untuk kasus ketika kedua jenis logika diterapkan ke pipeline produksi status UI, logika bisnis harus selalu diterapkan sebelum logika UI. Mencoba menerapkan logika bisnis setelah logika UI akan menunjukkan bahwa logika bisnis bergantung pada logika UI. Bagian berikut membahas mengapa hal ini menjadi masalah melalui pembahasan mendalam tentang berbagai jenis logika dan holder statusnya.
Holder status dan tanggung jawabnya
Tanggung jawab holder status adalah menyimpan status sehingga aplikasi dapat membacanya. Saat logika diperlukan, holder status akan bertindak sebagai perantara dan memberikan akses ke sumber data yang menghosting logika yang diperlukan. Dengan cara ini, holder status akan mendelegasikan logika ke sumber data yang sesuai.
Hal ini menghasilkan manfaat berikut:
- UI Sederhana: UI hanya mengikat statusnya.
- Kemudahan pemeliharaan: Logika yang ditentukan dalam holder status dapat diiterasi tanpa mengubah UI itu sendiri.
- Kemampuan untuk diuji: UI dan logika produksi statusnya dapat diuji secara independen.
- Keterbacaan: Pembaca kode dapat dengan jelas melihat perbedaan antara kode presentasi UI dan kode produksi status UI.
Terlepas dari ukuran atau cakupannya, setiap elemen UI memiliki hubungan 1:1 dengan holder status yang sesuai. Selain itu, holder status harus dapat menerima dan memproses tindakan pengguna apa pun yang dapat menyebabkan perubahan status UI dan harus menghasilkan perubahan status berikutnya.
Jenis holder status
Serupa dengan jenis status dan logika UI, ada dua jenis holder status di lapisan UI yang ditentukan oleh hubungannya dengan siklus proses UI:
- Holder status logika bisnis.
- Holder status logika UI.
Bagian berikut ini membahas lebih lanjut jenis holder status, dimulai dari holder status logika bisnis.
Logika bisnis dan holder statusnya
Holder status logika bisnis memproses peristiwa pengguna dan mengubah data dari lapisan data atau domain menjadi status UI layar. Untuk memberikan pengalaman pengguna yang optimal saat mempertimbangkan siklus proses Android dan perubahan konfigurasi aplikasi, holder status yang menggunakan logika bisnis harus memiliki properti berikut:
Properti | Detail |
---|---|
Menghasilkan Status UI | Holder status logika bisnis bertanggung jawab untuk menghasilkan status UI untuk UI-nya. Status UI ini sering kali menjadi hasil pemrosesan peristiwa pengguna dan pembacaan data dari domain dan lapisan data. |
Dipertahankan melalui pembuatan ulang aktivitas | Holder status logika bisnis mempertahankan pipeline pemrosesan status dan status mereka di seluruh pembuatan ulang Activity , membantu memberikan pengalaman pengguna yang lancar. Jika holder status tidak dapat dipertahankan dan dibuat ulang (biasanya setelah penghentian proses), holder status harus dapat dengan mudah membuat ulang status terakhirnya untuk memastikan pengalaman pengguna yang konsisten. |
Memiliki status berumur panjang | Holder status logika bisnis sering digunakan untuk mengelola status untuk tujuan navigasi. Oleh karena itu, holder tersebut sering kali mempertahankan statusnya di seluruh perubahan navigasi hingga dihapus dari grafik navigasi. |
Bersifat unik untuk UI-nya dan tidak dapat digunakan kembali | Holder status logika bisnis biasanya menghasilkan status untuk fungsi aplikasi tertentu, misalnya TaskEditViewModel atau TaskListViewModel , sehingga hanya berlaku untuk fungsi aplikasi tersebut. Holder status yang sama dapat mendukung fungsi aplikasi ini di berbagai faktor bentuk. Misalnya, aplikasi versi seluler, TV, dan tablet dapat menggunakan kembali holder status logika bisnis yang sama. |
Misalnya, pertimbangkan tujuan navigasi penulis di aplikasi "Now in Android":
Dalam hal ini, sebagai holder status logika bisnis,
AuthorViewModel
menghasilkan status UI:
@HiltViewModel
class AuthorViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val authorsRepository: AuthorsRepository,
newsRepository: NewsRepository
) : ViewModel() {
val uiState: StateFlow<AuthorScreenUiState> = …
// Business logic
fun followAuthor(followed: Boolean) {
…
}
}
Perhatikan bahwa AuthorViewModel
memiliki atribut yang sudah diuraikan sebelumnya:
Properti | Detail |
---|---|
Menghasilkan AuthorScreenUiState |
AuthorViewModel membaca data dari AuthorsRepository dan NewsRepository , lalu menggunakan data tersebut untuk menghasilkan AuthorScreenUiState . Ini juga menerapkan logika bisnis saat pengguna ingin mengikuti atau berhenti mengikuti Author dengan mendelegasikan ke AuthorsRepository . |
Memiliki akses ke lapisan data | Instance AuthorsRepository dan NewsRepository diteruskan ke instance tersebut dalam konstruktornya, sehingga memungkinkan penerapan logika bisnis dengan mengikuti Author . |
Bertahan dalam pembuatan ulang Activity |
Karena diterapkan dengan ViewModel , data tersebut akan dipertahankan di seluruh pembuatan ulang Activity yang cepat. Dalam kasus penghentian proses, objek SavedStateHandle dapat dibaca untuk memberikan jumlah informasi minimum yang diperlukan untuk memulihkan status UI dari lapisan data. |
Memiliki status berumur panjang | ViewModel diberi cakupan untuk grafik navigasi, sehingga kecuali tujuan penulis dihapus dari grafik navigasi, status UI di uiState StateFlow tetap ada di memori. Penggunaan StateFlow juga menambah manfaat dari penerapan logika bisnis yang menghasilkan status lambat karena status hanya dihasilkan jika ada kolektor status UI. |
Bersifat unik untuk UI-nya | AuthorViewModel hanya berlaku untuk tujuan navigasi penulis dan tidak dapat digunakan kembali di tempat lain. Jika ada logika bisnis yang digunakan kembali di seluruh tujuan navigasi, logika bisnis tersebut harus dienkapsulasi dalam komponen cakupan data atau lapisan domain. |
ViewModel sebagai holder status logika bisnis
Manfaat ViewModel dalam pengembangan Android membuatnya cocok untuk memberikan akses ke logika bisnis dan menyiapkan data aplikasi untuk presentasi di layar. Manfaat tersebut mencakup:
- Operasi yang dipicu oleh ViewModel bertahan dari perubahan konfigurasi.
- Integrasi dengan Navigation:
- Navigation men-cache ViewModel saat layar berada di data sebelumnya. Hal ini penting agar data yang sebelumnya dimuat langsung tersedia saat Anda kembali ke tujuan. Hal ini lebih sulit dilakukan dengan holder status yang mengikuti siklus proses layar composable.
- ViewModel juga dihapus saat tujuan dikeluarkan dari data sebelumnya, sehingga memastikan status Anda dibersihkan secara otomatis. Hal ini berbeda dengan memproses penghapusan composable yang dapat terjadi karena beberapa alasan seperti membuka layar baru, karena perubahan konfigurasi, dll.
- Integrasi dengan library Jetpack lainnya, seperti Hilt.
Logika UI dan holder statusnya
Logika UI adalah logika yang beroperasi pada data yang disediakan UI itu sendiri. Hal ini dapat
berupa status elemen UI, atau pada sumber data UI seperti API izin atau
Resources
. Holder status yang menggunakan logika UI biasanya memiliki
properti berikut:
- Menghasilkan status UI dan mengelola status elemen UI.
- Tidak bertahan dari pembuatan ulang
Activity
: Holder status yang dihosting dalam logika UI sering kali bergantung pada sumber data dari UI itu sendiri, dan upaya mempertahankan informasi ini di seluruh perubahan konfigurasi sering kali menyebabkan kebocoran memori. Jika holder status memerlukan data untuk bertahan di seluruh perubahan konfigurasi, holder status perlu didelegasikan ke komponen lain yang lebih sesuai untuk bertahan dari pembuatan ulangActivity
. Misalnya, di Jetpack Compose, status elemen UI Composable yang dibuat dengan fungsiremembered
sering didelegasikan kerememberSaveable
untuk mempertahankan status di seluruh pembuatan ulangActivity
. Contoh fungsi tersebut meliputirememberScaffoldState()
danrememberLazyListState()
. - Memiliki referensi ke sumber data cakupan UI: Sumber data seperti API siklus proses dan Resource dapat dirujuk dan dibaca dengan aman karena holder status logika UI memiliki siklus proses yang sama dengan UI.
- Dapat digunakan kembali di beberapa UI: Instance yang berbeda-beda dari holder status logika UI yang sama dapat digunakan kembali di berbagai bagian aplikasi. Misalnya, holder status untuk mengelola peristiwa input pengguna bagi grup chip dapat digunakan di halaman penelusuran untuk filter chip, dan juga di kolom "kepada" untuk penerima email.
Holder status logika UI biasanya diterapkan dengan class biasa. Hal ini karena UI itu sendiri bertanggung jawab atas pembuatan holder status logika UI dan holder status logika UI memiliki siklus proses yang sama dengan UI itu sendiri. Di Jetpack Compose, misalnya, holder status adalah bagian dari Komposisi dan mengikuti siklus proses Komposisi.
Hal ini dapat diilustrasikan dalam contoh berikut dalam aplikasi contoh Now in Android:
Aplikasi contoh Now in Android menampilkan panel aplikasi bawah atau kolom samping navigasi untuk navigasinya, bergantung pada ukuran layar perangkat. Layar yang lebih kecil akan menggunakan panel aplikasi bawah, dan layar yang lebih besar akan menggunakan kolom samping navigasi.
Karena logika untuk menentukan elemen UI navigasi yang sesuai yang digunakan dalam
NiaApp
fungsi composable tidak bergantung pada logika bisnis, logika ini dapat dikelola
oleh holder status class biasa yang disebut NiaAppState
:
@Stable
class NiaAppState(
val navController: NavHostController,
val windowSizeClass: WindowSizeClass
) {
// UI logic
val shouldShowBottomBar: Boolean
get() = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact ||
windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact
// UI logic
val shouldShowNavRail: Boolean
get() = !shouldShowBottomBar
// UI State
val currentDestination: NavDestination?
@Composable get() = navController
.currentBackStackEntryAsState().value?.destination
// UI logic
fun navigate(destination: NiaNavigationDestination, route: String? = null) { /* ... */ }
/* ... */
}
Pada contoh di atas, detail terkait NiaAppState
berikut bersifat penting:
- Tidak bertahan dari pembuatan ulang
Activity
:NiaAppState
adalahremembered
di dalam Komposisi dengan membuatnya menggunakanrememberNiaAppState
fungsi Composable mengikuti konvensi penamaan Compose. SetelahActivity
dibuat ulang, instance sebelumnya akan hilang dan instance baru akan dibuat dengan meneruskan semua dependensinya, sesuai untuk konfigurasi dari pembuatan ulangActivity
yang baru. Dependensi ini mungkin baru atau dipulihkan dari konfigurasi sebelumnya. Misalnya,rememberNavController()
digunakan dalam konstruktorNiaAppState
dan didelegasikan kerememberSaveable
untuk mempertahankan status di seluruh pembuatan ulangActivity
. - Memiliki referensi ke sumber data cakupan UI: Memberi referensi ke
navigationController
,Resources
, dan jenis cakupan siklus proses serupa lainnya dapat disimpan dengan aman diNiaAppState
karena memiliki cakupan siklus proses yang sama.
Memilih antara ViewModel dan class biasa untuk holder status
Dari bagian di atas, memilih antara ViewModel
dan holder status class
biasa bergantung pada logika yang diterapkan pada status UI dan sumber data
tempat logika dioperasikan.
Singkatnya, diagram di bawah menunjukkan posisi holder status dalam pipeline produksi Status UI:
Pada akhirnya, Anda harus membuat status UI menggunakan holder status terdekat
dengan tempat penggunaannya. Standarnya, Anda harus mempertahankan status serendah mungkin
sekaligus mempertahankan kepemilikan yang tepat. Jika Anda memerlukan akses ke logika
bisnis dan memerlukan status UI untuk tetap ada selama layar dapat dibuka, bahkan
di seluruh pembuatan ulang Activity
, ViewModel
adalah pilihan tepat untuk penerapan holder status
logika bisnis Anda. Untuk status UI dan logika UI
berumur pendek, class biasa yang siklus prosesnya hanya bergantung pada UI
seharusnya cukup.
Holder status dapat digabung
Holder status dapat bergantung pada holder status lain selama dependensi memiliki masa aktif yang sama atau lebih pendek. Contohnya adalah:
- holder status logika UI dapat bergantung pada holder status logika UI lainnya.
- holder status tingkat layar dapat bergantung pada holder status logika UI.
Cuplikan kode berikut menunjukkan cara DrawerState
Compose bergantung pada
holder status internal lainnya, SwipeableState
, dan cara holder status
logika UI aplikasi dapat bergantung pada DrawerState
:
@Stable
class DrawerState(/* ... */) {
internal val swipeableState = SwipeableState(/* ... */)
// ...
}
@Stable
class MyAppState(
private val drawerState: DrawerState,
private val navController: NavHostController
) { /* ... */ }
@Composable
fun rememberMyAppState(
drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
navController: NavHostController = rememberNavController()
): MyAppState = remember(drawerState, navController) {
MyAppState(drawerState, navController)
}
Contoh dependensi yang aktif lebih lama dibandingkan holder status adalah holder status logika UI yang bergantung pada holder status tingkat layar. Hal itu akan mengurangi penggunaan kembali holder status yang berumur lebih pendek dan memberinya akses ke lebih banyak logika dan status daripada yang benar-benar diperlukan.
Jika holder status berumur pendek memerlukan informasi tertentu dari holder status yang cakupannya lebih tinggi, teruskan hanya informasi yang diperlukan sebagai parameter, bukan meneruskan instance holder status. Misalnya, dalam cuplikan kode berikut, class holder status logika UI menerima hanya yang dibutuhkan sebagai parameter dari ViewModel, bukan meneruskan seluruh instance ViewModel sebagai dependensi.
class MyScreenViewModel(/* ... */) {
val uiState: StateFlow<MyScreenUiState> = /* ... */
fun doSomething() { /* ... */ }
fun doAnotherThing() { /* ... */ }
// ...
}
@Stable
class MyScreenState(
// DO NOT pass a ViewModel instance to a plain state holder class
// private val viewModel: MyScreenViewModel,
// Instead, pass only what it needs as a dependency
private val someState: StateFlow<SomeState>,
private val doSomething: () -> Unit,
// Other UI-scoped types
private val scaffoldState: ScaffoldState
) {
/* ... */
}
@Composable
fun rememberMyScreenState(
someState: StateFlow<SomeState>,
doSomething: () -> Unit,
scaffoldState: ScaffoldState = rememberScaffoldState()
): MyScreenState = remember(someState, doSomething, scaffoldState) {
MyScreenState(someState, doSomething, scaffoldState)
}
@Composable
fun MyScreen(
modifier: Modifier = Modifier,
viewModel: MyScreenViewModel = viewModel(),
state: MyScreenState = rememberMyScreenState(
someState = viewModel.uiState.map { it.toSomeState() },
doSomething = viewModel::doSomething
),
// ...
) {
/* ... */
}
Diagram berikut menunjukkan dependensi antara UI dan berbagai holder status cuplikan kode sebelumnya:
Contoh
Contoh Google berikut menunjukkan penggunaan holder status di lapisan UI. Jelajahi untuk melihat panduan ini dalam praktik:
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Lapisan UI
- Produksi Status UI
- Panduan untuk arsitektur aplikasi