Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to display error messages for multiple TextField in jetpack compose

How to display error messages for multiple TextField in jetpack compose. with only one field:

private var isError by mutableStateOf(false)

private fun validate(text: String){
    isError = if(text.isEmpty()){
        true
    }else{
        android.util.Patterns.EMAIL_ADDRESS.matcher(text).matches()
    }

    Log.i("Boolean",isError.toString())

}

    TextField(value = email,placeholder = { Text(text = "E-mail")},
            onValueChange = {
                email=it
                isError = false
            },
            shape = RoundedCornerShape(8.dp),
            colors = TextFieldDefaults.textFieldColors(
                    focusedIndicatorColor = Color.Transparent,
                    unfocusedIndicatorColor = Color.Transparent,
                    disabledIndicatorColor = Color.Transparent

            ),
            singleLine = true,
            isError = isError,
            keyboardActions = KeyboardActions { validate(email) },
            modifier=Modifier.align(Alignment.CenterHorizontally),
            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email),
            leadingIcon = { Icon(imageVector = Icons.Default.Email, contentDescription = null) })

I have a form with many TextField how do I validate one by one. for example if I have two fields with name and email. I thought about doing a loop with all the fields but I don't know if it's the best practice. Can someone help me

    var nome by rememberSaveable{ mutableStateOf("")}
    var email by rememberSaveable{ mutableStateOf("") }

       

 TextField(value = nome,placeholder = { Text(text = "Nome")},
                onValueChange = {
                    nome=it
                },
                shape = RoundedCornerShape(8.dp),
                colors = TextFieldDefaults.textFieldColors(
                        focusedIndicatorColor = Color.Transparent,
                        unfocusedIndicatorColor = Color.Transparent,
                        disabledIndicatorColor = Color.Transparent

                ),
                modifier=Modifier.align(Alignment.CenterHorizontally),
                leadingIcon = { Icon(imageVector = Icons.Default.Person, contentDescription = null) })

        Spacer(modifier = Modifier.padding(5.dp))


        TextField(value = email,placeholder = { Text(text = "E-mail")},
                onValueChange = {
                    email=it
                   
                },
                shape = RoundedCornerShape(8.dp),
                colors = TextFieldDefaults.textFieldColors(
                        focusedIndicatorColor = Color.Transparent,
                        unfocusedIndicatorColor = Color.Transparent,
                        disabledIndicatorColor = Color.Transparent

                ),
                modifier=Modifier.align(Alignment.CenterHorizontally),
                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email),
                leadingIcon = { Icon(imageVector = Icons.Default.Email, contentDescription = null) })

    Spacer(modifier = Modifier.padding(5.dp))


    Button(
            onClick = { verifyEmpty(strings=validate) },
            colors = ButtonDefaults.buttonColors(
                    contentColor = colorResource(id = R.color.marron),
                    backgroundColor = colorResource (id = R.color.pastel_green)
            ),
    ) {
        Text(text = stringResource(id = R.string.view_cad),
                color= colorResource(id = R.color.marron))
    }
like image 212
Rafael Souza Avatar asked Jan 31 '26 08:01

Rafael Souza


1 Answers

When you nave so much in common between two+ views, it's time to move that into a separate composable. You can specify all the differences in the parameters and not repeat same settings for each view.

I suggest you creating state class for your custom text field. I'll store text, error text and validator logic. So you can call validate when you need: on button click or on keyboard done button:

@Composable
fun TestView(
) {
    val nomeState = rememberErrorTextFieldState("", validate = { text ->
        when {
            text.isEmpty() -> {
                "text.isEmpty()"
            }
            else -> null
        }
    })
    val emailState = rememberErrorTextFieldState("", validate = { text ->
        when {
            text.isEmpty() -> {
                "text.isEmpty()"
            }
            !android.util.Patterns.EMAIL_ADDRESS.matcher(text).matches() -> {
                "pattern doesn't match"
            }
            else -> null
        }
    })

    Column {
        ErrorTextField(
            state = nomeState,
            placeholderText = "nome",
            leadingIconVector = Icons.Default.Person,
            modifier = Modifier.align(Alignment.CenterHorizontally),
        )
        ErrorTextField(
            state = emailState,
            placeholderText = "email",
            leadingIconVector = Icons.Default.Email,
            modifier = Modifier.align(Alignment.CenterHorizontally),
        )
        Button(
            onClick = {
                listOf(nomeState, emailState).forEach(ErrorTextFieldState::validate)
            },
        ) {
            Text(text = "stringResource(id = R.string.view_cad)")
        }
    }
}


@Composable
fun ErrorTextField(
    state: ErrorTextFieldState,
    placeholderText: String,
    leadingIconVector: ImageVector,
    modifier: Modifier,
) {
    Column {
        val error = state.error
        TextField(
            value = state.text,
            onValueChange = { state.updateText(it) },
            placeholder = { Text(text = placeholderText) },
            shape = RoundedCornerShape(8.dp),
            colors = TextFieldDefaults.textFieldColors(
                focusedIndicatorColor = Color.Transparent,
                unfocusedIndicatorColor = Color.Transparent,
                disabledIndicatorColor = Color.Transparent,
                errorCursorColor = Color.Red
            ),
            singleLine = true,
            isError = error != null,
            leadingIcon = { Icon(imageVector = leadingIconVector, contentDescription = null) },
            keyboardActions = KeyboardActions {
                state.validate()
            },
            modifier = modifier,
        )
        if (error != null) {
            Text(
                error,
                color = Color.Red,
            )
        }
    }
}

@Composable
fun rememberErrorTextFieldState(
    initialText: String,
    validate: (String) -> String? = { null },
): ErrorTextFieldState {
    return rememberSaveable(saver = ErrorTextFieldState.Saver(validate)) {
        ErrorTextFieldState(initialText, validate)
    }
}

class ErrorTextFieldState(
    initialText: String,
    private val validator: (String) -> String?,
) {
    var text by mutableStateOf(initialText)
        private set

    var error by mutableStateOf<String?>(null)
        private set

    fun updateText(newValue: String) {
        text = newValue
        error = null
    }

    fun validate() {
        error = validator(text)
    }

    companion object {
        fun Saver(
            validate: (String) -> String?,
        ) = androidx.compose.runtime.saveable.Saver<ErrorTextFieldState, String>(
            save = { it.text },
            restore = { ErrorTextFieldState(it, validate) }
        )
    }
}
like image 69
Philip Dukhov Avatar answered Feb 03 '26 06:02

Philip Dukhov