September 28, 2026

Jetpack Compose Dasar.

Dasar Jetpack Compose: fungsi composable, state & remember, recomposition, Modifier, Material 3, daftar dengan LazyColumn, dan navigasi.

Jetpack Compose adalah toolkit UI deklaratif untuk Android: UI ditulis sebagai fungsi Kotlin yang menerima state, bukan XML yang dimutasi lewat findViewById. Kalau pernah memakai React, modelnya akan terasa familiar — lihat Cara Menggunakan React sebagai pembanding. Seluruh UI NotiFly (riwayat notifikasi, pengaturan, JSON tester) dibangun dengan Compose + Material 3. Prasyarat: Dasar Kotlin .


1. Setup Singkat

Buat proyek dengan template Empty Activity di Android Studio; Compose sudah terpasang. Versi library Compose diatur lewat BOM sehingga semua artefak saling kompatibel:

// app/build.gradle.kts
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("org.jetbrains.kotlin.plugin.compose")   // compiler Compose (Kotlin 2.x)
}

dependencies {
    val composeBom = platform("androidx.compose:compose-bom:<versi-terbaru>")
    implementation(composeBom)
    implementation("androidx.compose.material3:material3")
    implementation("androidx.compose.ui:ui-tooling-preview")
    debugImplementation("androidx.compose.ui:ui-tooling")
    implementation("androidx.activity:activity-compose:<versi>")
    implementation("androidx.lifecycle:lifecycle-runtime-compose:<versi>")
}

Sejak Kotlin 2.0, compiler Compose ikut rilis Kotlin lewat plugin org.jetbrains.kotlin.plugin.compose — tidak perlu lagi mencocokkan kotlinCompilerExtensionVersion secara manual. Cek versi BOM terbaru di halaman rilis developer.android.com.


2. Composable Function

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            MaterialTheme {
                Greeting(name = "NotiFly")
            }
        }
    }
}

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    Column(modifier = modifier.padding(16.dp)) {
        Text("Halo, $name", style = MaterialTheme.typography.headlineSmall)
        Text("Siap meneruskan notifikasi", color = MaterialTheme.colorScheme.onSurfaceVariant)
    }
}

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

Aturan main composable:

  • Diberi anotasi @Composable, nama diawali huruf besar (seperti komponen), tidak mengembalikan nilai UI — ia memancarkan UI.
  • Harus idempoten dan bebas efek samping: bisa dipanggil ulang kapan saja, dalam urutan apa pun.
  • Konvensi: parameter modifier: Modifier = Modifier sebagai parameter opsional pertama, diteruskan ke elemen root.

Layout dasar: Column (vertikal), Row (horizontal), Box (tumpuk), Spacer, dan Scaffold untuk kerangka layar (top bar, bottom bar, snackbar, FAB).


3. State, remember, dan Recomposition

UI = f(state). Saat state yang dibaca sebuah composable berubah, Compose memanggil ulang composable itu (recomposition) — hanya bagian yang terdampak.

@Composable
fun KeywordEditor() {
    var input by remember { mutableStateOf("") }            // bertahan antar-recomposition
    var keywords by rememberSaveable { mutableStateOf(listOf<String>()) } // + bertahan saat rotasi

    Column(Modifier.padding(16.dp)) {
        OutlinedTextField(
            value = input,
            onValueChange = { input = it },
            label = { Text("Kata kunci") },
            singleLine = true,
        )
        Button(
            onClick = {
                keywords = keywords + input.trim().lowercase()
                input = ""
            },
            enabled = input.isNotBlank(),
        ) { Text("Tambah") }

        keywords.forEach { Text("• $it") }
    }
}
APIFungsi
mutableStateOf(x)state yang diamati Compose
remember { }simpan nilai selama composable ada di komposisi
rememberSaveable { }seperti remember, plus bertahan saat rotasi/process death
derivedStateOf { }nilai turunan yang hanya memicu recomposition bila hasilnya berubah
LaunchedEffect(key)jalankan coroutine saat masuk komposisi / key berubah
collectAsStateWithLifecycle()ubah StateFlow dari ViewModel menjadi state, sadar lifecycle

State hoisting: composable yang dapat dipakai ulang sebaiknya stateless — terima value dan onValueChange dari pemanggil. Sumber kebenaran untuk state layar berada di ViewModel (dibahas di Arsitektur Android Modern ).

@Composable
fun HistoryScreen(viewModel: HistoryViewModel = viewModel(factory = ViewModelFactory.Factory)) {
    val state by viewModel.uiState.collectAsStateWithLifecycle()
    HistoryContent(state = state, onRetry = viewModel::retry)   // stateless, mudah di-preview
}

4. Modifier

Modifier adalah rantai dekorasi: ukuran, padding, background, klik, dll. Urutan berpengaruh — setiap modifier membungkus hasil modifier sebelumnya.

