0

like this

I tried isError and it showed boarder only I am expecting it to show bottom red text too

1 Answers1

0

You have to create your own bottom text e manipulate the behavior, i have an implementation i use in my projects, as follows:

The base component:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MyOutlinedTextField(
    modifier: Modifier = Modifier,
    value: String,
    label: String,
    onValueChange: (String) -> Unit,
    singleLine: Boolean = false,
    maxLines: Int = 1,
    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
    keyboardActions: KeyboardActions = KeyboardActions.Default,
    leadingIcon: @Composable (() -> Unit)? = {
        IconButton(onClick = { onValueChange("") }) {
            Icon(imageVector = Icons.Rounded.Cancel, contentDescription = "Clear Text")
        }
    },
    trailingIcon: @Composable (() -> Unit)? = null,
    visualTransformation: VisualTransformation = VisualTransformation.None,
    colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors(),
    isError: Boolean = false
) {

    OutlinedTextField(
        value = value,
        label = {
            Text(text = label)
        },
        singleLine = singleLine,
        maxLines = if (singleLine) 1 else maxLines,
        keyboardOptions = keyboardOptions,
        keyboardActions = keyboardActions,
        onValueChange = onValueChange,
        modifier = modifier,
        leadingIcon = if (value.isNotEmpty()) leadingIcon else null,
        trailingIcon = trailingIcon,
        visualTransformation = visualTransformation,
        colors = colors,
        isError = isError,
    )
}

Then another component that uses the base one, it display a text message bellow the textfield when errorMessage.isNotEmpty().

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ValidationOutlinedTextField(
    modifier: Modifier = Modifier,
    value: String,
    label: String,
    onValueChange: (String) -> Unit,
    singleLine: Boolean = false,
    colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors(),
    errorColors: TextFieldColors? = TextFieldDefaults.outlinedTextFieldColors(),
    maxLines: Int = 1,
    maxColumns: Int = -1,
    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
    keyboardActions: KeyboardActions = KeyboardActions.Default,
    trailingIcon: @Composable (() -> Unit)? = null,
    visualTransformation: VisualTransformation = VisualTransformation.None,
    errorMessage: String = "",
    errorTrailingIcon: @Composable (() -> Unit)? = {
        Icon(Icons.Default.Error, errorMessage)
    },
    successTrailingIcon: @Composable (() -> Unit)? = {
        Icon(Icons.Default.CheckCircle, "Success", tint = Color.Green)
    },
    loadingTrailingIcon: @Composable (() -> Unit)? = {
        CircularProgressIndicator(Modifier.wrapContentSize().scale(0.5f))
    },
    successColors: TextFieldColors? = TextFieldDefaults.outlinedTextFieldColors(
        cursorColor = Color.Green,
        focusedLabelColor = Color.Green,
        unfocusedLabelColor = Color.Green,
        focusedBorderColor = Color.Green,
        unfocusedBorderColor = Color.Green,
    ),
    loadingColors: TextFieldColors? = TextFieldDefaults.outlinedTextFieldColors(),
    isError: Boolean = errorMessage.isNotEmpty(),
    isSuccess: Boolean = false,
    isLoading: Boolean = false
) {
    Column(modifier = modifier) {
        MyOutlinedTextField(
            modifier = Modifier
                .fillMaxWidth()
                .wrapContentHeight(),
            value = value,
            label = label,
            onValueChange = {
                if (maxColumns < 0 || it.length <= maxColumns)
                    onValueChange(it)
            },
            singleLine = singleLine,
            maxLines = maxLines,
            keyboardOptions = keyboardOptions,
            keyboardActions = keyboardActions,
            trailingIcon = {
                if (errorMessage.isNotEmpty())
                    errorTrailingIcon?.invoke() ?: trailingIcon?.invoke()
                else if (isSuccess)
                    successTrailingIcon?.invoke() ?: trailingIcon?.invoke()
                else if (isLoading)
                    loadingTrailingIcon?.invoke() ?: trailingIcon?.invoke()
                else
                    trailingIcon?.invoke()
            },
            visualTransformation = visualTransformation,
            colors =
            if (errorMessage.isNotEmpty())
                errorColors ?: colors
            else if (isSuccess)
                successColors ?: colors
            else if (isLoading)
                loadingColors ?: colors
            else
                colors,
            isError = isError
        )
        Row {
            AnimatedVisibility(
                modifier = Modifier
                    .weight(9f)
                    .wrapContentHeight(),
                visible = errorMessage.isNotEmpty()
            ) {
                Text(
                    textAlign = TextAlign.Start,
                    text = errorMessage,
                    style = MaterialTheme.typography.labelSmall,
                )
            }
            AnimatedVisibility(
                modifier = Modifier
                    .weight(1f)
                    .wrapContentHeight(),
                visible = maxColumns >= 0
            ) {
                Text(
                    textAlign = TextAlign.End,
                    text = "${value.length}/$maxColumns",
                    style = MaterialTheme.typography.labelSmall,
                )
            }

        }
    }
}

There are some other features, but these should suffice your need.

if you want to check how its used and some other components, you can see in this project: https://github.com/ygorluizfrazao/JustAnotherNotesApp

Hope it helps.

Ygor Frazão
  • 118
  • 1
  • 7