Interoperabilitas Compose

Jetpack Compose dirancang untuk bekerja dengan pendekatan UI yang sudah ada berbasis tampilan. Jika Anda membuat aplikasi baru, opsi terbaik adalah menerapkan seluruh UI Anda dengan Compose. Namun, jika Anda memodifikasi aplikasi yang sudah ada, Anda mungkin tidak ingin sepenuhnya memigrasikan aplikasi sekaligus. Sebagai gantinya, Anda dapat menggabungkan Compose dengan penerapan desain UI yang sudah ada.

Mengadopsi Compose di aplikasi Anda

Ada dua cara utama untuk mengintegrasikan Compose dengan UI berbasis tampilan:

  • Anda dapat menambahkan elemen Compose ke UI yang sudah ada dengan membuat layar baru berbasis Compose, atau dengan menambahkan elemen Compose ke aktivitas, fragmen atau tata letak tampilan yang sudah ada.

  • Anda dapat menambahkan elemen UI berbasis tampilan ke dalam fungsi yang dapat dikomposisi. Dengan melakukannya, Anda dapat menambahkan tampilan Android ke dalam desain berbasis Compose.

Cara terbaik untuk memigrasikan seluruh aplikasi ke Compose adalah dengan membuat perincian yang dibutuhkan project secara bertahap. Anda dapat memigrasikan satu layar dalam satu waktu, atau bahkan satu fragmen atau elemen UI lain yang dapat digunakan kembali dalam satu waktu. Anda dapat menggunakan beberapa pendekatan yang berbeda:

  • Pendekatan bottom-up mulai memigrasikan elemen UI yang lebih kecil di layar, seperti Button atau TextView, diikuti dengan elemen ViewGroup sampai semuanya dikonversi menjadi fungsi yang dapat dikomposisi.

  • Pendekatan vertikal mulai memigrasikan fragmen atau container tampilan, seperti FrameLayout, ConstraintLayout, atau RecyclerView, diikuti dengan elemen UI yang lebih kecil di layar.

Pendekatan ini mengasumsikan bahwa setiap layar bersifat mandiri, tetapi Anda juga dapat memigrasikan UI bersama, seperti sistem desain, ke Jetpack Compose. Lihat Memigrasikan UI bersama di bawah untuk informasi selengkapnya.

Interoperabilitas API

Saat mengadopsi Compose di aplikasi Anda, UI berbasis tampilan dan UI Compose dapat digabungkan. Berikut adalah daftar API, rekomendasi, dan tips untuk mempermudah transisi ke Compose.

Compose di Android View

Anda dapat menambahkan UI berbasis Compose ke dalam aplikasi yang sudah ada dan menggunakan desain berbasis tampilan.

Untuk membuat layar baru yang sepenuhnya berbasis Compose, minta aktivitas Anda memanggil metode setContent(), dan teruskan fungsi mana pun yang dapat dikomposisi, yang Anda suka.

class ExampleActivity : AppCompatActivity() {
    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.

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 harus memasang ComposeView ke ViewTreeLifecycleOwner. ViewTreeLifecycleOwner memungkinkan tampilan untuk dipasang dan dilepas berulang kali sambil mempertahankan komposisi. ComponentActivity, FragmentActivity, dan AppCompatActivity adalah contoh class yang menerapkan ViewTreeLifecycleOwner.

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, dan panggil setContent() untuk menggunakan Compose.

class ExampleFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        // Inflate the layout for this fragment
        return inflater.inflate(
            R.layout.fragment_example, container, false
        ).apply {
            findViewById<ComposeView>(R.id.compose_view).setContent {
                // In Compose world
                MaterialTheme {
                    Text("Hello Compose!")
                }
            }
        }
    }
}

Dua elemen teks yang sedikit berbeda, satu di atas yang lain

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 ExampleFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        return ComposeView(requireContext()).apply {
            setContent {
                MaterialTheme {
                    // In Compose world
                    Text("Hello Compose!")
                }
            }
        }
    }
}

Jika ada beberapa elemen ComposeView dalam tata letak yang sama, masing-masing harus memiliki ID unik agar savedInstanceState berfungsi. Baca informasi selengkapnya tentang hal ini di bagian SavedInstanceState.

class ExampleFragment : Fragment() {

