Back to Blog

State & Input in Kotlin Desktop: Compose Multiplatform | Lesson 03

Sandy LaneSandy Lane

Video: State & Input in Kotlin Desktop: Compose Multiplatform | Lesson 03 by Taught by Celeste AI - AI Coding Coach

Watch full page →

State & Input in Kotlin Desktop: Compose Multiplatform | Lesson 03

In this lesson, we learn how to manage user input and state in a Compose Desktop application using Kotlin. We build a settings form with various input controls like OutlinedTextField, Checkbox, Switch, and RadioButton, demonstrating state hoisting and immutable state updates with data class copy().

Code

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

// Data class representing the form state
data class SettingsState(
  val username: String = "",
  val notificationsEnabled: Boolean = false,
  val darkMode: Boolean = false,
  val favoriteColor: String = "Red"
)

@Composable
fun SettingsForm(
  state: SettingsState,
  onStateChange: (SettingsState) -> Unit
) {
  Column(modifier = Modifier.padding(16.dp)) {
    // OutlinedTextField with two-way binding
    OutlinedTextField(
      value = state.username,
      onValueChange = { newValue -> onStateChange(state.copy(username = newValue)) },
      label = { Text("Username") },
      modifier = Modifier.fillMaxWidth()
    )

    Spacer(Modifier.height(16.dp))

    // Checkbox with checked/onCheckedChange
    Row(verticalAlignment = Alignment.CenterVertically) {
      Checkbox(
        checked = state.notificationsEnabled,
        onCheckedChange = { checked -> onStateChange(state.copy(notificationsEnabled = checked)) }
      )
      Spacer(Modifier.width(8.dp))
      Text("Enable notifications")
    }

    Spacer(Modifier.height(16.dp))

    // Switch with checked/onCheckedChange
    Row(verticalAlignment = Alignment.CenterVertically) {
      Switch(
        checked = state.darkMode,
        onCheckedChange = { checked -> onStateChange(state.copy(darkMode = checked)) }
      )
      Spacer(Modifier.width(8.dp))
      Text("Dark Mode")
    }

    Spacer(Modifier.height(16.dp))

    // RadioButtons for favorite color selection
    val colors = listOf("Red", "Green", "Blue")
    Text("Favorite Color:")
    colors.forEach { color ->
      Row(verticalAlignment = Alignment.CenterVertically) {
        RadioButton(
          selected = (state.favoriteColor == color),
          onClick = { onStateChange(state.copy(favoriteColor = color)) }
        )
        Spacer(Modifier.width(8.dp))
        Text(color)
      }
    }
  }
}

@Composable
fun SettingsScreen() {
  // Hoisted state owned by parent
  var settingsState by remember { mutableStateOf(SettingsState()) }

  SettingsForm(
    state = settingsState,
    onStateChange = { newState -> settingsState = newState }
  )
}

Key Points

  • Use OutlinedTextField, Checkbox, Switch, and RadioButton with value/checked and onValueChange/onCheckedChange/onClick callbacks for two-way binding.
  • State hoisting means the parent component owns the state and passes it down with callbacks; child components remain stateless.
  • Use remember { mutableStateOf() } to create local state inside a composable.
  • Update immutable state using data class copy() to create modified copies rather than mutating state directly.
  • Iterate over options with forEach to create groups of RadioButtons sharing the same state and onClick handler.