Lapisan UI

Peran UI adalah untuk menampilkan data aplikasi di layar dan juga berfungsi sebagai titik utama interaksi pengguna. Setiap kali data berubah, baik karena interaksi pengguna (seperti menekan tombol) atau input eksternal (seperti respons jaringan), UI harus diupdate untuk mencerminkan perubahan tersebut. Secara efektif, UI adalah representasi visual dari status aplikasi yang diambil dari lapisan data.

Namun, data aplikasi yang Anda dapatkan dari lapisan data biasanya dalam format yang berbeda dari informasi yang perlu ditampilkan. Misalnya, Anda mungkin hanya memerlukan bagian data untuk UI, atau Anda mungkin perlu menggabungkan dua sumber data yang berbeda untuk menyajikan informasi yang relevan bagi pengguna. Terlepas dari logika yang Anda terapkan, Anda harus meneruskan semua informasi yang diperlukan ke UI untuk merender sepenuhnya. Lapisan UI adalah pipeline yang mengonversi perubahan data aplikasi menjadi bentuk yang dapat ditampilkan oleh UI, lalu menampilkannya.

Dalam arsitektur standar, elemen UI lapisan UI bergantung pada holder status, yang kemudian bergantung pada class dari lapisan data atau lapisan domain opsional.
Gambar 1. Peran lapisan UI dalam arsitektur aplikasi.

Studi kasus dasar

Pertimbangkan aplikasi yang mengambil artikel berita untuk dibaca oleh pengguna. Aplikasi ini memiliki layar artikel yang menyajikan artikel untuk dibaca, dan juga memungkinkan pengguna yang login membuat bookmark artikel yang benar-benar menarik. Mengingat mungkin ada banyak artikel kapan saja, pembaca harus dapat mencari artikel menurut kategori. Singkatnya, aplikasi memungkinkan pengguna melakukan hal berikut:

  • Melihat artikel yang tersedia untuk dibaca.
  • Menjelajahi artikel menurut kategori.
  • Login dan membuat bookmark artikel tertentu.
  • Mengakses beberapa fitur premium jika memenuhi syarat.
Gambar 2. Contoh aplikasi berita untuk studi kasus UI.

Bagian berikut menggunakan contoh ini sebagai studi kasus untuk memperkenalkan prinsip-prinsip aliran data searah, serta menggambarkan masalah-masalah yang diatasi dengan bantuan prinsip-prinsip tersebut dalam konteks arsitektur aplikasi untuk lapisan UI.

Arsitektur lapisan UI

Istilah UI mengacu pada elemen UI seperti aktivitas dan fragmen yang menampilkan data, terlepas dari API yang digunakan untuk melakukannya (View atau Jetpack Compose). Karena peran lapisan data adalah menahan, mengelola, dan memberikan akses ke data aplikasi, lapisan UI harus melakukan langkah-langkah berikut:

  1. Gunakan data aplikasi dan ubah menjadi data yang dapat dirender dengan mudah oleh UI.
  2. Gunakan data yang dapat dirender UI dan ubah menjadi elemen UI untuk ditampilkan kepada pengguna.
  3. Gunakan peristiwa input pengguna dari elemen UI yang telah disusun dan cerminkan efeknya dalam data UI sesuai kebutuhan.
  4. Ulangi langkah 1 sampai 3 selama diperlukan.

Bagian selanjutnya dari panduan ini menunjukkan cara menerapkan lapisan UI yang menjalankan langkah-langkah ini. Secara khusus, panduan ini mencakup tugas dan konsep berikut:

  • Cara menentukan status UI.
  • Aliran data searah (UDF) sebagai sarana untuk memproduksi dan mengelola status UI.
  • Cara mengekspos status UI dengan jenis data yang dapat diamati sesuai prinsip UDF.
  • Cara menerapkan UI yang memakai status UI yang dapat diamati.

Yang paling mendasar adalah definisi status UI.

Menentukan status UI

