Mobile

Jetpack Compose 2026: Membangun UI Android Modern yang Responsif dari Nol

Pernah nggak sih kamu buka Android Studio, lihat file XML layout yang isinya ratusan baris, dan langsung pusing? Saya dulu banget ngalamin itu. Setiap kali mau ganti posisi tombol aja harus bolak-balik antara XML dan Activity, belum lagi kalau ada error di preview tapi nggak muncul di runtime. Bikin frustasi.

Nah, di 2026 ini, Google udah bikin solusi yang namanya Jetpack Compose framework declarative UI yang bikin kamu bisa bikin seluruh tampilan Android cuma pake Kotlin. Nggak ada lagi XML. Nggak ada lagi findViewById. Semua ditulis dalam bahasa yang sama, di satu tempat yang sama.

Kalau kamu masih ragu buat migrasi atau baru mau mulai belajar Android development, artikel ini bakal jadi panduan lengkap kamu. Kita bakal mulai dari nol, bikin beberapa contoh app sederhana, dan bahas konsep-konsep penting yang wajib kamu pahami.

Kenapa Jetpack Compose? Apa Bedanya sama View System Lama?

Android View system yang lama (XML-based) udah ada sejak Android pertama kali rilis. Dia bekerja secara imperatif kamu harus bilang sistem "lakukan ini, lalu itu, lalu update ini." Kalau ada perubahan data, kamu harus manual update setiap UI element yang terpengaruh.

Jetpack Compose bekerja secara declarative. Kamu cukup bilang "kalau data-nya seperti ini, tampilannya harusnya seperti ini." Compose yang bakal handle semua proses update-nya otomatis.

Perbedaan utamanya:

  • Kode lebih sedikit Compose butuh ~30-50% lebih sedikit kode dibanding XML untuk UI yang sama
  • Preview real-time Kamu bisa lihat perubahan UI tanpa compile ulang ke emulator
  • State management terintegrasi Nggak perlu library tambahan buat handle state
  • Theme system yang fleksibel Material 3 built-in, bisa customize dari warna sampai bentuk
  • Animation yang mudah Animasi kompleks bisa ditulis dalam beberapa baris kode

Setup Project: Mulai dari Android Studio

Pastikan kamu pakai Android Studio terbaru (Ladybug atau versi lebih baru). Saat bikin project baru, pilih template "Empty Activity" yang sudah menggunakan Compose. Android Studio otomatis setup dependencies yang diperlukan.

Kalau kamu mau menambahkan Compose ke project yang sudah ada, tambahkan ini di build.gradle.kts (Module level):


plugins {
 id("org.jetbrains.kotlin.plugin.compose")
}

android {
 namespace = "com.example.myapp"
 compileSdk = 35

 defaultConfig {
 minSdk = 24
 targetSdk = 35
 }

 buildFeatures {
 compose = true
 }
}

dependencies {
 // Compose BOM - mengatur versi semua library Compose
 val composeBom = platform("androidx.compose:compose-bom:2025.01.01")
 implementation(composeBom)

 // Core Compose
 implementation("androidx.compose.ui:ui")
 implementation("androidx.compose.ui:ui-tooling-preview")
 implementation("androidx.compose.material3:material3")
 implementation("androidx.compose.material:material-icons-extended")

 // Activity Compose (untuk setContent{})
 implementation("androidx.activity:activity-compose:1.9.3")

 // ViewModel + Compose integration
 implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")

 // Navigation Compose
 implementation("androidx.navigation:navigation-compose:2.8.5")

 // Debug tools
 debugImplementation("androidx.compose.ui:ui-tooling")
}

Tips: Pakai compose-bom supaya semua library Compose kamu pakai versi yang kompatibel. Nggak perlu lagi mikirin versi satu per satu.

Composable Function: Blok Bangunan UI

Di Compose, semua UI dibuat dari Composable functions. Ini adalah function biasa yang didekorasi dengan @Composable. Mari kita mulai dengan contoh sederhana:


