Learn Jetpack Compose - Handling Input & Events
Episode 7 of 23

Learn Jetpack Compose - Handling Input & Events

This episode masters user interaction: TextField, Button, Checkbox, and Switch, gesture detection with pointer input, focus and keyboard management, input validation, and Snackbar, dialogs, and bottom sheets.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

An application without interaction is just a digital poster. Episode 7 takes you from static to interactive: capturing user input, detecting gestures, managing the keyboard and focus, and giving feedback through Snackbar, dialogs, and bottom sheets.

The pattern used here is consistent with episode 5: state goes up, events go down. Every control receives a value and a callback, and logic stays in the hoisted state or the ViewModel.

Episode 7 covers input controls, gestures and pointer input, focus and keyboard, validation, and feedback components.

Input Controls: TextField, Button, Checkbox, Switch

The TextField Family

OutlinedTextField and TextField are Material 3 text inputs. Both accept value and onValueChange — the state hoisting pattern you already know:

KotlinTextField dengan keyboard options
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 configures the keyboard type (KeyboardType.Email) and IME action (ImeAction.Done), while KeyboardActions catches when the Done button is pressed — a pattern that makes forms feel responsive.

Checkbox and Switch

For binary choices, Checkbox and Switch use a Boolean state:

KotlinCheckbox dan Switch
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")
}

Gesture and Pointer Input

clickable and Pointer Modifiers

Most interactions are covered by the clickable, toggleable, or selectable modifiers. For richer gestures, use pointerInput with detectors:

KotlinDeteksi tap dan long press
Modifier
    .pointerInput(Unit) {
        detectTapGestures(
            onTap = { onTampilkanToast() },
            onLongPress = { onBukaMenu() }
        )
    }
    .padding(16.dp)

detectTapGestures distinguishes onTap from onLongPress. For drag and pinch, Compose provides detectDragGestures and detectTransformGestures — the foundation for components such as a swipeable carousel. When testing without touching the screen, adb shell input tap 540 1200 sends a tap at specific coordinates from the terminal.

Focus, Keyboard, and Validation

Managing Focus

Focus moves when the user presses the IME button. FocusRequester gives programmatic control:

KotlinFocusRequester
val passwordFocus = FocusRequester()
OutlinedTextField(
    value = password,
    onValueChange = { password = it },
    modifier = Modifier.focusRequester(passwordFocus)
)
 
LaunchedEffect(Unit) { passwordFocus.requestFocus() }

Modifier.focusRequester(passwordFocus) registers the requester, and passwordFocus.requestFocus() moves the focus when composition starts — useful for showing the keyboard right away when a form screen opens.

Input Validation

Validation produces an error message shown via isError and supportingText:

KotlinValidasi sederhana
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 marks the field red, and supportingText shows the message. For complex forms, create a separate validation state so the logic can be tested without the UI — a pattern we will use again in episode 14.

Snackbar, Dialog, and Bottom Sheet

Snackbar for Feedback

SnackbarHost and SnackbarHostState show short messages at the bottom of the screen:

KotlinSnackbar
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(...) shows the message; the call is suspend, so it runs inside rememberCoroutineScope — a coroutine concept in Compose that will be dissected in episode 10.

Dialog and Bottom Sheet

For confirmation, use AlertDialog; for contextual actions, use ModalBottomSheet:

KotlinAlertDialog
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 appears only when tampilkanDialog is true. This if (state) { Component } pattern is a common Compose idiom for overlays — it will come up again when discussing animations in episode 12.

Closing

Episode 7 equips you for interaction: TextField with keyboard options and validation, Checkbox and Switch for binary choices, gestures with pointerInput, focus control with FocusRequester, and feedback through Snackbar, AlertDialog, and ModalBottomSheet.

Key takeaways:

  • Every control follows the value-and-callback contract.
  • KeyboardOptions and ImeAction configure keyboard behavior.
  • FocusRequester provides programmatic focus control.
  • isError and supportingText display validation right on the field.
  • SnackbarHostState works with a coroutine scope.
  • Overlays like dialogs are controlled with a Boolean state.

In episode 8 we will discuss navigation and app architecture — Navigation Compose and the nav graph, passing arguments, deep links, Single Activity architecture, and modular routing patterns.

Learn Jetpack Compose - Handling Input & Events | Learn Jetpack Compose