TextFieldState


The editable text state of a text field, including both the text itself and position of the cursor or selection.

To change the text field contents programmatically, call edit, setTextAndSelectAll, setTextAndPlaceCursorAtEnd, or clearText. Individual parts of the state like text, selection, or composition can be read from any snapshot restart scope like Composable functions. To observe these members from outside a restart scope, use snapshotFlow { textFieldState.text } or snapshotFlow { textFieldState.selection }.

When instantiating this class from a composable, use rememberTextFieldState to automatically save and restore the field state. For more advanced use cases, pass TextFieldState.Saver to rememberSaveable.

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Clear
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.sp

class SearchViewModel(val searchFieldState: TextFieldState = TextFieldState()) {
    private val queryValidationRegex = """\w+""".toRegex()

    // Use derived state to avoid recomposing every time the text changes, and only recompose
    // when the input becomes valid or invalid.
    val isQueryValid by derivedStateOf {
        // This lambda will be re-executed every time inputState.text changes.
        searchFieldState.text.matches(queryValidationRegex)
    }

    var searchResults: List<String> by mutableStateOf(emptyList())
        private set

    /** Called while the view model is active, e.g. from a LaunchedEffect. */
    suspend fun run() {
        snapshotFlow { searchFieldState.text }
            .collectLatest { queryText ->
                // Start a new search every time the user types something valid. If the previous
                // search is still being processed when the text is changed, it will be
                // cancelled
                // and this code will run again with the latest query text.
                if (isQueryValid) {
                    searchResults = performSearch(query = queryText)
                }
            }
    }

    fun clearQuery() {
        searchFieldState.setTextAndPlaceCursorAtEnd("")
    }

    private suspend fun performSearch(query: CharSequence): List<String> {
        TODO()
    }
}

@Composable
fun SearchScreen(viewModel: SearchViewModel) {
    Column {
        Row {
            BasicTextField(viewModel.searchFieldState)
            IconButton(onClick = { viewModel.clearQuery() }) {
                Icon(Icons.Default.Clear, contentDescription = "clear search query")
            }
        }
        if (!viewModel.isQueryValid) {
            Text("Invalid query", style = TextStyle(color = Color.Red))
        }
        LazyColumn { items(viewModel.searchResults) { TODO() } }
    }
}

Summary

Nested types

Saves and restores a TextFieldState for rememberSaveable.

Public constructors

TextFieldState(initialText: String, initialSelection: TextRange)
Cmn

Public functions

inline Unit
edit(block: TextFieldBuffer.() -> Unit)

Runs block with a mutable version of the current state.

Cmn
open String
Cmn

Public properties

TextRange?

The current composing range dictated by the IME.

Cmn
TextRange

The current selection range.

Cmn
CharSequence

The current text content.

Cmn
UndoState

Undo history controller for this TextFieldState.

Cmn

Extension functions

Unit

Deletes all the text in the state.

Cmn
Unit

Sets the text in this TextFieldState to text, replacing any text that was previously there, and places the cursor at the end of the new text.

Cmn
Unit

Sets the text in this TextFieldState to text, replacing any text that was previously there, and selects all the text.

Cmn
TextFieldBuffer

Creates a temporary, mutable TextFieldBuffer representing the current state of this TextFieldState.

Cmn

Public constructors

TextFieldState

TextFieldState(
    initialText: String = "",
    initialSelection: TextRange = TextRange(initialText.length)
)

Public functions

edit

inline fun edit(block: TextFieldBuffer.() -> Unit): Unit

Runs block with a mutable version of the current state. The block can make changes to the text and cursor/selection. See the documentation on TextFieldBuffer for a more detailed description of the available operations.

Make sure that you do not make concurrent calls to this function or call it again inside block's scope. Doing either of these actions will result in triggering an IllegalStateException.

import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.delete
import androidx.compose.foundation.text.input.insert
import androidx.compose.material.Text
import androidx.compose.ui.text.TextRange

val state = TextFieldState("hello world!")
state.edit {
    // Insert a comma after "hello".
    insert(5, ",") // = "hello, world!"

    // Delete the exclamation mark.
    delete(12, 13) // = "hello, world"

    // Add a different name.
    append("Compose") // = "hello, Compose"

    // Say goodbye.
    replace(0, 5, "goodbye") // "goodbye, Compose"

    // Select the new name so the user can change it by just starting to type.
    selection = TextRange(9, 16) // "goodbye, ̲C̲o̲m̲p̲o̲s̲e"
}

toString

open fun toString(): String

Public properties

composition

val compositionTextRange?

The current composing range dictated by the IME. If null, there is no composing region.