@Composable
fun Greeting(name: String) {
 Text(
 text = "Halo, $name! Selamat datang di app ini.",
 fontSize = 18.sp,
 fontWeight = FontWeight.Medium,
 color = MaterialTheme.colorScheme.onSurface
 )
}

// Preview untuk Android Studio
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
 MyApplicationTheme {
 Greeting("Developer")
 }
}

Beberapa hal yang perlu kamu perhatikan:

  • Nama Composable function pakai PascalCase (bukan camelCase seperti function biasa)
  • @Preview bikin kamu bisa lihat hasilnya di panel Preview tanpa run app
  • MaterialTheme memberikan akses ke tema aplikasi (warna, font, bentuk)

Layout: Column, Row, dan Box

Compose punya tiga layout dasar yang wajib kamu kuasai:

Column menyusun elemen secara vertikal (atas ke bawah):


@Composable
fun UserProfile() {
 Column(
 modifier = Modifier
 .fillMaxWidth()
 .padding(16.dp),
 horizontalAlignment = Alignment.CenterHorizontally
 ) {
 // Avatar
 Image(
 painter = painterResource(R.drawable.avatar),
 contentDescription = "Foto profil",
 modifier = Modifier
 .size(100.dp)
 .clip(CircleShape)
 )

 Spacer(modifier = Modifier.height(12.dp))

 // Nama
 Text(
 text = "Budi Santoso",
 style = MaterialTheme.typography.headlineSmall,
 fontWeight = FontWeight.Bold
 )

 // Bio
 Text(
 text = "Android Developer | Jakarta",
 style = MaterialTheme.typography.bodyMedium,
 color = MaterialTheme.colorScheme.onSurfaceVariant
 )
 }
}

Row menyusun elemen secara horizontal (kiri ke kanan):


@Composable
fun ActionButtons() {
 Row(
 modifier = Modifier.fillMaxWidth(),
 horizontalArrangement = Arrangement.SpaceEvenly
 ) {
 Button(onClick = { /* handle click */ }) {
 Icon(Icons.Default.Favorite, contentDescription = null)
 Spacer(modifier = Modifier.width(8.dp))
 Text("Like")
 }

 OutlinedButton(onClick = { /* handle click */ }) {
 Icon(Icons.Default.Share, contentDescription = null)
 Spacer(modifier = Modifier.width(8.dp))
 Text("Share")
 }

 IconButton(onClick = { /* handle click */ }) {
 Icon(Icons.Default.Bookmark, contentDescription = "Bookmark")
 }
 }
}

Box menumpuk elemen di atas satu sama lain (seperti FrameLayout):


@Composable
fun ImageWithBadge() {
 Box(modifier = Modifier.size(120.dp)) {
 Image(
 painter = painterResource(R.drawable.product),
 contentDescription = "Produk",
 modifier = Modifier.fillMaxSize(),
 contentScale = ContentScale.Crop
 )

 // Badge "Baru" di pojok kanan atas
 Surface(
 color = MaterialTheme.colorScheme.error,
 shape = RoundedCornerShape(4.dp),
 modifier = Modifier
 .align(Alignment.TopEnd)
 .padding(4.dp)
 ) {
 Text(
 text = "Baru",
 color = MaterialTheme.colorScheme.onError,
 fontSize = 10.sp,
 modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp)
 )
 }
 }
}

State Management: Bikin UI yang Reaktif

Ini adalah konsep paling penting di Compose. State adalah data yang bisa berubah seiring waktu. Ketika state berubah, Compose otomatis me-render ulang UI yang terpengaruh.

Cara paling dasar pakai remember dan mutableStateOf:


@Composable
fun Counter() {
 // remember = state disimpan saat recomposition
 var count by remember { mutableIntStateOf(0) }

 Column(
 modifier = Modifier.padding(16.dp),
 horizontalAlignment = Alignment.CenterHorizontally
 ) {
 Text(
 text = "Kamu sudah klik $count kali",
 style = MaterialTheme.typography.headlineMedium
 )

 Spacer(modifier = Modifier.height(16.dp))

 Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
 Button(onClick = { count++ }) {
 Text("Tambah")
 }
 OutlinedButton(onClick = { count = 0 }) {
 Text("Reset")
 }
 }
 }
}