Lihat studi kasus yang diuraikan sebelumnya. Singkatnya, UI menampilkan daftar artikel beserta beberapa metadata untuk setiap artikel. Informasi ini yang ditampilkan aplikasi kepada pengguna adalah status UI.

Dengan kata lain: jika UI adalah apa yang dilihat pengguna, status UI adalah tampilan berdasarkan apa yang diberitahukan aplikasi. Seperti dua sisi koin yang sama, UI adalah representasi visual status UI. Setiap perubahan pada status UI akan segera ditampilkan di UI.

UI adalah hasil dari binding elemen UI di layar dengan status UI.
Gambar 3. UI adalah hasil dari binding elemen UI di layar dengan status UI.

Pertimbangkan studi kasus; untuk memenuhi persyaratan aplikasi Berita, informasi yang diperlukan untuk merender UI sepenuhnya dapat dienkapsulasi dalam class data NewsUiState yang ditentukan sebagai berikut:

data class NewsUiState(
    val isSignedIn: Boolean = false,
    val isPremium: Boolean = false,
    val newsItems: List<NewsItemUiState> = listOf(),
    val userMessages: List<Message> = listOf()
)

data class NewsItemUiState(
    val title: String,
    val body: String,
    val bookmarked: Boolean = false,
    ...
)

Ketetapan

Definisi status UI dalam contoh di atas tidak dapat diubah. Manfaat utama dari hal ini adalah objek yang tidak dapat diubah memberikan jaminan terkait status aplikasi secara instan. Tindakan ini akan mengosongkan UI untuk berfokus pada satu peran: membaca status dan mengubah elemen UI-nya sebagaimana mestinya. Akibatnya, Anda tidak boleh memodifikasi status UI secara langsung di UI, kecuali jika UI tersebut adalah sumber datanya. Melanggar prinsip ini akan menghasilkan beberapa sumber kebenaran untuk informasi yang sama, sehingga mengakibatkan inkonsistensi data dan bug halus.

Misalnya, jika flag bookmarked dalam objek NewsItemUiState dari status UI dalam studi kasus telah diperbarui di class Activity, flag tersebut akan bersaing dengan lapisan data sebagai sumber status bookmark sebuah artikel. Class data yang tidak dapat diubah sangat berguna untuk mencegah jenis antipola ini.

Konvensi penamaan dalam panduan ini

Dalam panduan ini, class status UI diberi nama berdasarkan fungsi layar atau bagian layar yang dijelaskan. Konvensinya adalah sebagai berikut:

fungsi + UiState.

Misalnya, status layar yang menampilkan berita mungkin disebut NewsUiState, dan status item berita dalam daftar item berita mungkin adalah NewsItemUiState.

Mengelola status dengan Aliran Data Searah

Bagian sebelumnya menetapkan bahwa status UI adalah snapshot yang tidak dapat diubah dari detail yang diperlukan oleh UI untuk dirender. Namun, sifat data yang dinamis dalam aplikasi berarti status tersebut dapat berubah dari waktu ke waktu. Hal ini mungkin disebabkan oleh interaksi pengguna atau peristiwa lain yang mengubah data yang mendasari yang digunakan untuk mengisi aplikasi.

Interaksi ini dapat memanfaatkan mediator untuk memprosesnya, dengan menentukan logika untuk diterapkan pada setiap peristiwa dan melakukan transformasi yang diperlukan ke sumber data pendukung untuk membuat status UI. Interaksi ini dan logikanya mungkin ditempatkan di UI itu sendiri, tetapi cara ini bisa jadi sulit karena UI mulai menjadi lebih banyak dari yang seharusnya: UI menjadi pemilik data, pembuat, pengubah, dan lainnya. Selain itu, hal ini dapat memengaruhi kemampuan pengujian karena kode yang dihasilkan adalah penggabungan yang sangat erat tanpa batas yang jelas. Pada akhirnya, UI akan diuntungkan oleh beban yang berkurang. Kecuali status UI sangat sederhana, tanggung jawab penuh UI adalah menggunakan dan menampilkan status UI.

