Mobile

Tutorial Jetpack Compose - Membuat UI Android Modern dari Nol

Dulu waktu pertama kali bikin UI Android pakai XML layout, rasanya ribet banget. Mau bikin satu halaman sederhana aja harus bolak-balik antara file XML dan Kotlin/Java. Belum lagi masalah constraint di ConstraintLayout yang kadang bikin pusing. Sekarang ada Jetpack Compose, semuanya jadi lebih intuitif kamu tulis UI langsung di Kotlin, tanpa XML, tanpa findViewById, tanpa masalah.

Jetpack Compose itu framework UI modern dari Google yang bikin development Android jauh lebih cepat dan menyenangkan. Kalau kamu masih pakai XML layout atau baru mau mulai belajar Android, artikel ini cocok banget. Kita akan belajar dari nol mulai dari setup project sampai bikin aplikasi yang benar-benar bisa dipakai.

Kenapa Harus Pakai Jetpack Compose di 2026?

Sebelum masuk ke kode, penting buat paham kenapa Compose itu penting. Google sudah menjadikan Compose sebagai rekomendasi utama untuk membangun UI Android. Artinya, semua library baru dan fitur Android terbaru dibangun dengan Compose-first approach.

Beberapa keuntungan utama Compose:

  • Declarative UI kamu deskripsikan seperti apa UI-nya, bukan cara membuatnya. Lebih sedikit kode, lebih sedikit bug
  • Less boilerplate tidak perlu XML, adapter, ViewHolder, atau findViewById lagi
  • Real-time preview lihat perubahan UI langsung di Android Studio tanpa compile ulang
  • Kotlin-first dibangun sepenuhnya dengan Kotlin, jadi bisa manfaatkan semua fitur Kotlin seperti coroutine dan extension function
  • Interoperable bisa dicampur dengan XML layout yang sudah ada, jadi tidak perlu rewrite semuanya sekaligus

Setup Project Baru dengan Compose

Untuk mulai, kamu butuh Android Studio versi terbaru (Ladybug atau lebih baru). Buat project baru dan pilih template "Empty Activity" yang sudah pakai Compose.

Pastikan build.gradle.kts (Module level) punya dependency berikut:


dependencies {
    // Compose BOM   mengatur versi semua library Compose
    val composeBom = platform("androidx.compose:compose-bom:2025.04.00")
    implementation(composeBom)
    
    // Core Compose
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.ui:ui-graphics")
    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.10.1")
    
    // Lifecycle & ViewModel
    implementation("androidx.lifecycle:lifecycle-runtime-compose:2.9.0")
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.9.0")
    
    // Navigation
    implementation("androidx.navigation:navigation-compose:2.9.0")
    
    // Debug tools
    debugImplementation("androidx.compose.ui:ui-tooling")
}

Di bagian buildFeatures, aktifkan Compose:


android {
    buildFeatures {
        compose = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.14"
    }
}

Anatomi Composable Function

Di Compose, UI dibangun dari function yang di-annotate dengan @Composable. Setiap function ini mendeskripsikan sebagian dari UI. Konsepnya mirip dengan component di React atau widget di Flutter.


import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun Greeting(name: String) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "Halo, $name!",
            style = MaterialTheme.typography.headlineMedium
        )
        Spacer(modifier = Modifier.height(8.dp))
        Text(
            text = "Selamat datang di Jetpack Compose",
            style = MaterialTheme.typography.bodyLarge,
            color = MaterialTheme.colorScheme.onSurfaceVariant
        )
    }
}

Lihat bedanya dengan XML? Tidak ada file XML terpisah, tidak ada inflater, tidak ada findViewById. Semua langsung di Kotlin. Dan yang paling keren kamu bisa preview function ini langsung di Android Studio:


@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    MaterialTheme {
        Greeting("Developer")
    }
}

Kalau kamu tertarik mempelajari lebih lanjut tentang pengembangan mobile, bisa cek juga perbandingan Flutter vs React Native yang sudah saya tulis sebelumnya.

State Management di Compose

State itu konsep paling penting di Compose. Setiap kali data berubah, UI otomatis ter-update. Ini yang disebut "reactive" atau "declarative" programming.


import androidx.compose.runtime.*
import androidx.compose.material3.*

@Composable
fun Counter() {
    // remember = simpan state saat recomposition
    // mutableStateOf = buat state yang bisa diubah
    var count by remember { mutableStateOf(0) }
    
    Column(
        modifier = Modifier.padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "Klik: $count kali",
            style = MaterialTheme.typography.headlineSmall
        )
        Spacer(modifier = Modifier.height(16.dp))
        Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
            Button(onClick = { count-- }) {
                Text("-1")
            }
            Button(onClick = { count = 0 }) {
                Text("Reset")
            }
            Button(onClick = { count++ }) {
                Text("+1")
            }
        }
    }
}

