Meskipun migrasi dari View ke Compose sepenuhnya berkaitan dengan UI, ada banyak hal yang perlu dipertimbangkan untuk melakukan migrasi yang aman dan bertahap. Halaman ini berisi beberapa pertimbangan saat memigrasikan aplikasi berbasis View ke Compose.
Memigrasikan tema aplikasi
Desain Material adalah sistem desain yang direkomendasikan untuk penerapan tema aplikasi Android.
Untuk aplikasi berbasis View, tersedia tiga versi Material:
- Desain Material 1 menggunakan
library AppCompat (yaitu
Theme.AppCompat.*
) - Desain Material 2 menggunakan
library MDC-Android
(yaitu
Theme.MaterialComponents.*
) - Desain Material 3 menggunakan
library MDC-Android (yaitu
Theme.Material3.*
)
Untuk aplikasi Compose, tersedia dua versi Material:
- Desain Material 2 menggunakan
library Compose Material
(yaitu
androidx.compose.material.MaterialTheme
) - Desain Material 3 menggunakan
library Compose Material 3
(yaitu
androidx.compose.material3.MaterialTheme
)
Sebaiknya gunakan versi terbaru (Material 3) jika sistem desain aplikasi Anda dapat melakukannya. Tersedia panduan migrasi untuk View dan Compose:
- Material 1 ke Material 2 di View
- Material 2 ke Material 3 di View
- Material 2 ke Material 3 di Compose
Saat membuat layar baru di Compose, terlepas dari versi Desain Material
yang Anda gunakan, pastikan Anda menerapkan MaterialTheme
sebelum
composable mana pun yang membuat UI dari library Compose Material. Komponen Material
(Button
, Text
, dll.) bergantung pada MaterialTheme
yang ada dan
perilakunya tidak ditentukan tanpanya.
Semua
sampel Jetpack Compose
menggunakan tema Compose khusus yang dibuat berdasarkan MaterialTheme
.
Lihat Mendesain sistem di Compose dan Memigrasikan tema XML ke Compose untuk mempelajari lebih lanjut.
Navigasi
Jika Anda menggunakan komponen Navigation di aplikasi, lihat Menavigasi dengan Compose - Interoperabilitas dan Melakukan migrasi Navigasi Jetpack ke Navigation Compose untuk mengetahui informasi selengkapnya.
Menguji UI Compose/Views campuran
Setelah memigrasikan bagian aplikasi ke Compose, pengujian sangat penting dilakukan untuk memastikan Anda tidak merusak apa pun.
Saat aktivitas atau fragmen menggunakan Compose, Anda harus menggunakan
createAndroidComposeRule
,
bukan menggunakan ActivityScenarioRule
. createAndroidComposeRule
mengintegrasikan
ActivityScenarioRule
dengan ComposeTestRule
yang memungkinkan Anda menguji kode Compose dan
View secara bersamaan.
class MyActivityTest { @Rule @JvmField val composeTestRule = createAndroidComposeRule<MyActivity>() @Test fun testGreeting() { val greeting = InstrumentationRegistry.getInstrumentation() .targetContext.resources.getString(R.string.greeting) composeTestRule.onNodeWithText(greeting).assertIsDisplayed() } }
Lihat Menguji tata letak Compose untuk mempelajari pengujian lebih lanjut. Untuk interoperabilitas dengan framework pengujian UI, lihat interoperabilitas dengan Espresso dan interoperabilitas dengan UiAutomator.
Mengintegrasi Compose dengan arsitektur aplikasi yang sudah ada
Pola arsitektur Aliran Data Searah (UDF) berfungsi lancar dengan Compose. Jika aplikasi menggunakan jenis pola arsitektur lain, seperti Presenter View Model (MVP), sebaiknya migrasikan bagian UI tersebut ke UDF sebelum atau saat mengadopsi Compose.
Menggunakan ViewModel
di Compose
Jika Anda menggunakan library Komponen Arsitektur
ViewModel
Anda dapat mengakses
ViewModel
dari composable mana pun dengan memanggil fungsi
viewModel()
,
seperti yang dijelaskan dalam Compose dan library lainnya.
Saat menggunakan Compose, berhati-hatilah saat menggunakan jenis ViewModel
yang sama dalam berbagai composable karena elemen ViewModel
mengikuti cakupan siklus proses View. Cakupan
akan berupa aktivitas host, fragmen, atau grafik navigasi jika
library Navigasi digunakan.
Misalnya, jika composable dapat dihosting dalam aktivitas, viewModel()
selalu
menampilkan instance yang sama yang hanya akan dihapus saat aktivitas telah selesai.
Pada contoh berikut, pengguna yang sama ("user1") disapa dua kali karena
instance GreetingViewModel
yang sama digunakan kembali di semua composable dalam
aktivitas host. Instance ViewModel
pertama yang telah dibuat digunakan kembali di
composable lain.
class GreetingActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { Column { GreetingScreen("user1") GreetingScreen("user2") } } } } } @Composable fun GreetingScreen( userId: String, viewModel: GreetingViewModel = viewModel( factory = GreetingViewModelFactory(userId) ) ) { val messageUser by viewModel.message.observeAsState("") Text(messageUser) } class GreetingViewModel(private val userId: String) : ViewModel() { private val _message = MutableLiveData("Hi $userId") val message: LiveData<String> = _message } class GreetingViewModelFactory(private val userId: String) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T { return GreetingViewModel(userId) as T } }
Karena grafik navigasi juga mencakup elemen ViewModel
, composable yang merupakan
tujuan dalam grafik navigasi memiliki instance ViewModel
yang berbeda.
Dalam hal ini, ViewModel
dimasukkan ke siklus proses tujuan dan
akan dihapus saat tujuan dihapus dari backstack. Pada contoh berikut,
saat pengguna membuka
layar Profil, instance baru GreetingViewModel
akan dibuat.
@Composable fun MyApp() { NavHost(rememberNavController(), startDestination = "profile/{userId}") { /* ... */ composable("profile/{userId}") { backStackEntry -> GreetingScreen(backStackEntry.arguments?.getString("userId") ?: "") } } }
Sumber kebenaran status
Saat Anda mengadopsi Compose di satu bagian UI, Compose dan
kode sistem View mungkin perlu membagikan data. Jika memungkinkan, sebaiknya Anda
merangkum status bersama tersebut di class lain yang mengikuti praktik terbaik
UDF dan digunakan oleh kedua platform tersebut, misalnya, di ViewModel
yang mengekspos aliran data dari
data bersama untuk memberikan pembaruan data.
Namun, hal ini tidak selalu mungkin jika data yang akan dibagikan dapat diubah atau terikat erat dengan elemen UI. Dalam hal ini, satu sistem harus menjadi sumber kebenaran, dan sistem tersebut perlu membagikan setiap update data ke sistem lain. Sebagai aturan umum, sumber kebenaran harus dimiliki oleh elemen mana pun yang lebih dekat dengan root hierarki UI.
Compose sebagai sumber kebenaran
Gunakan fungsi
SideEffect
untuk memublikasikan status Compose ke kode non-Compose. Dalam hal ini,
sumber kebenaran disimpan dalam composable mengirim update status.
Contohnya, library analisis Anda dapat digunakan untuk mengelompokkan populasi
pengguna dengan melampirkan metadata khusus (properti pengguna pada contoh ini)
ke semua peristiwa analisis berikutnya. Untuk memberitahukan jenis pengguna dari
pengguna saat ini ke library analisis Anda, gunakan SideEffect
untuk memperbarui nilainya.
@Composable fun rememberFirebaseAnalytics(user: User): FirebaseAnalytics { val analytics: FirebaseAnalytics = remember { FirebaseAnalytics() } // On every successful composition, update FirebaseAnalytics with // the userType from the current User, ensuring that future analytics // events have this metadata attached SideEffect { analytics.setUserProperty("userType", user.userType) } return analytics }
Untuk informasi selengkapnya, lihat Efek samping di Compose.
Sistem View sebagai sumber kebenaran
Jika sistem View memiliki status dan membagikannya dengan Compose, sebaiknya
gabungkan status dalam objek mutableStateOf
agar Compose aman dari thread. Jika Anda menggunakan pendekatan ini, fungsi composable akan disederhanakan karena
tidak lagi memiliki sumber kebenaran, tetapi sistem View harus mengupdate status
yang dapat diubah dan View yang menggunakan status tersebut.
Dalam contoh berikut, CustomViewGroup
berisi TextView
dan
ComposeView
dengan komponen TextField
di dalamnya. TextView
harus menampilkan
konten yang diketik pengguna di TextField
.
class CustomViewGroup @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : LinearLayout(context, attrs, defStyle) { // Source of truth in the View system as mutableStateOf // to make it thread-safe for Compose private var text by mutableStateOf("") private val textView: TextView init { orientation = VERTICAL textView = TextView(context) val composeView = ComposeView(context).apply { setContent { MaterialTheme { TextField(value = text, onValueChange = { updateState(it) }) } } } addView(textView) addView(composeView) } // Update both the source of truth and the TextView private fun updateState(newValue: String) { text = newValue textView.text = newValue } }
Memigrasikan UI bersama
Jika bermigrasi secara bertahap ke Compose, Anda mungkin perlu menggunakan elemen UI bersama di sistem Compose dan View. Misalnya, jika aplikasi Anda memiliki
komponen CallToActionButton
khusus, Anda mungkin harus menggunakannya di layar Compose
dan juga layar berbasis View.
Di Compose, elemen UI bersama akan menjadi composable dan dapat digunakan kembali di seluruh aplikasi, apa pun elemen yang telah diberi gaya menggunakan XML atau menjadi tampilan khusus. Misalnya,
Anda akan membuat composable CallToActionButton
untuk komponen Button
pesan
ajakan (CTA) khusus.
Untuk menggunakan composable di layar berbasis View, buat wrapper tampilan khusus yang
diperluas dari AbstractComposeView
. Pada composable
Content
yang telah diganti, tempatkan composable yang sudah Anda buat dalam tema Compose,
seperti yang ditunjukkan pada contoh di bawah:
@Composable fun CallToActionButton( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, ) { Button( colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.secondary ), onClick = onClick, modifier = modifier, ) { Text(text) } } class CallToActionViewButton @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : AbstractComposeView(context, attrs, defStyle) { var text by mutableStateOf("") var onClick by mutableStateOf({}) @Composable override fun Content() { YourAppTheme { CallToActionButton(text, onClick) } } }
Perhatikan bahwa parameter composable akan menjadi variabel yang dapat diubah di dalam tampilan khusus. Ini akan membuat tampilan CallToActionViewButton
khusus menjadi dapat di-inflate dan digunakan,
seperti tampilan tradisional. Lihat contohnya dengan View Binding
di bawah:
class ViewBindingActivity : ComponentActivity() { private lateinit var binding: ActivityExampleBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityExampleBinding.inflate(layoutInflater) setContentView(binding.root) binding.callToAction.apply { text = getString(R.string.greeting) onClick = { /* Do something */ } } } }
Jika komponen khusus berisi status yang dapat berubah, lihat Sumber status kebenaran.
Memprioritaskan status pemisahan dari presentasi
Biasanya, View
adalah stateful. View
mengelola kolom yang
menjelaskan item yang akan ditampilkan, selain cara menampilkannya. Saat Anda
mengonversi View
ke Compose, lihat cara memisahkan data yang dirender
untuk mencapai aliran data searah, seperti yang dijelaskan lebih lanjut dalam pengangkatan status.
Misalnya, View
memiliki properti visibility
yang menjelaskan apakah properti tersebut terlihat, tidak terlihat, atau hilang. Ini adalah properti yang melekat pada View
. Meskipun
bagian kode lainnya dapat mengubah visibilitas View
, hanya View
yang benar-benar mengetahui visibilitasnya saat ini. Logika untuk memastikan bahwa
View
dapat terlihat cenderung rentan terhadap error dan sering dikaitkan dengan View
itu sendiri.
Sebaliknya, Compose memudahkan untuk menampilkan composable yang benar-benar berbeda menggunakan logika bersyarat di Kotlin:
@Composable fun MyComposable(showCautionIcon: Boolean) { if (showCautionIcon) { CautionIcon(/* ... */) } }
Dari segi desain, CautionIcon
tidak harus mengetahui atau peduli alasan produk tersebut ditampilkan,
dan tidak ada konsep visibility
: baik dalam Komposisi atau tidak
di dalamnya.
Dengan memisahkan pengelolaan status dan logika presentasi secara rapi, Anda dapat lebih bebas mengubah cara menampilkan konten sebagai konversi status ke UI. Dapat mengangkat status saat diperlukan juga membuat composable lebih dapat digunakan kembali karena kepemilikan status lebih fleksibel.
Mempromosikan komponen yang dienkapsulasi dan dapat digunakan kembali
Elemen View
sering kali memiliki gambaran posisinya: di dalam Activity
,
Dialog
, Fragment
, atau di dalam hierarki View
lainnya. Karena elemen
sering kali di-inflate dari file tata letak statis, struktur keseluruhan
View
cenderung sangat kaku. Ini menghasilkan pengaitan erat dan mempersulit
View
diubah atau digunakan ulang.
Misalnya, View
kustom dapat mengasumsikan bahwa tampilan tersebut memiliki tampilan turunan dari jenis
tertentu dengan ID tertentu, serta mengubah propertinya secara langsung sebagai respons terhadap beberapa
tindakan. Ini akan mengaitkan erat elemen View
tersebut secara bersamaan: View
kustom
dapat mengalami error atau rusak jika tidak dapat menemukan turunan, dan turunan mungkin tidak dapat
digunakan kembali tanpa induk View
kustom.
Masalah di Compose dengan composable yang dapat digunakan kembali menjadi lebih sedikit. Induk dapat dengan mudah menentukan status dan callback agar Anda dapat menulis composable yang dapat digunakan kembali tanpa harus mengetahui lokasi penggunaannya secara persis.
@Composable fun AScreen() { var isEnabled by rememberSaveable { mutableStateOf(false) } Column { ImageWithEnabledOverlay(isEnabled) ControlPanelWithToggle( isEnabled = isEnabled, onEnabledChanged = { isEnabled = it } ) } }
Pada contoh di atas, ketiga bagian lebih dienkapsulasi dan tidak terlalu dikaitkan:
ImageWithEnabledOverlay
hanya perlu mengetahui statusisEnabled
saat ini. Tidak perlu mengetahui bahwaControlPanelWithToggle
ada, atau bahkan cara mengontrolnya.ControlPanelWithToggle
tidak tahu bahwaImageWithEnabledOverlay
ada. Mungkin tidak ada cara, satu, atau bahkan beberapa cara agarisEnabled
ditampilkan, danControlPanelWithToggle
tidak perlu diubah.Untuk induk, tidak masalah seberapa dalam
ImageWithEnabledOverlay
atauControlPanelWithToggle
bertingkat. Turunan tersebut dapat menganimasikan perubahan, menukar konten, atau meneruskan konten ke turunan lain.
Pola ini dikenal sebagai inversi kontrol yang dapat Anda baca lebih lanjut
dalam dokumentasi CompositionLocal
.
Menangani perubahan ukuran layar
Memiliki berbagai resource untuk ukuran jendela yang berbeda adalah salah satu cara utama
untuk membuat tata letak View
yang responsif. Meskipun resource yang memenuhi syarat masih menjadi opsi
untuk keputusan tata letak level layar, Compose memudahkan Anda mengubah
keseluruhan tata letak dalam kode dengan logika bersyarat normal. Lihat Menggunakan class ukuran
jendela untuk mempelajari lebih lanjut.
Selain itu, lihat Mendukung berbagai ukuran layar untuk mempelajari teknik yang ditawarkan Compose untuk membuat UI adaptif.
Scrolling bertingkat dengan Views
Untuk informasi selengkapnya tentang cara mengaktifkan interop scrolling bertingkat antara elemen View yang dapat di-scroll dan composable yang dapat di-scroll, yang disusun bertingkat di kedua arah, baca Interop scrolling bertingkat.
Compose di RecyclerView
Composable di RecyclerView
berperforma tinggi sejak RecyclerView
versi
1.3.0-alpha02. Pastikan Anda menggunakan setidaknya versi 1.3.0-alpha02
RecyclerView
untuk melihat manfaat tersebut.
WindowInsets
interop dengan View
Anda mungkin perlu mengganti inset default saat layar memiliki kode View dan Compose dalam hierarki yang sama. Dalam hal ini, Anda harus jelas tentang mana yang harus menggunakan inset, dan mana yang harus mengabaikannya.
Misalnya, jika tata letak terluar adalah tata letak Android View, Anda harus
menggunakan inset dalam sistem View dan mengabaikannya untuk Compose.
Atau, jika tata letak terluar Anda adalah composable, Anda harus menggunakan
inset di Compose, dan menambahkan padding pada composable AndroidView
sebagaimana mestinya.
Secara default, setiap ComposeView
menggunakan semua inset pada
tingkat konsumsi WindowInsetsCompat
. Untuk mengubah perilaku default ini, tetapkan
ComposeView.consumeWindowInsets
ke false
.
Untuk informasi selengkapnya, baca dokumentasi WindowInsets
di Compose.
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Menampilkan emoji
- Desain Material 2 di Compose
- Inset jendela di Compose