Google berkomitmen untuk mendorong terwujudnya keadilan ras bagi komunitas Kulit Hitam. Lihat caranya.

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 memigrasikan aplikasi. Sebagai gantinya, Anda dapat menggabungkan Compose dengan desain UI yang sudah ada.

Ada dua cara utama untuk menggabungkan 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 fragmen atau tata letak tampilan yang sudah ada.
  • Anda dapat menambahkan elemen UI berbasis tampilan ke dalam fungsi yang dapat dikomposisi. Dengan begitu, Anda dapat menambahkan widget non-Compose ke desain berbasis 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 komposisi 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) }

  val context = ContextAmbient.current
  val customView = remember {
    // Creates custom view
    CustomView(context).apply {
      // Sets up listeners for View -> Compose communication
      myView.setOnClickListener {
        selectedItem.value = 1
      }
    }
  }

  // Adds view to Compose
  AndroidView({ customView }) { view ->
    // View's been inflated - 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
  }
}

@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.

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

Memanggil sistem View dari Compose

Framework Compose menawarkan sejumlah API yang memungkinkan kode Compose berinteraksi dengan UI berbasis tampilan.

Resource sistem

Framework Compose menawarkan metode pembantu ...Resource() untuk memungkinkan kode Compose Anda mendapatkan resource dari hierarki UI berbasis tampilan. Berikut beberapa contohnya:

Text(
  text = stringResource(R.string.ok),
  modifier = Modifier.padding(dimensionResource(R.dimen.padding_small))
)

Icon(
  assert = vectorResource(R.drawable.ic_plane),
  tint = colorResource(R.color.Blue700)
)

Konteks

Properti ContextAmbient.current memberi Anda konteks saat ini. Misalnya, kode ini membuat tampilan dalam konteks saat ini:

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

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, komposisi 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

Anda dapat menggunakan library favorit dalam Compose. Bagian ini menjelaskan cara menggabungkan beberapa library yang paling berguna.

ViewModel

Jika Anda menggunakan libraryViewModel Komponen Arsitektur, Anda dapat mengakses ViewModel dari komposisi mana pun dengan memanggil fungsi viewModel().

class ExampleViewModel() : ViewModel() { ... }

@Composable
fun MyExample() {
  val viewModel: ExampleViewModel = viewModel()

  ... // use viewModel here
}

viewModel() menampilkan ViewModel yang sudah ada atau membuat yang baru dalam cakupan yang ditentukan. ViewModel dipertahankan selama cakupan masih aktif. Misalnya, jika komposisi digunakan dalam suatu aktivitas, viewModel() akan menampilkan instance yang sama sampai aktivitas itu selesai atau prosesnya diakhiri.

@Composable
fun MyExample() {
  // Returns the same instance as long as the activity is alive,
  // just as if you grabbed the instance from an Activity or Fragment
  val viewModel: ExampleViewModel = viewModel()
}

@Composable
fun MyExample2() {
  val viewModel: ExampleViewModel = viewModel() // Same instance as in MyExample
}

Jika ViewModel Anda memiliki dependensi, viewModel() akan menggunakan ViewModelProvider.Factory opsional sebagai parameter.

Aliran data

Compose dilengkapi dengan ekstensi untuk solusi berbasis aliran data yang paling populer dari Android. Setiap ekstensi ini disediakan oleh artefak yang berbeda:

Artefak ini terdaftar sebagai pemroses dan mewakili nilai sebagai State. Setiap kali nilai baru dikeluarkan, Compose merekomposisi bagian-bagian UI tempat state.value digunakan. Misalnya, dalam kode ini, ShowData merekomposisi setiap kali exampleLiveData mengeluarkan nilai baru.

@Composable
fun MyExample() {
  val viewModel: ExampleViewModel = viewModel()
  val dataExample = viewModel.exampleLiveData.observeAsState()

  // Because the state is read here,
  // MyExample recomposes whenever dataExample changes.
  dataExample?.let {
    ShowData(dataExample)
  }
}

Operasi asinkron di Compose

Compose menyediakan mekanisme untuk memungkinkan Anda menjalankan operasi asinkron dari dalam komposisi Anda.

Untuk API berbasis callback, Anda dapat menggunakan kombinasi MutableState dan onCommit(). Gunakan MutableState untuk menyimpan hasil callback dan merekomposisi UI yang terpengaruh saat hasil berubah. Gunakan onCommit() untuk menjalankan operasi setiap kali parameter berubah. Anda juga dapat menentukan metode onDispose() untuk menghapus semua operasi yang tertunda jika komposisi berakhir sebelum operasi selesai. Contoh berikut menunjukkan cara API bekerja bersama-sama.