Bagian ini membahas Aliran Data Searah (UFD), yaitu pola arsitektur yang membantu menegakkan pemisahan tanggung jawab yang sehat ini.

Holder status

Class yang bertanggung jawab atas produksi status UI dan berisi logika yang diperlukan untuk tugas tersebut disebut holder status. Holder status tersedia dalam berbagai ukuran, bergantung pada cakupan elemen UI yang sesuai yang dikelolanya, mulai dari satu widget seperti panel aplikasi bawah hingga keseluruhan layar atau tujuan navigasi.

Pada kasus terakhir, implementasi standarnya adalah instance ViewModel, meskipun bergantung pada persyaratan aplikasi, class sederhana mungkin sudah cukup. Misalnya, aplikasi Berita dari studi kasus menggunakan class NewsViewModel sebagai holder status untuk menghasilkan status UI untuk layar yang ditampilkan di bagian tersebut.

Ada banyak cara untuk membuat model kodependensi antara UI dan pembuat statusnya. Namun, karena interaksi antara UI dan class ViewModel-nya sangat dapat dipahami sebagai input peristiwa dan output status berikutnya, hubungan tersebut dapat ditampilkan seperti dalam diagram berikut:

Data aplikasi mengalir dari lapisan data ke ViewModel. Status UI
    mengalir dari ViewModel ke elemen UI, dan peristiwa mengalir dari elemen
UI kembali ke ViewModel.
Gambar 4. Diagram cara kerja UDF dalam arsitektur aplikasi.

Pola aliran status ke bawah dan peristiwa yang mengalir ke atas disebut aliran data searah (UDF). Implikasi dari pola ini untuk arsitektur aplikasi adalah sebagai berikut:

  • ViewModel menyimpan dan mengekspos status yang akan digunakan oleh UI. Status UI adalah data aplikasi yang diubah oleh ViewModel.
  • UI memberi tahu ViewModel tentang peristiwa pengguna.
  • ViewModel menangani tindakan pengguna dan memperbarui status.
  • Status yang diperbarui dimasukkan kembali ke UI untuk dirender.
  • Semua tindakan di atas diulang untuk setiap peristiwa yang menyebabkan mutasi status.

Untuk tujuan atau layar navigasi, ViewModel berfungsi dengan repositori atau class kasus penggunaan untuk mendapatkan data dan mengubahnya menjadi status UI sekaligus menggabungkan efek dari peristiwa yang dapat menyebabkan mutasi status. Studi kasus yang disebutkan sebelumnya berisi daftar artikel, setiap artikel memiliki judul, deskripsi, sumber, nama penulis, tanggal publikasi, dan apakah artikel tersebut di-bookmark atau tidak. UI untuk setiap item artikel akan terlihat seperti ini:

Gambar 5. UI item artikel di aplikasi studi kasus.

Pengguna yang mengajukan permintaan untuk mem-bookmark artikel adalah contoh peristiwa yang dapat menyebabkan mutasi status. Sebagai pembuat status, ViewModel bertanggung jawab untuk menentukan semua logika yang diperlukan untuk mengisi semua kolom dalam status UI dan memproses peristiwa yang diperlukan oleh UI untuk dirender sepenuhnya.

Peristiwa UI terjadi saat pengguna mem-bookmark artikel. ViewModel
    memberi tahu lapisan data tentang perubahan status. Lapisan data mempertahankan
    perubahan data dan memperbarui data aplikasi. Data aplikasi baru dengan
    artikel yang di-bookmark akan diteruskan ke ViewModel, yang kemudian menghasilkan
    status UI baru dan meneruskannya ke elemen UI untuk ditampilkan.
Gambar 6. Diagram yang menggambarkan siklus peristiwa dan data dalam UDF.

Bagian berikut ini membahas peristiwa yang menyebabkan perubahan status dan cara memprosesnya menggunakan UDF lebih lanjut.

