CompositionLocal
adalah alat untuk
meneruskan data melalui Komposisi secara implisit. Di halaman ini, Anda akan
mempelajari definisi CompositionLocal
lebih mendetail, cara membuat CompositionLocal
Anda sendiri,
dan mengetahui apakah CompositionLocal
merupakan solusi yang baik untuk
kasus penggunaan Anda.
Memperkenalkan CompositionLocal
Biasanya di Compose, data mengalir ke bawah melalui hierarki UI yang berfungsi sebagai parameter untuk setiap fungsi composable. Ini mengakibatkan dependensi composable bersifat eksplisit. Namun, cara ini dapat cukup rumit untuk data yang sangat sering dan banyak digunakan, seperti warna atau gaya jenis. Lihat contoh berikut:
@Composable fun MyApp() { // Theme information tends to be defined near the root of the application val colors = colors() } // Some composable deep in the hierarchy @Composable fun SomeTextLabel(labelText: String) { Text( text = labelText, color = colors.onPrimary // ← need to access colors here ) }
Agar tidak perlu meneruskan warna sebagai dependensi parameter eksplisit ke sebagian
besar composable, Compose menawarkan CompositionLocal
yang memungkinkan Anda
membuat objek bernama dengan cakupan hierarki yang dapat digunakan sebagai cara implisit untuk memiliki
aliran data melalui hierarki UI.
Elemen CompositionLocal
biasanya diberi nilai di node
tertentu dalam hierarki UI. Nilai tersebut dapat digunakan oleh turunan composable-nya tanpa
mendeklarasikan CompositionLocal
sebagai parameter dalam fungsi composable.
CompositionLocal
adalah item yang digunakan tema Material di balik layar.
MaterialTheme
adalah
objek yang menyediakan tiga instance CompositionLocal
: colorScheme
,
typography
, dan shapes
, yang memungkinkan Anda mengambilnya nanti di bagian turunan
Komposisi.
Secara khusus, ini adalah properti LocalColorScheme
, LocalShapes
, dan
LocalTypography
yang dapat Anda akses melalui atribut MaterialTheme
colorScheme
, shapes
, dan typography
.
@Composable fun MyApp() { // Provides a Theme whose values are propagated down its `content` MaterialTheme { // New values for colorScheme, typography, and shapes are available // in MaterialTheme's content lambda. // ... content here ... } } // Some composable deep in the hierarchy of MaterialTheme @Composable fun SomeTextLabel(labelText: String) { Text( text = labelText, // `primary` is obtained from MaterialTheme's // LocalColors CompositionLocal color = MaterialTheme.colorScheme.primary ) }
Instance CompositionLocal
diberi cakupan untuk bagian dari Komposisi sehingga Anda
dapat memberikan nilai yang berbeda di berbagai pohon hierarki. Nilai current
dari
CompositionLocal
setara dengan nilai terdekat yang diberikan oleh
ancestor di bagian Komposisi tersebut.
Untuk memberikan nilai baru ke CompositionLocal
, gunakan
CompositionLocalProvider
dan fungsi infiks provides
-nya
yang menghubungkan kunci CompositionLocal
ke value
. Lambda
content
dari CompositionLocalProvider
akan mendapatkan nilai yang
diberikan saat mengakses properti current
dari CompositionLocal
. Jika
nilai baru diberikan, Compose akan merekomposisi bagian Komposisi yang membaca
CompositionLocal
.
Contohnya, LocalContentColor
CompositionLocal
berisi warna konten pilihan yang digunakan untuk teks dan
ikonografi untuk memastikan warna tersebut kontras dengan warna latar belakang saat ini. Pada
contoh berikut, CompositionLocalProvider
digunakan untuk memberikan nilai
yang berbeda untuk berbagai bagian Komposisi.
@Composable fun CompositionLocalExample() { MaterialTheme { // Surface provides contentColorFor(MaterialTheme.colorScheme.surface) by default // This is to automatically make text and other content contrast to the background // correctly. Surface { Column { Text("Uses Surface's provided content color") CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.primary) { Text("Primary color provided by LocalContentColor") Text("This Text also uses primary as textColor") CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.error) { DescendantExample() } } } } } } @Composable fun DescendantExample() { // CompositionLocalProviders also work across composable functions Text("This Text uses the error color now") }
Gambar 1. Pratinjau composable CompositionLocalExample
.
Dalam contoh terakhir, instance CompositionLocal
digunakan secara internal
oleh composable Material. Untuk mengakses nilai CompositionLocal
saat ini,
gunakan properti
current
-nya. Pada contoh berikut, nilai Context
saat ini dari LocalContext
CompositionLocal
yang umumnya digunakan di aplikasi Android digunakan untuk memformat
teks:
@Composable fun FruitText(fruitSize: Int) { // Get `resources` from the current value of LocalContext val resources = LocalContext.current.resources val fruitText = remember(resources, fruitSize) { resources.getQuantityString(R.plurals.fruit_title, fruitSize) } Text(text = fruitText) }
Membuat CompositionLocal
Anda sendiri
CompositionLocal
adalah alat untuk meneruskan data
secara implisit melalui Komposisi.
Sinyal penting lainnya untuk menggunakan CompositionLocal
adalah ketika parameter
memotong silang dan lapisan perantara penerapan tidak boleh
mengetahuinya, karena jika lapisan perantara tersebut mengetahuinya, lapisan ini akan membatasi
utilitas dari composable. Misalnya, membuat kueri untuk izin Android akan
diberikan oleh CompositionLocal
di balik layar. Composable pemilih media
dapat menambahkan fungsi baru untuk mengakses konten yang dilindungi oleh izin di
perangkat tanpa mengubah API-nya dan tidak mengharuskan pemanggil pemilih media
mengetahui konteks tambahan ini yang digunakan dari lingkungan.
Namun, CompositionLocal
tidak selalu merupakan solusi terbaik. Kami
tidak menyarankan CompositionLocal
untuk digunakan secara berlebihan karena memiliki beberapa kelemahan:
CompositionLocal
membuat perilaku composable menjadi lebih sulit untuk dipertimbangkan. Saat
membuat dependensi implisit, pemanggil dari composable yang menggunakannya harus
memastikan bahwa nilai untuk setiap CompositionLocal
terpenuhi.
Selain itu, mungkin tidak ada sumber kebenaran yang jelas untuk dependensi ini karena dependensi ini
dapat mengubah bagian Komposisi mana pun. Dengan demikian, men-debug aplikasi saat
terjadi masalah dapat menjadi lebih sulit karena Anda perlu menavigasi ke atas
Komposisi untuk melihat tempat nilai current
diberikan. Alat seperti Temukan
penggunaan di IDE atau Layout inspector compose memberikan informasi yang cukup untuk
menanggulangi masalah ini.
Menentukan apakah akan menggunakan CompositionLocal
atau tidak
Ada kondisi tertentu yang dapat membuat CompositionLocal
menjadi solusi yang baik
untuk kasus penggunaan Anda:
CompositionLocal
harus memiliki nilai default yang baik. Jika tidak ada nilai default,
Anda harus menjamin bahwa developer sangat sulit
untuk terlibat dalam sebuah situasi jika nilai untuk CompositionLocal
tidak diberikan.
Tidak memberikan nilai default dapat menyebabkan masalah dan kesulitan saat membuat
pengujian atau melihat pratinjau composable yang menggunakan CompositionLocal
akan selalu
mengharuskannya diberikan secara eksplisit.
Hindari CompositionLocal
untuk konsep yang tidak dianggap sebagai cakupan pohon atau
cakupan sub-hierarki. CompositionLocal
dapat diterima jika berpotensi untuk digunakan
oleh turunan mana pun, bukan oleh beberapa turunan.
Jika kasus penggunaan Anda tidak memenuhi persyaratan ini, lihat bagian
Alternatif yang perlu dipertimbangkan sebelum membuat
CompositionLocal
.
Contoh praktik yang buruk adalah membuat CompositionLocal
yang menyimpan
ViewModel
layar tertentu sehingga semua composable di layar tersebut bisa
mendapatkan referensi ke ViewModel
untuk menjalankan beberapa logika. Ini adalah praktik yang buruk
karena tidak semua composable di bawah pohon UI tertentu perlu mengetahui
ViewModel
. Praktik yang baik adalah meneruskan informasi yang diperlukan hanya ke composable
dengan mengikuti pola status mengalir ke bawah dan peristiwa mengalir ke atas. Pendekatan ini akan membuat composable Anda menjadi lebih
dapat digunakan kembali dan lebih mudah diuji.
Membuat CompositionLocal
Ada dua API untuk membuat CompositionLocal
:
compositionLocalOf
: Mengubah nilai yang diberikan selama rekomposisi akan membatalkan hanya konten yang membaca nilaicurrent
-nya.staticCompositionLocalOf
: Tidak seperticompositionLocalOf
, operasi bacastaticCompositionLocalOf
tidak dilacak oleh Compose. Mengubah nilai ini akan menyebabkan rekomposisi keseluruhan lambdacontent
, tempatCompositionLocal
disediakan, bukan hanya tempat nilaicurrent
dibaca di Komposisi.
Jika nilai yang diberikan ke CompositionLocal
sangat tidak mungkin berubah atau
tidak akan pernah berubah, gunakan staticCompositionLocalOf
untuk mendapatkan manfaat performa.
Misalnya, sistem desain aplikasi mungkin memiliki opini tentang cara
menaikkan composable menggunakan bayangan untuk komponen UI. Karena elevasi
yang berbeda untuk aplikasi harus diterapkan di seluruh pohon UI, kita akan menggunakan
CompositionLocal
. Kita menggunakan compositionLocalOf
API karena nilai CompositionLocal
diperoleh secara bersyarat
berdasarkan tema sistem:
// LocalElevations.kt file data class Elevations(val card: Dp = 0.dp, val default: Dp = 0.dp) // Define a CompositionLocal global object with a default // This instance can be accessed by all composables in the app val LocalElevations = compositionLocalOf { Elevations() }
Memberikan nilai ke CompositionLocal
Composable CompositionLocalProvider
mengikat nilai menjadi instance
CompositionLocal
untuk hierarki tertentu. Untuk memberikan nilai baru ke CompositionLocal
, gunakan fungsi infiks
provides
yang menghubungkan kunci CompositionLocal
dengan value
sebagai berikut:
// MyActivity.kt file class MyActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { // Calculate elevations based on the system theme val elevations = if (isSystemInDarkTheme()) { Elevations(card = 1.dp, default = 1.dp) } else { Elevations(card = 0.dp, default = 0.dp) } // Bind elevation as the value for LocalElevations CompositionLocalProvider(LocalElevations provides elevations) { // ... Content goes here ... // This part of Composition will see the `elevations` instance // when accessing LocalElevations.current } } } }
Menggunakan CompositionLocal
CompositionLocal.current
menampilkan nilai yang diberikan oleh CompositionLocalProvider
terdekat yang memberikan nilai ke CompositionLocal
tersebut:
@Composable fun SomeComposable() { // Access the globally defined LocalElevations variable to get the // current Elevations in this part of the Composition MyCard(elevation = LocalElevations.current.card) { // Content } }
Alternatif yang perlu dipertimbangkan
Untuk beberapa kasus penggunaan, CompositionLocal
mungkin merupakan solusi yang berlebihan. Jika kasus
penggunaan Anda tidak memenuhi kriteria yang ditentukan di bagian Menentukan apakah akan menggunakan
CompositionLocal atau tidak, solusi lain mungkin lebih cocok
untuk kasus penggunaan Anda.
Meneruskan parameter eksplisit
Menerapkan pernyataan eksplisit pada dependensi composable adalah kebiasaan yang baik. Sebaiknya Anda hanya meneruskan composable yang dibutuhkan. Untuk mendorong pemisahan dan penggunaan kembali composable, setiap composable harus memiliki sesedikit mungkin informasi.
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... MyDescendant(myViewModel.data) } // Don't pass the whole object! Just what the descendant needs. // Also, don't pass the ViewModel as an implicit dependency using // a CompositionLocal. @Composable fun MyDescendant(myViewModel: MyViewModel) { /* ... */ } // Pass only what the descendant needs @Composable fun MyDescendant(data: DataToDisplay) { // Display data }
Inversi kontrol
Cara lain untuk menghindari penerusan dependensi yang tidak diperlukan ke composable adalah melalui inversi kontrol. Induk akan menggunakan dependensi untuk menjalankan beberapa logika, bukan turunan.
Lihat contoh berikut saat turunan harus memicu permintaan untuk memuat beberapa data:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... MyDescendant(myViewModel) } @Composable fun MyDescendant(myViewModel: MyViewModel) { Button(onClick = { myViewModel.loadData() }) { Text("Load data") } }
Bergantung pada kasusnya, MyDescendant
mungkin memiliki banyak tanggung jawab. Selain itu,
meneruskan MyViewModel
sebagai dependensi akan membuat MyDescendant
tidak terlalu dapat digunakan kembali karena
keduanya kini telah digabungkan. Pertimbangkan alternatif yang tidak meneruskan
dependensi ke dalam turunan dan menggunakan inversi prinsip kontrol yang mengakibatkan
ancestor bertanggung jawab untuk menjalankan logika:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusableLoadDataButton( onLoadClick = { myViewModel.loadData() } ) } @Composable fun ReusableLoadDataButton(onLoadClick: () -> Unit) { Button(onClick = onLoadClick) { Text("Load data") } }
Pendekatan ini dapat lebih cocok untuk beberapa kasus penggunaan karena memisahkan turunan dari ancestor sebelumnya. Composable ancestor cenderung menjadi lebih kompleks karena memiliki composable yang lebih rendah dan fleksibel.
Demikian pula, lambda konten @Composable
dapat digunakan dengan cara yang sama untuk mendapatkan
manfaat yang sama:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusablePartOfTheScreen( content = { Button( onClick = { myViewModel.loadData() } ) { Text("Confirm") } } ) } @Composable fun ReusablePartOfTheScreen(content: @Composable () -> Unit) { Column { // ... content() } }
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Anatomi tema di Compose
- Menggunakan View di Compose
- Kotlin untuk Jetpack Compose