Penting: Jangan pernah modify state di luar onClick atau callback. Compose bisa me-recompose function berkali-kali, dan kalau kamu langsung ubah state di body function, hasilnya nggak terduga.

Untuk app yang lebih kompleks, gunakan ViewModel:


// ViewModel
class TodoViewModel : ViewModel() {
 private val _tasks = mutableStateListOf<Task>()
 val tasks: List<Task> = _tasks

 fun addTask(title: String) {
 _tasks.add(Task(title = title, done = false))
 }

 fun toggleTask(index: Int) {
 _tasks[index] = _tasks[index].copy(done = !_tasks[index].done)
 }
}

data class Task(val title: String, val done: Boolean)

// Composable yang pakai ViewModel
@Composable
fun TodoScreen(viewModel: TodoViewModel = viewModel()) {
 var newTaskText by remember { mutableStateOf("") }

 Column(modifier = Modifier.padding(16.dp)) {
 // Input field
 Row(verticalAlignment = Alignment.CenterVertically) {
 OutlinedTextField(
 value = newTaskText,
 onValueChange = { newTaskText = it },
 label = { Text("Tugas baru") },
 modifier = Modifier.weight(1f)
 )
 Spacer(modifier = Modifier.width(8.dp))
 Button(
 onClick = {
 if (newTaskText.isNotBlank()) {
 viewModel.addTask(newTaskText)
 newTaskText = ""
 }
 }
 ) {
 Text("Tambah")
 }
 }

 Spacer(modifier = Modifier.height(16.dp))

 // Daftar tugas
 LazyColumn {
 itemsIndexed(viewModel.tasks) { index, task ->
 Row(
 modifier = Modifier
 .fillMaxWidth()
 .clickable { viewModel.toggleTask(index) }
 .padding(vertical = 8.dp),
 verticalAlignment = Alignment.CenterVertically
 ) {
 Checkbox(
 checked = task.done,
 onCheckedChange = { viewModel.toggleTask(index) }
 )
 Text(
 text = task.title,
 textDecoration = if (task.done)
 TextDecoration.LineThrough else TextDecoration.None
 )
 }
 }
 }
 }
}

Material 3: Tampilan yang Konsisten dan Cantik

Material 3 (M3) adalah sistem desain terbaru dari Google. Compose sudah terintegrasi penuh dengan M3, termasuk Dynamic Color yang bisa mengambil warna dari wallpaper pengguna di Android 12+.

Setup tema di Theme.kt:


@Composable
fun MyAppTheme(
 darkTheme: Boolean = isSystemInDarkTheme(),
 dynamicColor: Boolean = true, // Android 12+ dynamic color
 content: @Composable () -> Unit
) {
 val colorScheme = when {
 dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
 val context = LocalContext.current
 if (darkTheme) dynamicDarkColorScheme(context)
 else dynamicLightColorScheme(context)
 }
 darkTheme -> darkColorScheme(
 primary = Color(0xFF80DEEA), // Cyan terang
 secondary = Color(0xFFA5D6A7), // Hijau lembut
 tertiary = Color(0xFFFFCC80) // Oranye pastel
 )
 else -> lightColorScheme(
 primary = Color(0xFF00838F), // Cyan gelap
 secondary = Color(0xFF388E3C), // Hijau
 tertiary = Color(0xFFFF9800) // Oranye
 )
 }

 MaterialTheme(
 colorScheme = colorScheme,
 typography = Typography(
 headlineLarge = TextStyle(
 fontWeight = FontWeight.Bold,
 fontSize = 28.sp,
 letterSpacing = (-0.5).sp
 ),
 bodyLarge = TextStyle(
 fontSize = 16.sp,
 lineHeight = 24.sp,
 letterSpacing = 0.5.sp
 )
 ),
 shapes = Shapes(
 small = RoundedCornerShape(8.dp),
 medium = RoundedCornerShape(12.dp),
 large = RoundedCornerShape(16.dp)
 ),
 content = content
 )
}

Dengan setup ini, semua komponen M3 (Button, Card, TextField, dll.) otomatis pakai warna dan bentuk yang sudah kamu definisikan. Kalau kamu ganti tema, seluruh app berubah tanpa perlu edit satu per satu.

List dengan LazyColumn: Performa untuk Data Banyak

Kalau kamu pernah pakai RecyclerView di View system lama, kamu tau ribetnya bikin Adapter, ViewHolder, layout manager, DiffUtil. Di Compose, semua itu jadi jauh lebih sederhana:


@Composable
fun ArticleList(articles: List<Article>) {
 LazyColumn(
 contentPadding = PaddingValues(16.dp),
 verticalArrangement = Arrangement.spacedBy(12.dp)
 ) {
 items(
 items = articles,
 key = { it.id } // Penting untuk animasi yang smooth
 ) { article ->
 ArticleCard(article = article)
 }
 }
}

@Composable
fun ArticleCard(article: Article) {
 Card(
 modifier = Modifier.fillMaxWidth(),
 elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
 shape = RoundedCornerShape(12.dp)
 ) {
 Column {
 // Gambar artikel
 AsyncImage(
 model = article.imageUrl,
 contentDescription = article.title,
 modifier = Modifier
 .fillMaxWidth()
 .height(180.dp),
 contentScale = ContentScale.Crop
 )

 Column(modifier = Modifier.padding(16.dp)) {
 // Kategori chip
 Surface(
 color = MaterialTheme.colorScheme.primaryContainer,
 shape = RoundedCornerShape(20.dp)
 ) {
 Text(
 text = article.category,
 modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp),
 style = MaterialTheme.typography.labelSmall,
 color = MaterialTheme.colorScheme.onPrimaryContainer
 )
 }

 Spacer(modifier = Modifier.height(8.dp))

 Text(
 text = article.title,
 style = MaterialTheme.typography.titleMedium,
 fontWeight = FontWeight.SemiBold,
 maxLines = 2,
 overflow = TextOverflow.Ellipsis
 )

 Spacer(modifier = Modifier.height(4.dp))

 Text(
 text = article.summary,
 style = MaterialTheme.typography.bodySmall,
 color = MaterialTheme.colorScheme.onSurfaceVariant,
 maxLines = 3,
 overflow = TextOverflow.Ellipsis
 )

 Spacer(modifier = Modifier.height(8.dp))

 // Footer: tanggal + author
 Row(
 modifier = Modifier.fillMaxWidth(),
 horizontalArrangement = Arrangement.SpaceBetween,
 verticalAlignment = Alignment.CenterVertically
 ) {
 Text(
 text = article.date,
 style = MaterialTheme.typography.labelSmall,
 color = MaterialTheme.colorScheme.outline
 )
 Text(
 text = article.author,
 style = MaterialTheme.typography.labelSmall,
 color = MaterialTheme.colorScheme.primary
 )
 }
 }
 }
 }
}

Keuntungan LazyColumn:

  • Otomatis recycle item yang nggak terlihat (performa tetap bagus walau ada 1000+ item)
  • Parameter key membantu Compose mengenali item yang pindah posisi
  • Animasi penambahan/penghapusan item built-in
  • Content padding dan spacing langsung di parameter, nggak perlu divider manual

Navigation: Pindah Halaman dengan Mudah

Compose punya library navigasi sendiri yang terintegrasi. Setup-nya seperti ini:


// Definisikan route
sealed class Screen(val route: String) {
 object Home : Screen("home")
 object Detail : Screen("detail/{articleId}") {
 fun createRoute(articleId: Int) = "detail/$articleId"
 }
 object Profile : Screen("profile")
}

