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.

ViewCompositionStrategy untuk ComposeView

Secara default, Compose akan menghapus Komposisi setiap kali tampilan dilepas dari jendela. Menulis jenis View UI seperti ComposeView dan AbstractComposeView menggunakan ViewCompositionStrategy yang menentukan perilaku ini.

Secara default, Compose menggunakan strategi DisposeOnDetachedFromWindow. 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 Compose UI 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.

  • Holder tampilan RecyclerView, atau View kustom yang dikelola siklus proses Anda sendiri.

Dalam beberapa situasi semacam ini, aplikasi juga dapat secara perlahan membocorkan memori dari instance Komposisi, kecuali 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 Compose UI yang memiliki hubungan 1-1 dengan LifecycleOwner yang dikenal. Jika LifecycleOwner tidak diketahui, DisposeOnViewTreeLifecycleDestroyed dapat digunakan.

Lihat cara kerja API ini di bagian ComposeView di Fragmen.

ComposeView di Fragmen

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
        view.composeView.apply {
            // Dispose the Composition when the view's LifecycleOwner
            // is destroyed
            setViewCompositionStrategy(DisposeOnViewTreeLifecycleDestroyed)
            setContent {
                // In Compose world
                MaterialTheme {
                    Text("Hello Compose!")
                }
            }
        }
        return view
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

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 {
            // Dispose the Composition when the view's LifecycleOwner
            // is destroyed
            setViewCompositionStrategy(DisposeOnViewTreeLifecycleDestroyed)
            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 dapat berfungsi.

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 composable bawaan lainnya, memerlukan parameter Modifier yang dapat digunakan, misalnya, untuk menyetel posisinya di composable 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)
    }
}

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 */
}