Interview QuestionPractical QuestionFollow-up Questions

State-Based vs Value-Based Text Fields in Jetpack Compose

skydovesJaewoong Eum (skydoves)||10 min read

State-Based vs Value-Based Text Fields in Jetpack Compose

Jetpack Compose offers two approaches to text input: value-based text fields and state-based text fields. They differ in how they manage input state, handle text transformations, configure line limits, and support secure input. The official documentation recommends state-based text fields because they encapsulate the full lifecycle of text input state and simplify handling of transformations and configuration. By the end of this lesson, you will be able to:

  • Explain how value-based text fields manage state through onValueChange callbacks.
  • Explain how state-based text fields use TextFieldState to encapsulate all input state.
  • Describe the difference between VisualTransformation and the separated InputTransformation/OutputTransformation model.
  • Identify how TextFieldLineLimits simplifies line configuration over separate singleLine, maxLines, and minLines parameters.
  • Describe the built-in SecureTextField composable for secure input scenarios.

Value-Based State Management

Value-based text fields manage state through an external mechanism. The developer provides the current text value and an onValueChange callback. When the user types, the callback fires with the new value, and the developer must update state manually, typically through mutableStateOf or a ViewModel.

@Composable
fun ValueBasedInput() {
    var text by remember { mutableStateOf("") }
    TextField(
        value = text,
        onValueChange = { text = it },
        label = { Text("Enter text") }
    )
}

This manual synchronization introduces complexity. The developer is responsible for ensuring consistency across recompositions and configuration changes. Asynchronous state updates can cause the displayed text to fall out of sync with the backing state if updates are not applied in the correct order. If the onValueChange callback triggers a ViewModel update that emits a new state with a different text value, the text field may flicker or lose cursor position.

State-Based State Management

State-based text fields use a TextFieldState object that encapsulates the full text input state: content, cursor position, selection range, and composition. The composable owns this state internally and updates it in response to user input without requiring the developer to wire up callbacks.

@Composable
fun StateBasedInput() {
    val textFieldState = rememberTextFieldState()
    BasicTextField(
        state = textFieldState,
        decorator = { innerTextField ->
            Box(Modifier.padding(16.dp)) {
                innerTextField()
            }
        }
    )
}

TextFieldState persists across recomposition and configuration changes. It can survive process death when combined with rememberSaveable. This eliminates the class of bugs caused by manual state synchronization and simplifies the developer's responsibility to just providing the state object. The developer reads the current text through textFieldState.text and never needs to set it through a callback.

Text Transformation Handling

Value-based text fields use VisualTransformation, which combines input filtering and output formatting into a single component. Developers must manually handle offset mappings between the raw text and the displayed text. For example, a phone number formatter that inserts dashes must provide an OffsetMapping that translates cursor positions between the formatted and unformatted text. This becomes complex when the transformation logic changes frequently or involves non-trivial mappings.

This interview continues for subscribers

Subscribe to Dove Letter for full access to exclusive interviews about Android and Kotlin development.

Become a Sponsor