To observe changes to this property outside a restartable function, use snapshotFlow { composition }.

selection

val selectionTextRange

The current selection range. If the selection is collapsed, it represents cursor location. This value will automatically update when the user enters text or otherwise changes the text field selection range. To change it programmatically, call edit.

To observe changes to this property outside a restartable function, use snapshotFlow { selection }.

text

val textCharSequence

The current text content. This value will automatically update when the user enters text or otherwise changes the text field contents. To change it programmatically, call edit.

To observe changes to this property outside a restartable function, use snapshotFlow { text }.

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.unit.sp

class SearchViewModel {
    val searchFieldState = TextFieldState()
    var searchResults: List<String> by mutableStateOf(emptyList())
        private set

    /** Called while the view model is active, e.g. from a LaunchedEffect. */
    suspend fun run() {
        snapshotFlow { searchFieldState.text }
            // Let fast typers get multiple keystrokes in before kicking off a search.
            .debounce(500)
            // collectLatest cancels the previous search if it's still running when there's a
            // new change.
            .collectLatest { queryText -> searchResults = performSearch(query = queryText) }
    }

    private suspend fun performSearch(query: CharSequence): List<String> {
        TODO()
    }
}

@Composable
fun SearchScreen(viewModel: SearchViewModel) {
    Column {
        BasicTextField(viewModel.searchFieldState)
        LazyColumn { items(viewModel.searchResults) { TODO() } }
    }
}
See also
edit
snapshotFlow

undoState

@ExperimentalFoundationApi
val undoStateUndoState

Undo history controller for this TextFieldState.

import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material.Text
import androidx.compose.material.icons.filled.Clear
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

val state = rememberTextFieldState()

Column(Modifier.padding(8.dp)) {
    Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
        androidx.compose.material.Button(
            onClick = { state.undoState.undo() },
            enabled = state.undoState.canUndo
        ) {
            Text("Undo")
        }

        androidx.compose.material.Button(
            onClick = { state.undoState.redo() },
            enabled = state.undoState.canRedo
        ) {
            Text("Redo")
        }

        androidx.compose.material.Button(
            onClick = { state.undoState.clearHistory() },
            enabled = state.undoState.canUndo || state.undoState.canRedo
        ) {
            Text("Clear History")
        }
    }

    BasicTextField(
        state = state,
        modifier =
            Modifier.fillMaxWidth()
                .border(1.dp, Color.LightGray, RoundedCornerShape(6.dp))
                .padding(8.dp),
        textStyle = TextStyle(fontSize = 16.sp)
    )
}

Extension functions

fun TextFieldState.clearText(): Unit

Deletes all the text in the state.

To perform more complicated edits on the text, call TextFieldState.edit. This function is equivalent to calling:

edit {
delete(0, length)
placeCursorAtEnd()
}

setTextAndPlaceCursorAtEnd

fun TextFieldState.setTextAndPlaceCursorAtEnd(text: String): Unit

Sets the text in this TextFieldState to text, replacing any text that was previously there, and places the cursor at the end of the new text.

To perform more complicated edits on the text, call TextFieldState.edit. This function is equivalent to calling:

edit {
replace(0, length, text)
placeCursorAtEnd()
}

setTextAndSelectAll

fun TextFieldState.setTextAndSelectAll(text: String): Unit

Sets the text in this TextFieldState to text, replacing any text that was previously there, and selects all the text.

To perform more complicated edits on the text, call TextFieldState.edit. This function is equivalent to calling:

edit {
replace(0, length, text)
selectAll()
}

toTextFieldBuffer

fun TextFieldState.toTextFieldBuffer(): TextFieldBuffer

Creates a temporary, mutable TextFieldBuffer representing the current state of this TextFieldState.

Use a TextFieldBuffer to:

  • Apply transformations for testing purposes

  • Preview how the TextField would render with a specific OutputTransformation

This is similar to calling TextFieldState.edit, but without committing the changes back to the TextFieldState.

Important: A TextFieldBuffer is intended for short-term use. Let the garbage collecter dispose of it when you're finished to avoid unnecessary memory usage.

import androidx.compose.foundation.text.input.OutputTransformation
import androidx.compose.foundation.text.input.TextFieldBuffer
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.insert
import androidx.compose.foundation.text.input.toTextFieldBuffer
import androidx.compose.material.Text

val state = TextFieldState("Hello, World")
val outputTransformation = OutputTransformation { insert(0, "> ") }

val buffer = state.toTextFieldBuffer()
with(outputTransformation) { buffer.transformOutput() }

val transformedText = buffer.asCharSequence()
val transformedSelection = buffer.selection