Jenis logika

Mem-bookmark artikel adalah contoh logika bisnis karena memberikan nilai bagi aplikasi Anda. Untuk mempelajari hal ini lebih lanjut, lihat halaman lapisan data. Namun, ada berbagai jenis logika yang penting untuk ditentukan:

  • Logika bisnis adalah penerapan persyaratan produk untuk data aplikasi. Seperti yang sudah disebutkan, satu contoh adalah mem-bookmark artikel di aplikasi studi kasus. Logika bisnis biasanya ditempatkan di lapisan domain atau data, tetapi tidak pernah di lapisan UI.
  • Logika perilaku UI atau logika UI adalah cara menampilkan perubahan status di layar. Contohnya termasuk mendapatkan teks yang tepat untuk ditampilkan di layar menggunakan Resources Android, membuka layar tertentu saat pengguna mengklik tombol, atau menampilkan pesan pengguna di layar menggunakan toast atau snackbar.

Logika UI, terutama ketika melibatkan jenis UI seperti Context, harus berada di UI, bukan di ViewModel. Jika UI semakin kompleks dan Anda ingin mendelegasikan logika UI ke class lain untuk mendukung pengujian dan pemisahan fokus, Anda dapat membuat class sederhana sebagai holder status. Class sederhana yang dibuat di UI dapat menggunakan dependensi Android SDK karena mengikuti siklus proses UI; objek ViewModel memiliki masa aktif yang lebih lama.

Untuk informasi selengkapnya tentang holder status dan kecocokannya dengan konteks membantu mem-build UI, lihat panduan Status Jetpack Compose.

Apa alasan menggunakan UDF?

UDF membuat model siklus produksi status seperti yang ditunjukkan pada Gambar 4. UDF juga memisahkan tempat asal perubahan status, tempat perubahan tersebut dilakukan, dan tempat perubahan tersebut akhirnya digunakan. Pemisahan ini memungkinkan UI melakukan persis seperti namanya: menampilkan informasi dengan mengamati perubahan status, dan menyampaikan intent pengguna dengan meneruskan perubahan tersebut ke ViewModel.

Dengan kata lain, UDF memungkinkan hal berikut:

  • Konsistensi data. Ada satu sumber kebenaran untuk UI.
  • Kemampuan untuk diuji. Sumber status diisolasi sehingga dapat diuji secara terpisah dari UI.
  • Kemudahan pemeliharaan. Mutasi status mengikuti pola yang ditetapkan dengan baik di mana mutasi disebabkan oleh peristiwa pengguna dan sumber data yang ditarik dari mutasi tersebut.

Mengekspos status UI

Setelah Anda menentukan status UI dan menentukan cara mengelola produksi status tersebut, langkah berikutnya adalah menampilkan status yang dihasilkan ke UI. Karena Anda menggunakan UDF untuk mengelola produksi status, Anda dapat menganggap status yang dihasilkan sebagai aliran—dengan kata lain, beberapa versi status akan dihasilkan dari waktu ke waktu. Akibatnya, Anda harus memperlihatkan status UI dalam holder data yang dapat diamati seperti LiveData atau StateFlow. Alasannya adalah agar UI dapat bereaksi terhadap perubahan apa pun yang dilakukan dalam status tanpa harus menarik data secara manual langsung dari ViewModel. Jenis ini juga memiliki manfaat karena selalu memiliki versi terbaru status UI yang di-cache, yang berguna untuk pemulihan status cepat setelah perubahan konfigurasi.

View

class NewsViewModel(...) : ViewModel() {

    val uiState: StateFlow<NewsUiState> = …
}

Compose

class NewsViewModel(...) : ViewModel() {

    val uiState: NewsUiState = …
}

Untuk pengantar LiveData sebagai holder data yang dapat diobservasi, lihat codelab ini. Untuk pengantar serupa tentang alur Kotlin, lihat alur Kotlin di Android.

