Episode ini menguasai interaksi pengguna: TextField, Button, Checkbox, dan Switch, deteksi gesture dengan pointer input, manajemen fokus dan keyboard, validasi input, serta Snackbar, dialog, dan bottom sheet.

Aplikasi tanpa interaksi hanyalah poster digital. Episode 7 membawa kalian dari statis ke interaktif: menangkap input pengguna, mendeteksi gesture, mengelola keyboard dan fokus, serta memberi umpan balik lewat Snackbar, dialog, dan bottom sheet.
Pola yang dipakai di sini konsisten dengan episode 5: state naik, event turun. Setiap kontrol menerima nilai dan callback, dan logika tinggal di hoisted state atau ViewModel.
Episode 7 ini meliputi kontrol input, gesture dan pointer input, fokus dan keyboard, validasi, serta komponen umpan balik.
OutlinedTextField dan TextField adalah input teks Material 3. Keduanya menerima value dan onValueChange — pola state hoisting yang sudah kalian kenal:
var email by rememberSaveable { mutableStateOf("") }
OutlinedTextField(
value = email,
onValueChange = { email = it },
label = { Text("Email") },
singleLine = true,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(onDone = { verifikasi() })
)KeyboardOptions mengatur tipe keyboard (KeyboardType.Email) dan aksi IME (ImeAction.Done), sementara KeyboardActions menangkap saat tombol Done ditekan — pola yang membuat form terasa responsif.
Untuk pilihan biner, Checkbox dan Switch memakai state Boolean:
var setuju by remember { mutableStateOf(false) }
var notifikasi by remember { mutableStateOf(true) }
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(checked = setuju, onCheckedChange = { setuju = it })
Text("Setuju dengan syarat")
}
Row(verticalAlignment = Alignment.CenterVertically) {
Switch(checked = notifikasi, onCheckedChange = { notifikasi = it })
Text("Aktifkan notifikasi")
}Kebanyakan interaksi cukup dengan modifier clickable, toggleable, atau selectable. Untuk gesture yang lebih kaya, gunakan pointerInput dengan detector:
Modifier
.pointerInput(Unit) {
detectTapGestures(
onTap = { onTampilkanToast() },
onLongPress = { onBukaMenu() }
)
}
.padding(16.dp)detectTapGestures membedakan onTap dan onLongPress. Untuk drag dan pinch, Compose menyediakan detectDragGestures dan detectTransformGestures — dasar untuk komponen seperti carousel yang bisa digeser. Saat menguji tanpa menyentuh layar, adb shell input tap 540 1200 mengirim tap di koordinat tertentu lewat terminal.
Fokus berpindah saat pengguna menekan tombol IME. FocusRequester memberi kontrol programatik:
val passwordFocus = FocusRequester()
OutlinedTextField(
value = password,
onValueChange = { password = it },
modifier = Modifier.focusRequester(passwordFocus)
)
LaunchedEffect(Unit) { passwordFocus.requestFocus() }Modifier.focusRequester(passwordFocus) mendaftarkan requester, dan passwordFocus.requestFocus() memindahkan fokus saat komposisi dimulai — berguna untuk menampilkan keyboard langsung saat layar form dibuka.
Validasi menghasilkan pesan error yang ditampilkan lewat isError dan supportingText:
val emailValid = email.contains("@")
OutlinedTextField(
value = email,
onValueChange = { email = it },
isError = !emailValid && email.isNotEmpty(),
supportingText = {
if (!emailValid && email.isNotEmpty()) {
Text("Format email tidak valid")
}
}
)isError menandai field merah, dan supportingText menampilkan pesan. Untuk form kompleks, buat state validasi terpisah agar logika bisa diuji tanpa UI — pola yang akan kita pakai lagi di episode 14.
SnackbarHost dan SnackbarHostState menampilkan pesan singkat di bawah layar:
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) }
) { padding ->
Button(
onClick = {
scope.launch {
snackbarHostState.showSnackbar("Berhasil disimpan")
}
},
modifier = Modifier.padding(padding)
) { Text("Simpan") }
}snackbarHostState.showSnackbar(...) menampilkan pesan; panggilan bersifat suspend, jadi dijalankan dalam rememberCoroutineScope — konsep coroutine di Compose yang akan dibedah di episode 10.
Untuk konfirmasi, AlertDialog; untuk aksi kontekstual, ModalBottomSheet:
var tampilkanDialog by remember { mutableStateOf(false) }
if (tampilkanDialog) {
AlertDialog(
onDismissRequest = { tampilkanDialog = false },
title = { Text("Hapus item?") },
text = { Text("Tindakan ini tidak bisa dibatalkan.") },
confirmButton = {
Button(onClick = { tampilkanDialog = false }) { Text("Hapus") }
},
dismissButton = {
TextButton(onClick = { tampilkanDialog = false }) { Text("Batal") }
}
)
}AlertDialog muncul hanya saat tampilkanDialog bernilai true. Pola if (state) { Komponen } ini adalah idiom umum Compose untuk overlay — akan muncul lagi saat membahas animasi di episode 12.
Episode 7 melengkapi kalian untuk berinteraksi: TextField dengan keyboard options dan validasi, Checkbox dan Switch untuk pilihan biner, gesture dengan pointerInput, kontrol fokus dengan FocusRequester, serta umpan balik lewat Snackbar, AlertDialog, dan ModalBottomSheet.
Inti yang harus dibawa pulang:
Di episode 8 selanjutnya kita akan membahas navigation dan app architecture — Navigation Compose dan nav graph, passing arguments, deep links, arsitektur Single Activity, dan pola routing modular.