  override fun onCreateView(...): View = LinearLayout(...).apply {
      addView(ComposeView(...).apply {
        id = R.id.compose_view_x
        ...
      })
      addView(TextView(...))
      addView(ComposeView(...).apply {
        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>

Android 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 atau MapView. Pendekatan ini juga memungkinkan Anda menggunakan kembali tampilan kustom yang mungkin sudah Anda desain.

Untuk menyertakan elemen tampilan atau hierarki, gunakan komponen AndroidView. AndroidView diteruskan 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.

@Composable
fun CustomView() {
    val selectedItem = remember { mutableStateOf(0) }

    // Adds view to Compose
    AndroidView(
        modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree
        factory = { context ->
            // Creates custom view
            CustomView(context).apply {
                // Sets up listeners for View -> Compose communication
                myView.setOnClickListener {
                    selectedItem.value = 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.coordinator.selectedItem = selectedItem.value
        }
    )
}

@Composable
fun ContentExample() {
    Column(Modifier.fillMaxSize()) {
        Text("Look at this CustomView!")
        CustomView()
    }
}

Untuk menyematkan tata letak XML, gunakan API AndroidViewBinding, yang disediakan oleh library androidx.compose.ui:ui-viewbinding. Untuk melakukannya, project Anda harus mengaktifkan binding tampilan.

AndroidView, seperti banyak fungsi bawaan lainnya, memerlukan parameter Modifier yang dapat digunakan, misalnya, untuk menyetel posisinya di komponen induk.

@Composable
fun AndroidViewBindingExample() {
    AndroidViewBinding(ExampleLayoutBinding::inflate) {
        exampleView.setBackgroundColor(Color.GRAY)
    }
}

Memanggil framework Android dari Compose

Compose terikat erat dengan 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.

Komposisi Lokal

Dengan class CompositionLocal, penerusan data secara implisit bisa dilakukan melalui fungsi yang dapat dikomposisi. Fungsi ini biasanya diberi nilai dalam node tertentu dari pohon UI. Nilai tersebut dapat digunakan oleh turunannya yang dapat disusun tanpa mendeklarasikan CompositionLocal sebagai parameter dalam fungsi yang dapat dikomposisi.

CompositionLocal digunakan untuk menyebarkan nilai jenis framework Android di Compose sepertiContext ,Configuration atauView 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 membuat tampilan kustom menggunakan Context yang tersedia di bagian pohon UI Compose dengan memanggil LocalContext.current.

@Composable
fun rememberCustomView(): CustomView {
    val context = LocalContext.current
    return remember { CustomView(context).apply { /*...*/ } }
}

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 Berpikir dalam Compose). Misalnya, komponen ini akan meluncurkan aktivitas yang berbeda:

class ExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // get data from savedInstanceState
        setContent {
            MaterialTheme {
                ExampleComposable(data, onButtonClick = {
                    startActivity(/*...*/)
                })
            }
        }
    }
}

@Composable
fun ExampleComposable(data: DataExample, onButtonClick: () -> Unit) {
    Button(onClick = onButtonClick) {
        Text(data.title)
    }
}

Integrasi dengan library umum

Untuk melihat bagaimana Compose terintegrasi dengan library umum seperti ViewModel, Flow, Paging, atau Hilt, lihat Panduan integrasi Compose dengan library umum.

Tema

Mengikuti Desain Material, menggunakan library Komponen Desain Material untuk Android (MDC) adalah cara yang disarankan untuk tema aplikasi Android. Seperti yang tercakup dalam dokumentasi Compose Theming, Compose menerapkan konsep ini dengan komponen MaterialTheme.

Saat membuat layar baru di Compose, Anda harus memastikan bahwa Anda telah menerapkan MaterialTheme sebelum komponen yang membuat UI dari library komponen 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.

Beberapa sumber kebenaran

Aplikasi yang ada cenderung memiliki tema dan gaya visual yang banyak untuk ditampilkan. Saat memperkenalkan Compose di aplikasi yang ada, Anda harus memigrasikan tema untuk menggunakan MaterialTheme untuk layar Compose apa pun. Ini berarti tema aplikasi Anda akan memiliki 2 sumber kebenaran: tema berbasis View dan tema Compose. Setiap perubahan pada gaya visual Anda harus dilakukan di beberapa tempat.

Jika rencana Anda adalah untuk memigrasikan aplikasi secara total ke Compose, Anda harus membuat versi Compose dari tema yang sudah ada. Masalahnya adalah, semakin awal Anda membuat tema Compose, semakin banyak pemeliharaan yang harus dilakukan selama pengembangan.

Adaptor Tema Compose MDC

Jika menggunakan library MDC di aplikasi Android, libraryAdaptor Tema Compose MDC memungkinkan Anda menggunakan kembali tema warna, tipografi, danbentuk dari tema berbasis View yang ada dalam komponen dengan mudah:

import com.google.android.material.composethemeadapter.MdcTheme

class ExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            MdcTheme {
                // Colors, typography, and shape have been read from the
                // View-based theme used in this Activity
                ExampleComposable(/*...*/)
            }
        }
    }
}

