State-Based vs Value-Based Text Fields in Jetpack Compose
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
onValueChangecallbacks. - Explain how state-based text fields use
TextFieldStateto encapsulate all input state. - Describe the difference between
VisualTransformationand the separatedInputTransformation/OutputTransformationmodel. - Identify how
TextFieldLineLimitssimplifies line configuration over separatesingleLine,maxLines, andminLinesparameters. - Describe the built-in
SecureTextFieldcomposable 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