Composable Function

BasicTextField

Basic text composable that provides an interactive box that accepts text input through software or hardware keyboard, but provides no decorations like hint or placeholder.

BasicTextFieldCustomInputTransformationSample

@Composable
fun BasicTextFieldCustomInputTransformationSample() {
    // Demonstrates how to create a custom and relatively complex InputTransformation.
    val state = remember { TextFieldState() }
    BasicTextField(
        state,
        inputTransformation =
            InputTransformation {
                // A filter that always places newly-input text at the start of the string, after a
                // prompt character, like a shell.
                val promptChar = '>'
                fun CharSequence.countPrefix(char: Char): Int {
                    var i = 0
                    while (i < length && get(i) == char) i++
                    return i
                }
                // Step one: Figure out the insertion point.
                val newPromptChars = asCharSequence().countPrefix(promptChar)
                val insertionPoint = if (newPromptChars == 0) 0 else 1
                // Step two: Ensure text is placed at the insertion point.
                if (changes.changeCount == 1) {
                    val insertedRange = changes.getRange(0)
                    val replacedRange = changes.getOriginalRange(0)
                    if (!replacedRange.collapsed && insertedRange.collapsed) {
                        // Text was deleted, delete forwards from insertion point.
                        delete(insertionPoint, insertionPoint + replacedRange.length)
                    }
                }
                // Else text was replaced or there were multiple changes - don't handle.
                // Step three: Ensure the prompt character is there.
                if (newPromptChars == 0) {
                    insert(0, ">")
                }
                // Step four: Ensure the cursor is ready for the next input.
                placeCursorAfterCharAt(0)
            },
    )
}

BasicTextFieldDecoratorSample

@Composable
fun BasicTextFieldDecoratorSample() {
    // Demonstrates how to use the decorator API on BasicTextField
    val state = rememberTextFieldState("Hello, World!")
    BasicTextField(
        state = state,
        decorator = { innerTextField ->
            // Because the decorator is used, the whole Row gets the same behaviour as the internal
            // input field would have otherwise. For example, there is no need to add a
            // `Modifier.clickable` to the Row anymore to bring the text field into focus when user
            // taps on a larger text field area which includes paddings and the icon areas.
            Row(
                Modifier.background(Color.LightGray, RoundedCornerShape(percent = 30))
                    .padding(16.dp)
            ) {
                Icon(Icons.Default.MailOutline, contentDescription = "Mail Icon")
                Spacer(Modifier.width(16.dp))
                innerTextField()
            }
        },
    )
}

BasicTextFieldSample

@Composable
fun BasicTextFieldSample() {
    var value by
        rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue()) }
    BasicTextField(
        value = value,
        onValueChange = {
            // it is crucial that the update is fed back into BasicTextField in order to
            // see updates on the text
            value = it
        },
    )
}

BasicTextFieldWithStringSample

@Composable
fun BasicTextFieldWithStringSample() {
    var value by rememberSaveable { mutableStateOf("initial value") }
    BasicTextField(
        value = value,
        onValueChange = {
            // it is crucial that the update is fed back into BasicTextField in order to
            // see updates on the text
            value = it
        },
    )
}

BasicTextFieldWithValueOnValueChangeSample

@Composable
fun BasicTextFieldWithValueOnValueChangeSample() {
    var text by remember { mutableStateOf("") }
    // A reference implementation that demonstrates how to create a TextField with the legacy
    // state hoisting design around `BasicTextField(TextFieldState)`
    StringTextField(value = text, onValueChange = { text = it })
}

CreditCardSample

@Composable
fun CreditCardSample() {
    /** The offset translator used for credit card input field */
    val creditCardOffsetTranslator =
        object : OffsetMapping {
            override fun originalToTransformed(offset: Int): Int {
                return when {
                    offset < 4 -> offset
                    offset < 8 -> offset + 1
                    offset < 12 -> offset + 2
                    offset <= 16 -> offset + 3
                    else -> 19
                }
            }
            override fun transformedToOriginal(offset: Int): Int {
                return when {
                    offset <= 4 -> offset
                    offset <= 9 -> offset - 1
                    offset <= 14 -> offset - 2
                    offset <= 19 -> offset - 3
                    else -> 16
                }
            }
        }
    /**
     * Converts up to 16 digits to hyphen connected 4 digits string. For example, "1234567890123456"
     * will be shown as "1234-5678-9012-3456"
     */
    val creditCardTransformation = VisualTransformation { text ->
        val trimmedText = if (text.text.length > 16) text.text.substring(0..15) else text.text
        var transformedText = ""
        trimmedText.forEachIndexed { index, char ->
            transformedText += char
            if ((index + 1) % 4 == 0 && index != 15) transformedText += "-"
        }
        TransformedText(AnnotatedString(transformedText), creditCardOffsetTranslator)
    }
    var text by rememberSaveable { mutableStateOf("") }
    BasicTextField(
        value = text,
        onValueChange = { input ->
            if (input.length <= 16 && input.none { !it.isDigit() }) {
                text = input
            }
        },
        modifier = Modifier.size(170.dp, 30.dp).background(Color.LightGray).wrapContentSize(),
        singleLine = true,
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
        visualTransformation = creditCardTransformation,
    )
}

PlaceholderBasicTextFieldSample

@Composable
fun PlaceholderBasicTextFieldSample() {
    var value by rememberSaveable { mutableStateOf("initial value") }
    Box {
        BasicTextField(value = value, onValueChange = { value = it })
        if (value.isEmpty()) {
            Text(text = "Placeholder")
        }
    }
}

TextFieldWithIconSample

@Composable
fun TextFieldWithIconSample() {
    var value by rememberSaveable { mutableStateOf("initial value") }
    BasicTextField(
        value = value,
        onValueChange = { value = it },
        decorationBox = { innerTextField ->
            // Because the decorationBox is used, the whole Row gets the same behaviour as the
            // internal input field would have otherwise. For example, there is no need to add a
            // Modifier.clickable to the Row anymore to bring the text field into focus when user
            // taps on a larger text field area which includes paddings and the icon areas.
            Row(
                Modifier.background(Color.LightGray, RoundedCornerShape(percent = 30))
                    .padding(16.dp)
            ) {
                Icon(Icons.Default.MailOutline, contentDescription = null)
                Spacer(Modifier.width(16.dp))
                innerTextField()
            }
        },
    )
}