Jika data yang diekspos ke UI relatif sederhana, sebaiknya gabungkan data dalam jenis status UI karena hal ini menyampaikan hubungan antara pengiriman holder status dan layar atau elemen UI yang terkait. Selain itu, seiring dengan semakin kompleksnya elemen UI, akan selalu lebih mudah untuk menambahkan definisi status UI untuk mengakomodasi informasi tambahan yang diperlukan untuk merender elemen UI.

Cara umum untuk membuat aliran UiState adalah dengan mengekspos aliran data pendukung yang dapat diubah sebagai aliran yang tidak dapat diubah dari ViewModel—misalnya, mengekspos MutableStateFlow<UiState> sebagai StateFlow<UiState>.

View

class NewsViewModel(...) : ViewModel() {

    private val _uiState = MutableStateFlow(NewsUiState())
    val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()

    ...

}

Compose

class NewsViewModel(...) : ViewModel() {

    var uiState by mutableStateOf(NewsUiState())
        private set

    ...
}

ViewModel kemudian dapat mengekspos metode yang mengubah status secara internal, dengan memublikasikan update untuk UI yang akan digunakan. Misalnya, kasus saat tindakan asinkron perlu dilakukan; coroutine dapat diluncurkan menggunakan viewModelScope, dan status yang dapat diubah dapat diperbarui setelah selesai.

View

class NewsViewModel(
    private val repository: NewsRepository,
    ...
) : ViewModel() {

    private val _uiState = MutableStateFlow(NewsUiState())
    val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()

    private var fetchJob: Job? = null

    fun fetchArticles(category: String) {
        fetchJob?.cancel()
        fetchJob = viewModelScope.launch {
            try {
                val newsItems = repository.newsItemsForCategory(category)
                _uiState.update {
                    it.copy(newsItems = newsItems)
                }
            } catch (ioe: IOException) {
                // Handle the error and notify the UI when appropriate.
                _uiState.update {
                    val messages = getMessagesFromThrowable(ioe)
                    it.copy(userMessages = messages)
                 }
            }
        }
    }
}

Compose

class NewsViewModel(
    private val repository: NewsRepository,
    ...
) : ViewModel() {

   var uiState by mutableStateOf(NewsUiState())
        private set

    private var fetchJob: Job? = null

    fun fetchArticles(category: String) {
        fetchJob?.cancel()
        fetchJob = viewModelScope.launch {
            try {
                val newsItems = repository.newsItemsForCategory(category)
                uiState = uiState.copy(newsItems = newsItems)
            } catch (ioe: IOException) {
                // Handle the error and notify the UI when appropriate.
                val messages = getMessagesFromThrowable(ioe)
                uiState = uiState.copy(userMessages = messages)
            }
        }
    }
}

Pada contoh di atas, class NewsViewModel mencoba mengambil artikel untuk kategori tertentu, lalu menampilkan hasil upaya—apakah berhasil atau gagal—dalam status UI tempat UI dapat bereaksi terhadapnya dengan tepat. Lihat bagian Menampilkan error di layar untuk mempelajari penanganan error lebih lanjut.

Pertimbangan tambahan

