Saat mengadopsi Compose di aplikasi Anda, UI berbasis View dan UI Compose dapat digabungkan. Berikut adalah daftar API, rekomendasi, dan tips untuk mempermudah transisi ke Compose.
Compose di View
Anda dapat menambahkan UI berbasis Compose ke dalam aplikasi yang sudah ada dan menggunakan desain berbasis View.
Untuk membuat layar baru yang sepenuhnya berbasis Compose, minta
aktivitas Anda memanggil metode setContent()
, dan teruskan fungsi composable, yang Anda suka.
class ExampleActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { // In here, we can call composables! MaterialTheme { Greeting(name = "compose") } } } } @Composable fun Greeting(name: String) { Text(text = "Hello $name!") }
Kode ini terlihat seperti yang Anda temukan di aplikasi khusus Compose.
ViewCompositionStrategy untuk ComposeView
Secara default, Compose akan menghapus Komposisi
setiap kali tampilan dilepas dari jendela. Jenis View
UI Compose seperti ComposeView
dan AbstractComposeView
menggunakan ViewCompositionStrategy
yang menentukan perilaku ini.
Secara default, Compose menggunakan
strategi
DisposeOnDetachedFromWindowOrReleasedFromPool
. Namun, nilai default ini mungkin tidak diinginkan dalam beberapa
situasi saat jenis View
UI Compose digunakan di:
Fragmen. Komposisi harus mengikuti siklus proses tampilan fragmen untuk jenis
View
UI Compose untuk menyimpan status.Transisi. Setiap kali UI Compose
View
digunakan sebagai bagian dari transisi, UI tersebut akan dilepaskan dari jendelanya saat transisi dimulai, bukan saat transisi berakhir, sehingga menyebabkan composable Anda dibuang statusnya saat berada di layar.View
kustom yang dikelola siklus proses Anda sendiri.
Dalam beberapa situasi semacam ini, aplikasi juga dapat secara perlahan membocorkan memori dari
instance Komposisi, kecuali jika Anda memanggil
AbstractComposeView.disposeComposition
secara manual.
Untuk menghapus Komposisi secara otomatis saat tidak diperlukan lagi, tetapkan
strategi yang berbeda atau buat strategi sendiri dengan memanggil
metode
setViewCompositionStrategy
. Misalnya, strategi
DisposeOnLifecycleDestroyed
membuang Komposisi saat lifecycle
dihancurkan. Strategi ini sesuai untuk jenis View
UI Compose yang memiliki hubungan 1-1 dengan LifecycleOwner
yang dikenal.
Jika LifecycleOwner
tidak diketahui,
DisposeOnViewTreeLifecycleDestroyed
dapat digunakan.
Lihat cara kerja API ini di ComposeView di Fragmen.
ComposeView di Fragment
Jika Anda ingin menggabungkan konten UI Compose dalam fragmen atau tata letak
View yang sudah ada, gunakan ComposeView
dan panggil
metode
setContent()
. ComposeView
adalah View
Android.
Anda dapat menempatkan ComposeView
di tata letak XML, seperti View
lainnya:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/hello_world"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello Android!" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Dalam kode sumber Kotlin, inflate tata letak dari resource
tata letak yang ditentukan dalam XML. Kemudian, dapatkan
ComposeView
menggunakan ID XML, tetapkan strategi Komposisi yang paling sesuai untuk
View
host, dan panggil setContent()
untuk menggunakan Compose.
class ExampleFragment : Fragment() { private var _binding: FragmentExampleBinding? = null // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentExampleBinding.inflate(inflater, container, false) val view = binding.root binding.composeView.apply { // Dispose of the Composition when the view's LifecycleOwner // is destroyed setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { // In Compose world MaterialTheme { Text("Hello Compose!") } } } return view } override fun onDestroyView() { super.onDestroyView() _binding = null } }
Gambar 1. Ini menunjukkan output kode yang menambahkan elemen Compose dalam
hierarki UI View. Teks "Hello Android!" ditampilkan oleh
widget TextView
. Teks "Hello Compose!" ditampilkan oleh
elemen teks Compose.
Anda juga dapat menyertakan ComposeView
secara langsung dalam fragmen jika layar penuh Anda
dibuat dengan Compose, yang memungkinkan Anda menghindari penggunaan file tata letak XML sepenuhnya.
class ExampleFragmentNoXml : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { return ComposeView(requireContext()).apply { // Dispose of the Composition when the view's LifecycleOwner // is destroyed setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { MaterialTheme { // In Compose world Text("Hello Compose!") } } } } }
Beberapa ComposeView dalam tata letak yang sama
Jika ada beberapa elemen ComposeView
dalam tata letak yang sama, masing-masing harus
memiliki ID unik agar savedInstanceState
dapat berfungsi.
class ExampleFragmentMultipleComposeView : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View = LinearLayout(requireContext()).apply { addView( ComposeView(requireContext()).apply { setViewCompositionStrategy( ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed ) id = R.id.compose_view_x // ... } ) addView(TextView(requireContext())) addView( ComposeView(requireContext()).apply { setViewCompositionStrategy( ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed ) id = R.id.compose_view_y // ... } ) } }
ID ComposeView
ditentukan dalam file res/values/ids.xml
:
<resources>
<item name="compose_view_x" type="id" />
<item name="compose_view_y" type="id" />
</resources>
View di Compose
Anda bisa menyertakan hierarki Android View di UI Compose. Pendekatan ini
sangat berguna jika Anda ingin menggunakan elemen UI yang belum tersedia di
Compose, seperti
AdView
.
Pendekatan ini juga memungkinkan Anda menggunakan kembali tampilan kustom yang mungkin sudah Anda desain.
Untuk menyertakan elemen tampilan atau hierarki, gunakan composable AndroidView
.
AndroidView
dimasukkan lambda yang menampilkan View
. AndroidView
juga menyediakan callback
update
yang dipanggil saat tampilan di-inflate. AndroidView
akan merekomposisi
setiap kali pembacaan State
dalam callback berubah. AndroidView
, seperti banyak
composable bawaan lainnya, memerlukan parameter Modifier
yang dapat digunakan, misalnya,
untuk menyetel posisinya di composable induk.
@Composable fun CustomView() { var selectedItem by remember { mutableStateOf(0) } // Adds view to Compose AndroidView( modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree factory = { context -> // Creates view MyView(context).apply { // Sets up listeners for View -> Compose communication setOnClickListener { selectedItem = 1 } } }, update = { view -> // View's been inflated or state read in this block has been updated // Add logic here if necessary // As selectedItem is read here, AndroidView will recompose // whenever the state changes // Example of Compose -> View communication view.selectedItem = selectedItem } ) } @Composable fun ContentExample() { Column(Modifier.fillMaxSize()) { Text("Look at this CustomView!") CustomView() } }
Untuk menyematkan tata letak XML, gunakan AndroidViewBinding
API, yang disediakan oleh library androidx.compose.ui:ui-viewbinding
. Untuk
melakukannya, project Anda harus mengaktifkan binding tampilan.
@Composable fun AndroidViewBindingExample() { AndroidViewBinding(ExampleLayoutBinding::inflate) { exampleView.setBackgroundColor(Color.GRAY) } }
Fragment di Compose
Gunakan composable AndroidViewBinding
untuk menambahkan Fragment
di Compose.
AndroidViewBinding
memiliki penanganan khusus fragmen seperti menghapus
fragmen saat composable keluar dari komposisi.
Lakukan dengan meng-inflate XML yang berisi FragmentContainerView
sebagai holder untuk Fragment
.
Misalnya, jika memiliki my_fragment_layout.xml
yang ditentukan, Anda dapat menggunakan
kode seperti ini saat mengganti atribut XML android:name
dengan
nama class Fragment
:
<androidx.fragment.app.FragmentContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container_view"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:name="com.example.MyFragment" />
Inflate fragmen ini di Compose sebagai berikut:
@Composable fun FragmentInComposeExample() { AndroidViewBinding(MyFragmentLayoutBinding::inflate) { val myFragment = fragmentContainerView.getFragment<MyFragment>() // ... } }
Jika Anda perlu menggunakan beberapa fragmen dalam tata letak yang sama, pastikan Anda telah
menentukan ID unik untuk setiap FragmentContainerView
.
Memanggil framework Android dari Compose
Compose beroperasi dalam class framework Android. Misalnya, class
tersebut dihosting di class Android View, seperti Activity
atau Fragment
, dan mungkin perlu
menggunakan class framework Android seperti Context
, resource sistem,
Service
, atau BroadcastReceiver
.
Untuk mempelajari resource sistem lebih lanjut, lihat dokumentasi Resource dalam Compose.
Lokal Komposisi
Dengan class CompositionLocal
, penerusan data secara implisit bisa dilakukan melalui fungsi composable. Fungsi ini
biasanya diberi nilai dalam node tertentu dari pohon UI. Nilai tersebut dapat digunakan oleh turunan composable-nya tanpa mendeklarasikan CompositionLocal
sebagai parameter dalam fungsi composable.
CompositionLocal
digunakan untuk mengisi nilai jenis framework Android di Compose seperti Context
, Configuration
, atau View
di mana kode Compose dihosting dengan kode yang sesuai LocalContext
, LocalConfiguration
, atau LocalView
.
Perhatikan bahwa class CompositionLocal
diawali dengan Local
agar mendapatkan visibilitas yang lebih baik
dengan pelengkapan otomatis di IDE.
Akses nilai CompositionLocal
saat ini menggunakan properti
current
. Misalnya, kode di bawah ini menampilkan pesan toast dengan menyediakan
LocalContext.current
ke dalam metode Toast.makeToast
.
@Composable fun ToastGreetingButton(greeting: String) { val context = LocalContext.current Button(onClick = { Toast.makeText(context, greeting, Toast.LENGTH_SHORT).show() }) { Text("Greet") } }
Untuk contoh yang lebih lengkap, lihat bagian Studi Kasus: BroadcastReceivers di akhir dokumen ini.
Interaksi lainnya
Jika tidak ada utilitas yang ditetapkan untuk interaksi yang Anda butuhkan, praktik terbaiknya adalah mengikuti pedoman Compose umum, data mengalir ke bawah, peristiwa mengalir ke atas (dibahas lebih jauh lagi di bagian Paradigma Compose). Misalnya, composable ini akan meluncurkan aktivitas yang berbeda:
class OtherInteractionsActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // get data from savedInstanceState setContent { MaterialTheme { ExampleComposable(data, onButtonClick = { startActivity(Intent(this, MyActivity::class.java)) }) } } } } @Composable fun ExampleComposable(data: DataExample, onButtonClick: () -> Unit) { Button(onClick = onButtonClick) { Text(data.title) } }
Studi Kasus: BroadcastReceivers
Untuk contoh fitur yang lebih realistis, Anda mungkin ingin bermigrasi atau menerapkannya di Compose dan menampilkan CompositionLocal
dan efek samping, misalnya BroadcastReceiver
harus didaftarkan dari fungsi composable.
Solusi ini memanfaatkan LocalContext
untuk menggunakan konteks saat ini, serta efek samping rememberUpdatedState
dan DisposableEffect
.
@Composable fun SystemBroadcastReceiver( systemAction: String, onSystemEvent: (intent: Intent?) -> Unit ) { // Grab the current context in this part of the UI tree val context = LocalContext.current // Safely use the latest onSystemEvent lambda passed to the function val currentOnSystemEvent by rememberUpdatedState(onSystemEvent) // If either context or systemAction changes, unregister and register again DisposableEffect(context, systemAction) { val intentFilter = IntentFilter(systemAction) val broadcast = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { currentOnSystemEvent(intent) } } context.registerReceiver(broadcast, intentFilter) // When the effect leaves the Composition, remove the callback onDispose { context.unregisterReceiver(broadcast) } } } @Composable fun HomeScreen() { SystemBroadcastReceiver(Intent.ACTION_BATTERY_CHANGED) { batteryStatus -> val isCharging = /* Get from batteryStatus ... */ true /* Do something if the device is charging */ } /* Rest of the HomeScreen */ }