Lihat dokumentasi library MDC untuk mengetahui informasi selengkapnya.

Adaptor Tema Compose AppCompat

Dengan Adaptor Tema Compose AppCompat, Anda dapat menggunakan kembali tema XML AppCompat dengan mudah untuk tema di Jetpack Compose. Ini akan membuat MaterialTheme dengan nilai warna dan tipografi dari tema konteks.

import com.google.accompanist.appcompattheme.AppCompatTheme

class ExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            AppCompatTheme {
                // Colors, typography, and shape have been read from the
                // View-based theme used in this Activity
                ExampleComposable(/*...*/)
            }
        }
    }
}

Gaya komponen default

Baik library MDC maupun Adaptor Tema Compose AppCompat Compose tidak membaca gaya widget default yang ditentukan oleh tema. Ini karena Compose tidak memiliki konsep komponen default.

Baca gaya komponen dan sistem desain khusus selengkapnya di dokumentasi Tema.

Overlay Tema di Compose

Saat memigrasikan layar berbasis View ke Compose, perhatikan penggunaan atribut android:theme. Sepertinya Anda memerlukan MaterialTheme baru di bagian pohon UI Compose.

Baca selengkapnya di Panduan tema.

WindowInset dan Animasi IME

Anda dapat menangani WindowInsets menggunakan library accompanist-insets yang menyediakan komponen dan pengubah untuk menanganinya dalam tata letak Anda, serta dukungan untuk Animasi IME.

class ExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        WindowCompat.setDecorFitsSystemWindows(window, false)

        setContent {
            MaterialTheme {
                ProvideWindowInsets {
                    MyScreen()
                }
            }
        }
    }
}

@Composable
fun MyScreen() {
    Box {
        FloatingActionButton(
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .padding(16.dp) // normal 16dp of padding for FABs
                .navigationBarsPadding(), // Move it out from under the nav bar
            onClick = { }
        ) {
            Icon( /* ... */)
        }
    }
}

Animasi yang menampilkan elemen UI yang menggulir ke atas dan ke bawah untuk memberi jalan bagi keyboard

Gambar 2. Animasi IME menggunakan library accompanist-insets.

Lihat dokumentasi library accompanist-insets untuk mengetahui informasi selengkapnya.

Menangani perubahan ukuran layar

Saat memigrasikan aplikasi yang menggunakan tata letak XML yang berbeda, bergantung pada ukuran layar, gunakan komponen BoxWithConstraints untuk mengetahui ukuran minimum dan maksimum yang dapat mengisi.

@Composable
fun MyComposable() {
    BoxWithConstraints {
        if (minWidth < 480.dp) {
            /* Show grid with 4 columns */
        } else if (minWidth < 720.dp) {
            /* Show grid with 8 columns */
        } else {
            /* Show grid with 12 columns */
        }
    }
}

Arsitektur dan sumber status kebenaran

Pola arsitektur Aliran Data Searah (UFD) 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.

ViewModel dalam Compose

Jika menggunakan library Architecture Components ViewModel, Anda dapat mengakses ViewModel dari fungsi mana pun dengan memanggil fungsi viewModel(), seperti yang dijelaskan dalam Dokumentasi integrasi Compose dengan library umum.

Saat menggunakan Compose, berhati-hatilah saat menggunakan jenis ViewModel yang sama dalam berbagai komponen karena elemen ViewModel mengikuti cakupan siklus proses View. Cakupan akan berupa aktivitas host, fragmen, atau grafik navigasi jika library Navigasi digunakan.

Misalnya, jika komponen dapat dihosting dalam aktivitas, viewModel() selalu menampilkan instance yang sama yang hanya akan dihapus saat aktivitas telah selesai. Pada contoh berikut, pengguna yang sama akan disapa dua kali karena instance GreetingViewModel yang sama digunakan kembali di semua komponen dalam aktivitas host. Instance ViewModel pertama yang telah dibuat digunakan kembali di komponen lain.

class ExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            MaterialTheme {
                Column {
                    Greeting("user1")
                    Greeting("user2")
                }
            }
        }
    }
}

@Composable
fun Greeting(userId: String) {
    val greetingViewModel: GreetingViewModel = viewModel(
        factory = GreetingViewModelFactory(userId)
    )
    val messageUser by greetingViewModel.message.observeAsState("")

    Text(messageUser)
}

class GreetingViewModel(private val userId: String) : ViewModel() {
    private val _message = MutableLiveData("Hi $userId")
    val message: LiveData<String> = _message
}

Karena grafik navigasi juga mencakup elemen ViewModel, komponen 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 MyScreen() {
    NavHost(rememberNavController(), startDestination = "profile/{userId}") {
        /* ... */
        composable("profile/{userId}") { backStackEntry ->
            Greeting(backStackEntry.arguments?.getString("userId") ?: "")
        }
    }
}

@Composable
fun Greeting(userId: String) {
    val greetingViewModel: GreetingViewModel = viewModel(
        factory = GreetingViewModelFactory(userId)
    )
    val messageUser by greetingViewModel.message.observeAsState("")

    Text(messageUser)
}

Sumber status kebenaran

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 mengurangi update 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 properti yang dapat disusun yang mengirim update status.

Misalnya, OnBackPressedCallback harus didaftarkan agar dapat memproses penekanan tombol kembali di OnBackPressedDispatcher. Untuk memberitahukan apakah callback harus diaktifkan atau tidak, gunakan SideEffect untuk mengupdate nilainya.

@Composable
fun BackHandler(
    enabled: Boolean,
    backDispatcher: OnBackPressedDispatcher,
    onBack: () -> Unit
) {

    // Safely update the current `onBack` lambda when a new one is provided
    val currentOnBack by rememberUpdatedState(onBack)

    // Remember in Composition a back callback that calls the `onBack` lambda
    val backCallback = remember {
        // Always intercept back events. See the SideEffect for a more complete version
        object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                currentOnBack()
            }
        }
    }

    // On every successful composition, update the callback with the `enabled` value
    // to tell `backCallback` whether back events should be intercepted or not
    SideEffect {
        backCallback.isEnabled = enabled
    }

    // If `backDispatcher` changes, dispose and reset the effect
    DisposableEffect(backDispatcher) {
        // Add callback to the backDispatcher
        backDispatcher.addCallback(backCallback)

        // When the effect leaves the Composition, remove the callback
        onDispose {
            backCallback.remove()
        }
    }
}

Untuk mengetahui informasi efek samping selengkapnya, lihat Dokumentasi Siklus Proses dan Efek Samping.

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 yang dapat dikomposisi 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 komponen dan dapat digunakan kembali di seluruh aplikasi, apa pun elemen yang telah diberi gaya menggunakan XML atau menjadi tampilan khusus. Misalnya, Anda akan membuat komponen CallToActionButton untuk komponen Button pesan ajakan (CTA) khusus.

Untuk menggunakan komponen di layar berbasis View, Anda harus membuat wrapper tampilan khusus yang diperluas dari AbstractComposeView. Pada komponen Content telah yang diganti, tempatkan fungsi 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(
            backgroundColor = MaterialTheme.colors.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<String>("")
    var onClick by mutableStateOf<() -> Unit>({})

    @Composable
    override fun Content() {
        YourAppTheme {
            CallToActionButton(text, onClick)
        }
    }
}

Perhatikan bahwa parameter yang dapat disusun akan menjadi variabel yang dapat diubah di dalam tampilan khusus. Ini akan membuat tampilan CallToActionViewButton khusus menjadi inflatable dan dapat digunakan, misalnya View Binding, seperti tampilan tradisional. Lihat contoh di bawah:

class ExampleActivity : Activity() {

    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.something)
            onClick = { /* Do something */ }
        }
    }
}

Jika komponen khusus berisi status yang dapat diubah, lihat bagian sumber status kebenaran di atas.

Pengujian

Anda dapat menguji gabungan kode View dan Compose secara bersamaan dengan menggunakan API createAndroidComposeRule(). Untuk informasi selengkapnya, lihat Menguji tata letak Compose.

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 yang dapat dikomposisi.

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?) {
                onSystemEvent(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 */
}