Selain panduan sebelumnya, pertimbangkan hal-hal berikut saat menampilkan status UI:

  • Objek status UI harus menangani status yang terkait satu sama lain. Hal ini menyebabkan inkonsistensi yang lebih sedikit dan membuat kode lebih mudah dipahami. Jika Anda mengekspos daftar item berita dan jumlah bookmark dalam dua aliran yang berbeda, Anda mungkin akan berakhir dalam situasi ketika satu aliran diperbarui dan yang lainnya tidak. Jika Anda menggunakan satu aliran, kedua elemen akan terus diperbarui. Selain itu, beberapa logika bisnis mungkin memerlukan kombinasi sumber. Misalnya, Anda mungkin perlu menampilkan tombol bookmark hanya jika pengguna login dan pengguna tersebut adalah pelanggan layanan berita premium. Anda dapat menentukan class status UI sebagai berikut:

    data class NewsUiState(
        val isSignedIn: Boolean = false,
        val isPremium: Boolean = false,
        val newsItems: List<NewsItemUiState> = listOf()
    )
    
    val NewsUiState.canBookmarkNews: Boolean get() = isSignedIn && isPremium
    

    Dalam deklarasi ini, visibilitas tombol bookmark adalah properti turunan dari dua properti lainnya. Karena logika bisnis semakin kompleks, memiliki class UiState tunggal tempat semua properti langsung tersedia menjadi semakin penting.

  • Status UI: satu atau beberapa aliran? Prinsip panduan utama untuk memilih antara mengekspos status UI dalam satu atau beberapa aliran adalah poin butir sebelumnya: hubungan antara item yang ditampilkan. Keuntungan terbesar dari eksposur aliran tunggal adalah kemudahan dan konsistensi data: pengguna status selalu memiliki informasi terbaru yang tersedia kapan saja. Namun, ada kalanya aliran status terpisah dari ViewModel mungkin sesuai:

    • Jenis data yang tidak terkait: Beberapa status yang diperlukan untuk merender UI mungkin tidak saling bergantung satu sama lain. Dalam kasus semacam ini, biaya menggabungkan berbagai status ini mungkin lebih besar daripada manfaatnya, terutama jika salah satu status ini diperbarui lebih sering daripada yang lain.

    • Diffing UiState: Semakin banyak kolom dalam objek UiState, makin besar kemungkinan aliran data akan dimunculkan sebagai hasil dari salah satu kolomnya sedang diperbarui. Karena tampilan tidak memiliki mekanisme diffing untuk memahami apakah pengiriman berturut-turut berbeda atau sama, setiap pengiriman menyebabkan pembaruan tampilan. Artinya, mitigasi menggunakan Flow API atau metode seperti distinctUntilChanged() pada LiveData mungkin diperlukan.

Menggunakan status UI

Untuk menggunakan aliran objek UiState di UI, Anda menggunakan operator terminal untuk jenis data yang dapat diamati yang Anda gunakan. Misalnya, untuk LiveData, Anda menggunakan metode observe(), dan untuk alur Kotlin, Anda menggunakan metode collect() atau variasinya.

Saat menggunakan holder data yang dapat diamati di UI, pastikan Anda mempertimbangkan siklus proses UI. Hal ini penting karena UI tidak boleh mengamati status UI saat tampilan tidak ditampilkan kepada pengguna. Untuk mempelajari topik ini lebih lanjut, lihat postingan blog ini. Saat menggunakan LiveData, LifecycleOwner secara implisit menangani masalah siklus proses. Saat menggunakan alur, sebaiknya tangani ini dengan cakupan coroutine yang sesuai dan repeatOnLifecycle API:

View

class NewsActivity : AppCompatActivity() {

    private val viewModel: NewsViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Update UI elements
                }
            }
        }
    }
}

Compose

@Composable
fun LatestNewsScreen(
    viewModel: NewsViewModel = viewModel()
) {
    // Show UI elements based on the viewModel.uiState
}

Menampilkan operasi yang sedang berlangsung

Cara mudah untuk merepresentasikan status pemuatan di class UiState adalah dengan kolom boolean:

data class NewsUiState(
    val isFetchingArticles: Boolean = false,
    ...
)

Nilai flag ini menunjukkan ada tidaknya status progres di UI.

View

class NewsActivity : AppCompatActivity() {

    private val viewModel: NewsViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Bind the visibility of the progressBar to the state
                // of isFetchingArticles.
                viewModel.uiState
                    .map { it.isFetchingArticles }
                    .distinctUntilChanged()
                    .collect { progressBar.isVisible = it }
            }
        }
    }
}

Compose

