Jetpack Compose dibuat berdasarkan Kotlin. Dalam beberapa kasus, Kotlin memberikan idiom khusus yang mempermudah penulisan kode Compose yang baik. Jika Anda memikirkan bahasa pemrograman lain dan secara natural menerjemahkan bahasa tersebut ke Kotlin, Anda mungkin akan melewatkan beberapa keunggulan Compose, dan mungkin akan merasa sulit memahami kode Kotlin yang ditulis secara idiomatis. Mengenal gaya Kotlin lebih jauh dapat membantu Anda menghindari kesulitan tersebut.
Argumen default
Saat menulis fungsi Kotlin, Anda dapat menentukan nilai default untuk argumen fungsi, yang digunakan jika pemanggil tidak secara eksplisit meneruskan nilai tersebut. Fitur ini mengurangi kebutuhan akan fungsi yang berlebihan.
Misalnya, Anda ingin menulis fungsi yang menggambar persegi. Fungsi tersebut mungkin memiliki satu parameter yang diperlukan, yaitu sideLength, yang menentukan panjang setiap sisi. Fungsi itu mungkin memiliki beberapa parameter opsional, seperti thickness, edgeColor, dan sebagainya; jika pemanggil tidak menentukannya, fungsi akan menggunakan nilai default. Dalam bahasa lain, Anda mungkin perlu menulis beberapa fungsi:
// We don't need to do this in Kotlin! void drawSquare(int sideLength) { } void drawSquare(int sideLength, int thickness) { } void drawSquare(int sideLength, int thickness, Color edgeColor) { }
Di Kotlin, Anda dapat menulis fungsi tunggal dan menentukan nilai default untuk argumen:
fun drawSquare( sideLength: Int, thickness: Int = 2, edgeColor: Color = Color.Black ) { }
Selain mencegah Anda menulis beberapa fungsi secara berlebihan, fitur
ini menjadikan kode Anda lebih mudah dibaca. Jika pemanggil tidak menentukan
nilai untuk sebuah argumen, itu menunjukkan bahwa fungsi akan menggunakan nilai
default. Selain itu, parameter yang dinamai mempermudah untuk melihat apa
yang terjadi. Jika Anda melihat kode dan melihat panggilan fungsi seperti ini, Anda mungkin tidak
tahu apa arti parameter itu tanpa memeriksa kode drawSquare()
:
drawSquare(30, 5, Color.Red);
Sebaliknya, kode ini mendokumentasikan dirinya sendiri:
drawSquare(sideLength = 30, thickness = 5, edgeColor = Color.Red)
Sebagian besar library Compose menggunakan argumen default, dan ada baiknya untuk melakukan hal yang sama pada fungsi yang dapat dikomposisi, yang Anda tulis. Praktik ini membuat komposisi Anda dapat disesuaikan, tetapi tetap membuat perilaku default mudah dipanggil. Jadi, misalnya, Anda dapat membuat elemen teks sederhana seperti ini:
Text(text = "Hello, Android!")
Kode tersebut memiliki efek yang sama seperti kode berikut yang jauh lebih panjang, dengan
lebih banyak
parameter Text
yang ditetapkan secara eksplisit:
Text( text = "Hello, Android!", color = Color.Unspecified, fontSize = TextUnit.Unspecified, letterSpacing = TextUnit.Unspecified, overflow = TextOverflow.Clip )
Cuplikan kode pertama tidak hanya jauh lebih sederhana dan lebih mudah dibaca, tetapi juga
mendokumentasikan dirinya sendiri. Dengan hanya menentukan parameter text
, Anda mendokumentasikan bahwa untuk
semua parameter lainnya, Anda ingin menggunakan nilai default. Sebaliknya,
cuplikan kedua menyiratkan bahwa Anda ingin menetapkan nilai secara eksplisit untuk
parameter lainnya, meskipun nilai yang Anda tetapkan kebetulan adalah nilai default untuk
fungsi tersebut.
Fungsi dan ekspresi lambda yang lebih tinggi
Kotlin mendukung fungsi yang lebih tinggi, yang
menerima fungsi lain sebagai parameter. Compose dibuat berdasarkan pendekatan ini. Misalnya,
fungsi
Button
yang dapat dikomposisi
menyediakan parameter lambda onClick
. Nilai
parameter tersebut adalah fungsi, yang dipanggil oleh tombol saat pengguna mengkliknya:
Button( // ... onClick = myClickFunction ) // ...
Fungsi yang lebih tinggi berpasangan secara alami dengan ekspresi lambda, yaitu ekspresi
yang mengevaluasi sebuah fungsi. Jika Anda hanya memerlukan fungsi tersebut sekali, Anda tidak perlu
menentukannya di tempat lain untuk meneruskannya ke fungsi yang lebih tinggi. Sebagai gantinya, Anda dapat
menentukan fungsi secara langsung dengan ekspresi lambda. Contoh sebelumnya
mengasumsikan bahwa myClickFunction()
ditentukan di tempat lain. Namun, jika Anda hanya menggunakan
fungsi tersebut di sini, akan lebih mudah untuk menentukan fungsi itu sebagai bagian dari ekspresi
lambda:
Button( // ... onClick = { // do something // do something else } ) { /* ... */ }
Lambda akhir
Kotlin menawarkan sintaksis khusus untuk memanggil fungsi yang lebih tinggi dengan parameter terakhir adalah lambda. Jika Anda ingin meneruskan ekspresi lambda sebagai parameter tersebut, Anda dapat menggunakan sintaksis lambda akhir. Alih-alih menempatkan ekspresi lambda dalam tanda kurung, Anda harus menempatkannya setelahnya. Ini adalah situasi umum di Compose, jadi Anda harus mengetahui tampilan kodenya.
Misalnya, parameter terakhir untuk semua tata letak, seperti
fungsi Column()
yang dapat dikomposisi, adalah content
, yaitu fungsi yang membuat elemen
UI turunan. Misalnya Anda ingin membuat kolom yang berisi tiga elemen teks,
dan Anda perlu menerapkan beberapa pemformatan. Kode ini akan berfungsi, tetapi sangat
rumit:
Column( modifier = Modifier.padding(16.dp), content = { Text("Some text") Text("Some more text") Text("Last text") } )
Karena parameter content
adalah parameter terakhir dalam tanda tangan fungsi, dan
kita meneruskan nilainya sebagai ekspresi lambda, kita dapat mengeluarkannya dari
tanda kurung:
Column(modifier = Modifier.padding(16.dp)) { Text("Some text") Text("Some more text") Text("Last text") }
Kedua contoh tersebut memiliki arti yang sama persis. Kurung kurawal menentukan ekspresi
lambda yang diteruskan ke parameter content
.
Sebenarnya, jika satu-satunya parameter yang Anda teruskan adalah lambda akhir—yaitu,
jika parameter terakhir adalah lambda, dan Anda tidak meneruskan
parameter lain—Anda bisa menghapus tanda kurung sekaligus. Jadi, misalnya, Anda
tidak perlu meneruskan pengubah ke Column
. Anda dapat menulis kode seperti
ini:
Column { Text("Some text") Text("Some more text") Text("Last text") }
Sintaksis ini sangat umum di Compose, terutama untuk elemen tata letak seperti
Column
. Parameter terakhir adalah ekspresi lambda yang menentukan turunan
elemen, dan turunan tersebut ditentukan dalam tanda kurung kurawal setelah panggilan fungsi.
Cakupan dan penerima
Beberapa metode dan properti hanya tersedia dalam cakupan tertentu. Cakupan terbatas memungkinkan Anda menawarkan fungsi di tempat yang memerlukannya dan menghindari penggunaan fungsi tersebut secara tidak sengaja jika tidak sesuai.
Pertimbangkan contoh yang digunakan di Compose. Saat Anda memanggil komposisi
tata letak Row
, lambda konten Anda akan dipanggil secara otomatis dalam RowScope
.
Ini memungkinkan Row
mengekspos fungsi yang hanya valid dalam Row
.
Contoh di bawah menunjukkan cara Row
mengekspos nilai spesifik baris untuk
pengubah align
:
Row { Text( text = "Hello world", // This Text is inside a RowScope so it has access to // Alignment.CenterVertically but not to // Alignment.CenterHorizontally, which would be available // in a ColumnScope. modifier = Modifier.align(Alignment.CenterVertically) ) }
Beberapa API menerima lambda yang dipanggil dalam cakupan penerima. Lambda tersebut memiliki akses ke properti dan fungsi yang ditentukan di tempat lain, berdasarkan deklarasi parameter:
Box( modifier = Modifier.drawBehind { // This method accepts a lambda of type DrawScope.() -> Unit // therefore in this lambda we can access properties and functions // available from DrawScope, such as the `drawRectangle` function. drawRect( /*...*/ /* ... ) } )
Untuk informasi selengkapnya, lihat literal fungsi dengan penerima dalam dokumentasi Kotlin.
Properti yang didelegasikan
Kotlin mendukung properti
yang didelegasikan.
Properti ini dipanggil seolah-olah merupakan kolom, tetapi nilainya
ditentukan secara dinamis dengan mengevaluasi ekspresi. Anda dapat mengenali properti
ini dari penggunaan sintaksis by
:
class DelegatingClass { var name: String by nameGetterFunction() // ... }
Kode lain dapat mengakses properti dengan kode seperti ini:
val myDC = DelegatingClass() println("The name property is: " + myDC.name)
Saat println()
dieksekusi, nameGetterFunction()
dipanggil untuk menampilkan nilai
string.
Properti yang didelegasikan ini sangat berguna ketika Anda bekerja dengan properti yang didukung status:
var showDialog by remember { mutableStateOf(false) } // Updating the var automatically triggers a state change showDialog = true
Destrukturisasi class data
Jika Anda menentukan class
data, Anda dapat dengan mudah mengakses data dengan deklarasi
destrukturisasi. Misalnya,
anggaplah Anda menentukan class Person
:
data class Person(val name: String, val age: Int)
Jika Anda memiliki objek dari jenis tersebut, Anda dapat mengakses nilainya dengan kode seperti ini:
val mary = Person(name = "Mary", age = 35) // ... val (name, age) = mary
Anda akan sering melihat kode semacam itu di fungsi Compose:
Row { val (image, title, subtitle) = createRefs() // The `createRefs` function returns a data object; // the first three components are extracted into the // image, title, and subtitle variables. // ... }
Class data menyediakan banyak fungsi berguna lainnya. Misalnya, saat Anda
menentukan class data, compiler akan secara otomatis menentukan fungsi yang berguna seperti
equals()
dan copy()
. Anda dapat menemukan informasi selengkapnya di dokumentasi class
data.
Objek singleton
Kotlin memudahkan untuk mendeklarasikan class singleton yang selalu memiliki satu
instance saja. Objek singleton ini dideklarasikan dengan kata kunci object
.
Compose sering menggunakan objek tersebut. Misalnya,
MaterialTheme
didefinisikan sebagai objek singleton; properti MaterialTheme.colors
, shapes
, dan
typography
semuanya berisi nilai untuk tema saat ini.
Builder jenis aman dan DSL
Kotlin memungkinkan pembuatan domain-specific language (DSL) dengan builder jenis aman. DSL memungkinkan pembuatan struktur data hierarki kompleks dengan cara yang lebih mudah dikelola dan lebih mudah dibaca.
Jetpack Compose menggunakan DSL untuk beberapa API, seperti
LazyRow
dan LazyColumn
.
@Composable fun MessageList(messages: List<Message>) { LazyColumn { // Add a single item as a header item { Text("Message List") } // Add list of messages items(messages) { message -> Message(message) } } }
Kotlin menjamin builder jenis aman menggunakan
literal fungsi dengan penerima.
Jika menggunakan fungsi Canvas
yang dapat dikomposisi
sebagai contoh, fungsi dengan
DrawScope
sebagai penerima akan digunakan sebagai parameter, onDraw: DrawScope.() -> Unit
, sehingga memungkinkan blok kode memanggil
fungsi anggota yang ditentukan di DrawScope
.
Canvas(Modifier.size(120.dp)) { // Draw grey background, drawRect function is provided by the receiver drawRect(color = Color.Gray) // Inset content by 10 pixels on the left/right sides // and 12 by the top/bottom inset(10.0f, 12.0f) { val quadrantSize = size / 2.0f // Draw a rectangle within the inset bounds drawRect( size = quadrantSize, color = Color.Red ) rotate(45.0f) { drawRect(size = quadrantSize, color = Color.Blue) } } }
Pelajari lebih lanjut builder jenis aman dan DSL di dokumentasi Kotlin.
Coroutine Kotlin
Coroutine menawarkan dukungan pemrograman asinkron pada tingkat bahasa di Kotlin. Coroutine dapat menangguhkan eksekusi tanpa memblokir thread. UI responsif pada dasarnya asinkron, dan Jetpack Compose memecahkan masalah ini dengan menggunakan coroutine pada API level, bukan menggunakan callback.
Jetpack Compose menawarkan API yang menjadikan penggunaan coroutine aman dalam lapisan UI.
Fungsi rememberCoroutineScope
menampilkan CoroutineScope
yang dapat digunakan untuk membuat coroutine di pengendali peristiwa dan memanggil
API penangguhan Compose. Lihat contoh di bawah menggunakan
animateScrollTo
API
ScrollState
.
// Create a CoroutineScope that follows this composable's lifecycle val composableScope = rememberCoroutineScope() Button( // ... onClick = { // Create a new coroutine that scrolls to the top of the list // and call the ViewModel to load data composableScope.launch { scrollState.animateScrollTo(0) // This is a suspend function viewModel.loadData() } } ) { /* ... */ }
Coroutine secara default mengeksekusi blok kode secara berurutan. Coroutine
yang berjalan dan memanggil fungsi penangguhan menangguhkan eksekusinya hingga
fungsi penangguhan ditampilkan. Hal ini benar meskipun fungsi penangguhan memindahkan
eksekusi ke berbagai CoroutineDispatcher
. Pada contoh sebelumnya,
loadData
tidak akan dieksekusi hingga fungsi penangguhan animateScrollTo
ditampilkan.
Untuk mengeksekusi kode secara berurutan, coroutine baru harus dibuat. Pada contoh
di atas, untuk memparalelkan scroll ke bagian atas layar dan memuat data dari
viewModel
, dua coroutine diperlukan.
// Create a CoroutineScope that follows this composable's lifecycle val composableScope = rememberCoroutineScope() Button( // ... onClick = { // Scroll to the top and load data in parallel by creating a new // coroutine per independent work to do composableScope.launch { scrollState.animateScrollTo(0) } composableScope.launch { viewModel.loadData() } } ) { /* ... */ }
Coroutine memudahkan penggabungan API asinkron. Pada contoh
berikut, kita menggabungkan pengubah pointerInput
dengan API animasi untuk
menganimasikan posisi elemen saat pengguna mengetuk layar.
@Composable fun MoveBoxWhereTapped() { // Creates an `Animatable` to animate Offset and `remember` it. val animatedOffset = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) } Box( // The pointerInput modifier takes a suspend block of code Modifier .fillMaxSize() .pointerInput(Unit) { // Create a new CoroutineScope to be able to create new // coroutines inside a suspend function coroutineScope { while (true) { // Wait for the user to tap on the screen val offset = awaitPointerEventScope { awaitFirstDown().position } // Launch a new coroutine to asynchronously animate to // where the user tapped on the screen launch { // Animate to the pressed position animatedOffset.animateTo(offset) } } } } ) { Text("Tap anywhere", Modifier.align(Alignment.Center)) Box( Modifier .offset { // Use the animated offset as the offset of this Box IntOffset( animatedOffset.value.x.roundToInt(), animatedOffset.value.y.roundToInt() ) } .size(40.dp) .background(Color(0xff3c1361), CircleShape) ) }
Untuk mempelajari Coroutine lebih lanjut, lihat panduan Coroutine Kotlin di Android.
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Komponen dan tata letak Material
- Efek samping di Compose
- Dasar-dasar tata letak Compose