Ada beberapa tipe state di Compose:

  • remember { mutableStateOf() } state lokal yang survive recomposition, tapi hilang saat configuration change (rotate layar)
  • rememberSaveable { mutableStateOf() } survive configuration change juga. Pakai ini untuk data yang penting
  • ViewModel + StateFlow state yang survive bahkan saat activity di-recreate. Cocok untuk data dari API atau database

Untuk state management yang lebih kompleks, kamu bisa menggunakan pola ViewModel:


import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

data class TaskListUiState(
    val tasks: List<String> = emptyList(),
    val isLoading: Boolean = false,
    val error: String? = null
)

class TaskViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(TaskListUiState())
    val uiState: StateFlow<TaskListUiState> = _uiState.asStateFlow()
    
    fun addTask(task: String) {
        _uiState.value = _uiState.value.copy(
            tasks = _uiState.value.tasks + task
        )
    }
    
    fun removeTask(index: Int) {
        _uiState.value = _uiState.value.copy(
            tasks = _uiState.value.tasks.toMutableList().apply {
                removeAt(index)
            }
        )
    }
}

Lalu di composable, collect state-nya:


@Composable
fun TaskScreen(viewModel: TaskViewModel = viewModel()) {
    val uiState by viewModel.uiState.collectAsState()
    var newTask by remember { mutableStateOf("") }
    
    Column(modifier = Modifier.padding(16.dp)) {
        OutlinedTextField(
            value = newTask,
            onValueChange = { newTask = it },
            label = { Text("Tambah tugas baru") },
            modifier = Modifier.fillMaxWidth()
        )
        Spacer(modifier = Modifier.height(8.dp))
        Button(
            onClick = {
                if (newTask.isNotBlank()) {
                    viewModel.addTask(newTask)
                    newTask = ""
                }
            },
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("Tambah")
        }
        Spacer(modifier = Modifier.height(16.dp))
        LazyColumn {
            itemsIndexed(uiState.tasks) { index, task ->
                Card(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(vertical = 4.dp)
                ) {
                    Row(
                        modifier = Modifier.padding(16.dp),
                        horizontalArrangement = Arrangement.SpaceBetween
                    ) {
                        Text(text = task, modifier = Modifier.weight(1f))
                        IconButton(onClick = { viewModel.removeTask(index) }) {
                            Icon(
                                Icons.Default.Delete,
                                contentDescription = "Hapus"
                            )
                        }
                    }
                }
            }
        }
    }
}

Membuat List dengan LazyColumn

Kalau di XML kamu pakai RecyclerView dengan adapter dan ViewHolder yang ribet, di Compose cukup pakai LazyColumn. Jauh lebih simpel:


data class Article(val title: String, val excerpt: String, val date: String)

@Composable
fun ArticleList(articles: List<Article>) {
    LazyColumn(
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        items(
            items = articles,
            key = { it.title } // stable key untuk animasi yang smooth
        ) { article ->
            Card(
                elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
            ) {
                Column(modifier = Modifier.padding(16.dp)) {
                    Text(
                        text = article.title,
                        style = MaterialTheme.typography.titleMedium
                    )
                    Spacer(modifier = Modifier.height(4.dp))
                    Text(
                        text = article.excerpt,
                        style = MaterialTheme.typography.bodyMedium,
                        maxLines = 2,
                        overflow = TextOverflow.Ellipsis
                    )
                    Spacer(modifier = Modifier.height(8.dp))
                    Text(
                        text = article.date,
                        style = MaterialTheme.typography.labelSmall,
                        color = MaterialTheme.colorScheme.onSurfaceVariant
                    )
                }
            }
        }
    }
}

Perhatikan key parameter di items. Ini penting untuk performa Compose pakai key untuk tahu item mana yang berubah saat list di-update, sehingga tidak perlu render ulang semua item.

Navigation antar Screen

Hampir semua aplikasi punya lebih dari satu halaman. Di Compose, navigasi ditangani oleh NavHost:


import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController

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

@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    
    NavHost(
        navController = navController,
        startDestination = Screen.Home.route
    ) {
        composable(Screen.Home.route) {
            HomeScreen(
                onArticleClick = { id ->
                    navController.navigate(Screen.Detail.createRoute(id))
                },
                onProfileClick = {
                    navController.navigate(Screen.Profile.route)
                }
            )
        }
        composable(Screen.Detail.route) { backStackEntry ->
            val itemId = backStackEntry.arguments?.getString("itemId") ?: ""
            DetailScreen(
                itemId = itemId,
                onBack = { navController.popBackStack() }
            )
        }
        composable(Screen.Profile.route) {
            ProfileScreen(
                onBack = { navController.popBackStack() }
            )
        }
    }
}

Setiap screen adalah composable function yang menerima callback untuk navigasi. Pola ini bikin kode tetap clean dan testable setiap screen tidak perlu tahu tentang navigation library, cukup terima callback.

Material Design 3 dengan Compose

Compose punya integrasi native dengan Material Design 3 (Material You). Kamu bisa bikin UI yang cantik dengan sedikit kode:


@Composable
fun ArticleCard(
    title: String,
    description: String,
    category: String,
    onClick: () -> Unit
) {
    Card(
        onClick = onClick,
        modifier = Modifier.fillMaxWidth(),
        colors = CardDefaults.cardColors(
            containerColor = MaterialTheme.colorScheme.surfaceContainerLow
        )
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            // Category chip
            SuggestionChip(
                onClick = { },
                label = { Text(category, style = MaterialTheme.typography.labelSmall) }
            )
            Spacer(modifier = Modifier.height(8.dp))
            Text(
                text = title,
                style = MaterialTheme.typography.titleLarge
            )
            Spacer(modifier = Modifier.height(4.dp))
            Text(
                text = description,
                style = MaterialTheme.typography.bodyMedium,
                color = MaterialTheme.colorScheme.onSurfaceVariant
            )
        }
    }
}

MaterialTheme otomatis menyesuaikan warna berdasarkan wallpaper device pengguna (Dynamic Color). Fitur ini hanya tersedia di Android 12+, tapi Compose punya fallback color scheme untuk device yang lebih lama.

Side Effects Handle Operasi Async

Di Compose, ada beberapa function khusus untuk "side effects" operasi yang terjadi di luar siklus rendering:


@Composable
fun UserProfile(userId: String) {
    var user by remember { mutableStateOf<User?>(null) }
    var isLoading by remember { mutableStateOf(true) }
    
    // LaunchedEffect   jalankan coroutine saat composable pertama muncul
    LaunchedEffect(userId) {
        isLoading = true
        user = userRepository.fetchUser(userId) // suspend function
        isLoading = false
    }
    
    when {
        isLoading -> CircularProgressIndicator()
        user != null -> UserInfoCard(user!!)
        else -> Text("User tidak ditemukan")
    }
}

@Composable
fun AnalyticsScreen(screenName: String) {
    // DisposableEffect   jalankan saat masuk, cleanup saat keluar
    DisposableEffect(screenName) {
        analytics.trackScreenView(screenName)
        onDispose {
            analytics.trackScreenExit(screenName)
        }
    }
}

Penting untuk diingat: jangan panggil API atau operasi berat langsung di dalam composable function. Selalu gunakan LaunchedEffect atau ViewModel untuk operasi async.

Tips Praktis untuk Production

Setelah cukup lama pakai Compose di project production, ini beberapa tips yang saya pelajari:

  • Pakai key di LazyColumn/LazyRow tanpa key, Compose bisa salah mengira item yang berubah dan render ulang semuanya. Performa langsung turun drastis di list besar
  • Hindari lambda allocation di recomposable kalau kamu bikin lambda baru setiap recomposition, GC pressure naik. Pakai remember atau bikin function di ViewModel
  • Gunakan derivedStateOf untuk computed state yang depends pada beberapa state lain. Ini mencegah unnecessary recomposition
  • Test composable dengan ComposeTestRule jangan cuma rely pada manual testing. Compose punya testing framework yang powerful
  • Manfaatkan Modifier dengan benar urutan modifier itu penting. Modifier.padding(16.dp).background(Color.Red) beda hasilnya dengan Modifier.background(Color.Red).padding(16.dp)

Untuk project yang lebih besar, pertimbangkan juga arsitektur yang proper. Kamu bisa baca tentang state management patterns yang applicable juga di Android Compose, meskipun artikel itu fokus ke Flutter.

Kesimpulan

Jetpack Compose itu game-changer untuk Android development. Dari yang dulu bikin UI harus bolak-balik XML dan Kotlin, sekarang semuanya satu tempat. Declarative approach-nya bikin kode lebih mudah dibaca, dikurangi, dan di-debug.

Kalau kamu masih baru di Android development, langsung mulai dengan Compose jangan buang waktu belajar XML layout. Dan kalau kamu sudah experienced dengan XML, mulai migrate pelan-pelan. Compose bisa dicampur dengan View lama, jadi tidak perlu rewrite semuanya sekaligus.

Mulai dari project kecil dulu bikin to-do app atau weather app. Setelah terbiasa dengan composable, state, dan navigation, kamu bisa mulai explore fitur yang lebih advanced seperti custom layout, animation, dan gesture handling. Selamat coding!


You may also like


0 Comments


Leave a Reply

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