@Composable
fun LatestNewsScreen(
    modifier: Modifier = Modifier,
    viewModel: NewsViewModel = viewModel()
) {
    Box(modifier.fillMaxSize()) {

        if (viewModel.uiState.isFetchingArticles) {
            CircularProgressIndicator(Modifier.align(Alignment.Center))
        }

        // Add other UI elements. For example, the list.
    }
}

Menampilkan error di layar

Menampilkan error di UI mirip dengan menampilkan operasi yang sedang berlangsung karena keduanya direpresentasikan dengan mudah oleh nilai boolean yang menunjukkan kehadiran atau ketiadaannya. Akan tetapi, error juga dapat mencakup pesan terkait untuk disampaikan kembali ke pengguna, atau tindakan yang terkait dengan percobaan kembali operasi yang gagal. Oleh karena itu, saat operasi yang sedang berlangsung sedang memuat atau tidak memuat, status error mungkin perlu dimodelkan dengan class data yang menghosting metadata yang sesuai untuk konteks error.

Misalnya, perhatikan contoh dari bagian sebelumnya yang menampilkan status progres saat mengambil artikel. Jika operasi ini menghasilkan error, Anda mungkin ingin menampilkan satu atau beberapa pesan kepada pengguna yang menjelaskan error tersebut.

data class Message(val id: Long, val message: String)

data class NewsUiState(
    val userMessages: List<Message> = listOf(),
    ...
)

Pesan error mungkin ditampilkan kepada pengguna dalam bentuk elemen UI seperti snackbar. Karena hal ini terkait dengan peristiwa UI yang dihasilkan dan digunakan, lihat halaman peristiwa UI untuk mempelajari lebih lanjut.

Threading dan konkurensi

Setiap pekerjaan yang dilakukan di ViewModel harus main-safe—aman untuk dipanggil dari thread utama. Hal ini karena lapisan data dan domain bertanggung jawab untuk memindahkan pekerjaan ke thread yang berbeda.

Jika ViewModel menjalankan operasi yang berjalan lama, tugas tersebut juga bertanggung jawab untuk memindahkan logika tersebut ke thread latar belakang. Coroutine Kotlin adalah cara bagus untuk mengelola operasi serentak, dan Komponen Arsitektur Jetpack menyediakan dukungan bawaan untuk hal itu. Untuk mempelajari penggunaan coroutine di aplikasi Android lebih lanjut, lihat Coroutine Kotlin di Android.

Perubahan dalam navigasi aplikasi sering kali didorong oleh pengiriman seperti peristiwa. Misalnya, setelah class SignInViewModel menjalankan login, kolom UiState mungkin memiliki kolom isSignedIn yang ditetapkan ke true. Pemicu seperti ini harus digunakan seperti yang tercakup dalam bagian Menggunakan status UI di atas, kecuali bahwa penerapan konsumsi harus mematuhi Komponen navigasi.

Paging

Library Paging digunakan di UI dengan jenis yang disebut PagingData. Karena PagingData mewakili dan berisi item yang dapat berubah dari waktu ke waktu—dengan kata lain, jenis ini tidak dapat diubah—seharusnya tidak ditampilkan dalam status UI yang tidak dapat diubah. Sebagai gantinya, Anda harus mengeksposnya dari ViewModel secara independen di alirannya sendiri. Lihat codelab Paging Android untuk contoh spesifik tentang hal ini.

Animasi

Untuk memberikan transisi navigasi level atas yang lancar, Anda mungkin harus menunggu layar kedua memuat data sebelum memulai animasi. Framework tampilan Android menyediakan hook untuk menunda transisi antara tujuan fragmen dengan postponeEnterTransition() dan startPostponedEnterTransition() API. API ini menyediakan cara untuk memastikan bahwa elemen UI di layar kedua (biasanya gambar yang diambil dari jaringan) siap ditampilkan sebelum UI menganimasikan transisi ke layar tersebut. Untuk detail dan detail implementasi selengkapnya, lihat contoh Android Motion.

Contoh

Contoh Google berikut menunjukkan penggunaan lapisan UI. Jelajahi untuk melihat panduan ini dalam praktik: