Konsep dan penerapan Jetpack Compose
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.
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.
class NewsViewModel(...) : ViewModel() {
val uiState: StateFlow<NewsUiState> = …
}
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>.
class NewsViewModel(...) : ViewModel() {
private val _uiState = MutableStateFlow(NewsUiState())
val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()
...
}
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.
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)
}
}
}
}
}
Menggunakan status UI
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:
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
}
}
}
}
}
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.
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 }
}
}
}
}
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.
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Produksi Status UI
- Holder status dan Status UI {:#mad-arch}
- Panduan untuk arsitektur aplikasi