@Composable
fun StatusBadge(label: String, container: Color, content: Color, modifier: Modifier = Modifier) {
    Text(
        text = label,
        style = MaterialTheme.typography.labelSmall,
        color = content,
        modifier = modifier
            .clip(RoundedCornerShape(6.dp))   // 1. potong bentuk
            .background(container)           // 2. warnai area yang sudah dipotong
            .padding(horizontal = 8.dp, vertical = 3.dp),  // 3. padding di dalam background
    )
}

Tukar urutan padding dan background, dan padding akan berada di luar warna. Modifier umum: fillMaxWidth(), size(), weight() (di Row/Column), clickable {}, border(), verticalScroll(rememberScrollState()).


5. Material 3 & Tema

@Composable
fun NotiFlyTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
    val context = LocalContext.current
    val colors = when {
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ->
            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
        darkTheme -> darkColorScheme()
        else -> lightColorScheme(primary = Color(0xFF0F766E))
    }
    MaterialTheme(colorScheme = colors, content = content)
}

Gunakan role warna (primary, onPrimary, surfaceVariant, errorContainer, …) alih-alih warna hardcode, agar light/dark mode dan dynamic color (Android 12+) otomatis benar. Material 3 tidak punya role “success”, jadi di NotiFly status SENT memakai sepasang warna hijau tetap yang tetap terbaca di kedua tema.

Komponen yang sering dipakai: Scaffold, TopAppBar, NavigationBar, Card, ListItem, FilterChip, ModalBottomSheet, AlertDialog, SnackbarHost.


6. Daftar dengan LazyColumn

Column + forEach merender semua item sekaligus; untuk daftar panjang pakai LazyColumn yang hanya mengomposisi item yang terlihat.

data class Notif(val id: Long, val title: String, val status: String)
data class DayGroup(val label: String, val items: List<Notif>)

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun HistoryList(groups: List<DayGroup>, onClick: (Notif) -> Unit, modifier: Modifier = Modifier) {
    LazyColumn(
        modifier = modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp),
    ) {
        groups.forEach { group ->
            stickyHeader(key = "day-${group.label}") {
                Text(group.label, style = MaterialTheme.typography.titleSmall,
                     modifier = Modifier.fillMaxWidth().background(MaterialTheme.colorScheme.surface))
            }
            items(group.items, key = { it.id }) { item ->
                ListItem(
                    headlineContent = { Text(item.title) },
                    supportingContent = { Text(item.status) },
                    modifier = Modifier.clickable { onClick(item) }.animateItem(),
                )
            }
        }
    }
}

key yang stabil (ID database) penting: posisi scroll dan animasi tetap benar saat item baru masuk di atas. Padanannya: LazyRow, LazyVerticalGrid.


7. Navigasi

Ada tiga pendekatan, pilih sesuai kebutuhan:

PendekatanKapan
Navigation Compose (type-safe route)Aplikasi dengan back stack: daftar → detail → edit
Navigation 3 (androidx.navigation3, stabil)Proyek baru yang ingin back stack sebagai state milik sendiri, layout adaptif
Pager / state sederhanaBeberapa tab setara tanpa back stack

Navigation Compose dengan route bertipe (butuh plugin kotlinx.serialization):

@Serializable object HistoryRoute
@Serializable data class DetailRoute(val id: Long)

@Composable
fun AppNavHost(navController: NavHostController = rememberNavController()) {
    NavHost(navController = navController, startDestination = HistoryRoute) {
        composable<HistoryRoute> {
            HistoryScreen(onOpen = { id -> navController.navigate(DetailRoute(id)) })
        }
        composable<DetailRoute> { entry ->
            val route: DetailRoute = entry.toRoute()
            DetailScreen(id = route.id, onBack = { navController.popBackStack() })
        }
    }
}

NotiFly justru tidak memakai NavHost: empat layar utamanya setara, jadi cukup HorizontalPager yang dikendalikan NavigationBar — bisa di-swipe, tanpa back stack yang perlu dikelola. Detail item dibuka sebagai ModalBottomSheet, bukan layar baru.


Kesalahan Umum

KesalahanAkibatPerbaikan
mutableStateOf tanpa rememberState ter-reset setiap recompositionremember { mutableStateOf(...) }
Memanggil API / menulis DB langsung di body composableTerpanggil berkali-kaliPindahkan ke ViewModel atau LaunchedEffect
Mengubah MutableList di dalam stateUI tidak ter-update (referensi sama)Ganti dengan list baru: list = list + x
LazyColumn tanpa keyScroll melompat, animasi salahitems(list, key = { it.id })
collectAsState() biasa untuk Flow dari ViewModelTetap collect saat app di backgroundcollectAsStateWithLifecycle()
Urutan modifier asalPadding/klik/background di area yang salahBaca rantai dari luar ke dalam
Warna hardcode di mana-manaDark mode rusakPakai MaterialTheme.colorScheme
LazyColumn di dalam Column yang verticalScrollCrash: infinite height constraintsSatu container scroll saja; gabungkan item ke LazyColumn

Hey! I’m Fanny, the software engineer tending to this digital garden. You can read more about me, or subscribe by email.

Comments