@Composable
fun AppNavigation() {
 val navController = rememberNavController()

 NavHost(
 navController = navController,
 startDestination = Screen.Home.route
 ) {
 composable(Screen.Home.route) {
 HomeScreen(
 onArticleClick = { articleId ->
 navController.navigate(Screen.Detail.createRoute(articleId))
 },
 onProfileClick = {
 navController.navigate(Screen.Profile.route)
 }
 )
 }

 composable(
 route = Screen.Detail.route,
 arguments = listOf(navArgument("articleId") { type = NavType.IntType })
 ) { backStackEntry ->
 val articleId = backStackEntry.arguments?.getInt("articleId") ?: 0
 DetailScreen(
 articleId = articleId,
 onBack = { navController.popBackStack() }
 )
 }

 composable(Screen.Profile.route) {
 ProfileScreen()
 }
 }
}

Tips yang Sering Dilupain Developer Pemula

1. Hindari Recomposition yang Nggak Perlu

Recomposition adalah proses Compose me-render ulang Composable. Kalau kamu nggak hati-hati, function yang nggak perlu di-render ulang bisa bikin app jadi lambat.


// BURUK: lambda baru dibuat setiap recomposition
@Composable
fun BadExample(viewModel: MyViewModel) {
 Button(onClick = { viewModel.doSomething() }) {
 Text("Click")
 }
}

// BAIK: lambda stabil
@Composable
fun GoodExample(viewModel: MyViewModel) {
 val onClick = remember { { viewModel.doSomething() } }
 Button(onClick = onClick) {
 Text("Click")
 }
}

2. Pakai derivedStateOf untuk Computed State


@Composable
fun SearchResults(query: String, items: List<String>) {
 // Hanya recompute kalau items atau query berubah
 val filteredItems by remember {
 derivedStateOf {
 if (query.isBlank()) items
 else items.filter { it.contains(query, ignoreCase = true) }
 }
 }

 LazyColumn {
 items(filteredItems) { item ->
 Text(item, modifier = Modifier.padding(16.dp))
 }
 }
}

3. Side Effects: LaunchedEffect dan DisposableEffect


@Composable
fun Timer() {
 var seconds by remember { mutableIntStateOf(0) }

 // LaunchedEffect: berjalan saat Composable masuk, cancel saat keluar
 LaunchedEffect(Unit) {
 while (true) {
 delay(1000)
 seconds++
 }
 }

 Text("Waktu: ${seconds}s")
}

@Composable
fun LifecycleAwareComponent() {
 val lifecycleOwner = LocalLifecycleOwner.current

 // DisposableEffect: setup + cleanup
 DisposableEffect(lifecycleOwner) {
 val observer = LifecycleEventObserver { _, event ->
 if (event == Lifecycle.Event.ON_PAUSE) {
 // Pause sensor, stop animation, etc.
 }
 }
 lifecycleOwner.lifecycle.addObserver(observer)

 onDispose {
 lifecycleOwner.lifecycle.removeObserver(observer)
 }
 }
}

Kesimpulan

Jetpack Compose benar-benar mengubah cara kita bikin UI Android. Dari yang dulu harus bolak-balik XML dan Kotlin, sekarang semua di satu tempat. Learning curve-nya memang ada, terutama kalau kamu udah terbiasa dengan View system lama. Tapi begitu kamu paham konsep Composable, State, dan Recomposition, development bakal jadi jauh lebih cepat dan menyenangkan.

Saran saya: mulai dari project kecil. Coba rebuild salah satu screen dari app kamu yang existing pakai Compose. Dari situ kamu bakal ngerasain sendiri bedanya lebih sedikit kode, lebih sedikit bug, dan preview real-time yang bikin iterasi desain jadi super cepat.

Ada pertanyaan atau pengalaman kamu pakai Compose? Share di kolom komentar ya. Apakah kamu team Compose atau masih setia sama XML? Saya penasaran!


You may also like


0 Comments


Leave a Reply

Comments with links or spam keywords will be rejected.
Scroll to Top