Holder status dan Status UI

Tetap teratur dengan koleksi Simpan dan kategorikan konten berdasarkan preferensi Anda.

Panduan lapisan UI membahas aliran data searah (UDF) sebagai sarana untuk memproduksi dan mengelola Status UI untuk lapisan UI.

Data mengalir secara searah dari lapisan data ke UI.
Gambar 1: Aliran data searah

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 dan set dari class TextView 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 adalah ScaffoldState untuk composable Scaffold.

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 menghasilkan status UI
Gambar 2: Logika sebagai produser status UI

Logika 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.

Data mengalir dari lapisan penghasil data ke UI
Gambar 3: Penerapan logika di lapisan UI

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 diiterasikan 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":

Aplikasi Now in Android menunjukkan bagaimana tujuan navigasi yang merepresentasikan fungsi aplikasi utama harus memiliki
holder status logika bisnis yang unik.
Gambar 4: 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 ViewModels 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 ulang Activity. Misalnya, di Jetpack Compose, status elemen UI Composable yang dibuat dengan fungsi remembered sering didelegasikan ke rememberSaveable untuk mempertahankan status di seluruh pembuatan ulang Activity. Contoh fungsi tersebut meliputi rememberScaffoldState() dan rememberLazyListState().
  • 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:

Now in Android menggunakan holder status class biasa untuk mengelola logika UI
Gambar 5: 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 adalah remembered di dalam Komposisi dengan membuatnya menggunakan fungsi Composable rememberNiaAppState mengikuti konvensi penamaan Compose. Setelah Activity dibuat ulang, instance sebelumnya akan hilang dan instance baru akan dibuat dengan meneruskan semua dependensinya, sesuai untuk konfigurasi dari pembuatan ulang Activity yang baru. Dependensi ini mungkin baru atau dipulihkan dari konfigurasi sebelumnya. Misalnya, rememberNavController() digunakan dalam konstruktor NiaAppState dan didelegasikan ke rememberSaveable untuk mempertahankan status di seluruh pembuatan ulang Activity.
  • Memiliki referensi ke sumber data cakupan UI: Memberi referensi ke navigationController, Resources, dan jenis cakupan siklus proses serupa lainnya dapat disimpan dengan aman di NiaAppState 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 yang dioperasikan oleh logika.

Singkatnya, diagram di bawah menunjukkan posisi holder status dalam pipeline produksi Status UI:

Data mengalir dari lapisan yang menghasilkan data ke lapisan UI
Gambar 6: Holder status di pipeline produksi Status UI

Pada akhirnya, status UI harus ditempatkan dan dihasilkan dari holder status yang terdekat dengan lokasi penggunaannya. Kurang formal, status seharusnya berada serendah mungkin selagi masih dimiliki dengan semestinya. 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 saja sudah cukup.