@Composable
fun fetchImage(url: String): ImageAsset? {
    // Holds our current image, and will be updated by the onCommit lambda below
    var image by remember(url) { mutableStateOf<ImageAsset?>(null) }

    onCommit(url) {
        // This onCommit lambda will be invoked every time url changes

        val listener = object : ExampleImageLoader.Listener() {
            override fun onSuccess(bitmap: Bitmap) {
                // When the image successfully loads, update our image state
                image = bitmap.asImageAsset()
            }
        }

        // Now execute the image loader
        val imageLoader = ExampleImageLoader.get()
        imageLoader.load(url).into(listener)

        onDispose {
            // If we leave composition, cancel any pending requests
            imageLoader.cancel(listener)
        }
    }

    // Return the state-backed image property. Any callers of this function
    // will be recomposed once the image finishes loading
    return image
}

Jika operasi asinkron adalah fungsi yang ditangguhkan, Anda dapat menggunakan launchInComposition() sebagai gantinya:

/** Example suspending loadImage function */
suspend fun loadImage(url: String): Bitmap

@Composable
fun fetchImage(url: String): ImageAsset? {
    // This holds our current image, and will be updated by the
    // launchInComposition lambda below
    var image by remember(url) { mutableStateOf<ImageAsset?>(null) }

    // launchInComposition will automatically launch a coroutine to execute
    // the given block. If the `url` changes, any previously launched coroutine
    // will be cancelled, and a new coroutine launched.
    launchInComposition(url) {
        image = loadImage(url)
    }

    // Return the state-backed image property
    return image
}

SavedInstanceState

Gunakan savedInstanceState untuk memulihkan status UI Anda setelah suatu aktivitas atau proses dibuat ulang. savedInstanceState mempertahankan status di seluruh rekomposisi. Selain itu, savedInstanceState juga mempertahankan status pada seluruh pembuatan ulang aktivitas dan proses.

@Composable
fun MyExample() {
  var selectedId by savedInstanceState<String?> { null }
  ...
}

Semua jenis data yang ditambahkan ke Bundle disimpan secara otomatis. Jika Anda ingin menyimpan sesuatu yang tidak dapat ditambahkan ke Bundle, ada beberapa opsi.

Solusi paling sederhana adalah menambahkan anotasi @Parcelize ke objek. Objek menjadi parcelable dan dapat dijadikan bundle. Misalnya, kode ini membuat jenis data City parcelable dan menyimpannya ke status.

@Parcelize
data class City(name: String, country: String): Parcelable

@Composable
fun MyExample() {
  var selectedCity = savedInstanceState { City("Madrid", "Spain") }
}

Jika karena alasan tertentu @Parcelize tidak cocok, Anda dapat menggunakan mapSaver untuk menentukan aturan sendiri guna mengonversi objek menjadi kumpulan nilai yang dapat disimpan oleh sistem ke Bundle.

data class City(name: String, country: String)

val CitySaver = run {
  val nameKey = "Name"
  val countryKey = "Country"
  mapSaver(
    save = { mapOf(nameKey to it.name, nameKey to it.country) },
    restore = { City(it[nameKey] as String, it[countryKey] as String) }
  )
}

@Composable
fun MyExample() {
  var selectedCity = savedInstanceState(CitySaver) { City("Madrid", "Spain") }
}

Agar tidak perlu menentukan kunci untuk peta, Anda juga dapat menggunakan listSaver dan menggunakan indeksnya sebagai kunci:

data class City(name: String, country: String)

val CitySaver = listSaver<City, Any>(
  save = { listOf(it.name, it.country) },
  restore = { City(it[0] as String, it[1] as String) }
)

@Composable
fun MyExample() {
  var selectedCity = savedInstanceState(CitySaver) { City("Madrid", "Spain") }
  ...
}

Tema

Jika Anda menggunakan Komponen Desain Material untuk Android di aplikasi Anda, library MDC Compose Theme Adapter memudahkan Anda menggunakan kembali tema warna, tipografi, dan bentuk dari tema yang sudah ada, dari dalam komposisi Anda:

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

    setContent {
      // We use MdcTheme instead of MaterialTheme {}
      MdcTheme {
        ExampleComposable(...)
      }
    }
  }
}

Pengujian

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

Pelajari lebih lanjut

Untuk mempelajari lebih lanjut cara mengintegrasikan Jetpack Compose dengan UI yang sudah ada, cobalah untuk Memigrasi ke codelab Jetpack Compose.