💻 Exemplos: Capítulo 08: Formulários e Validação de Inputs no Compose
Exemplos de Código: Capítulo 08 — Formulários e Validação no Compose
Especialização em Mobile Nativo com Android • Demonstrações Práticas Compiláveis
💻 Código de Demonstração: Formulário de Autenticação com Validação Reativa e FocusManager
O código abaixo demonstra o desenvolvimento de formulários profissionais no Jetpack Compose:
- Validação reativa de campos de texto (E-mail corporativo e Senha com entropia mínima).
- Alternância de visibilidade de senha com
PasswordVisualTransformation. - Navegação fluida de foco entre campos através de
FocusRequestereImeAction.Next / ImeAction.Done. - Exibição de mensagens de erro acessíveis via
supportingTextdo Material 3.
package br.com.portal.mobile.android.cap08
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Email
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
// =====================================================================
// 1. Regras Puras de Validação de Domínio
// =====================================================================
object ValidadorFormulario {
fun validarEmail(email: String): String? {
if (email.isBlank()) return "E-mail não pode ser vazio."
val padrao = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[a-z]{2,}$".toRegex()
return if (padrao.matches(email)) null else "Formato de e-mail inválido."
}
fun validarSenha(senha: String): String? {
if (senha.length < 8) return "Senha deve ter no mínimo 8 caracteres."
if (!senha.any { it.isDigit() }) return "Senha deve conter pelo menos um dígito."
if (!senha.any { it.isUpperCase() }) return "Senha deve conter pelo menos uma letra maiúscula."
return null
}
}
// =====================================================================
// 2. Formulário Interativo
// =====================================================================
@Composable
fun FormularioLoginScreen(
onLoginSucesso: (String, String) -> Unit,
modifier: Modifier = Modifier
) {
var email by rememberSaveable { mutableStateOf("") }
var senha by rememberSaveable { mutableStateOf("") }
var senhaVisivel by rememberSaveable { mutableStateOf(false) }
// Estados de Erro
var erroEmail by remember { mutableStateOf<String?>(null) }
var erroSenha by remember { mutableStateOf<String?>(null) }
// Gestão de Foco
val focusManager = LocalFocusManager.current
val senhaFocusRequester = remember { FocusRequester() }
Surface(
modifier = modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(24.dp)
) {
Text(
text = "Acesso Corporativo",
style = MaterialTheme.typography.headlineLarge,
color = MaterialTheme.colorScheme.primary
)
Text(
text = "Insira suas credenciais seguras para autenticar.",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.height(32.dp))
// Campo de E-mail
OutlinedTextField(
value = email,
onValueChange = {
email = it
erroEmail = ValidadorFormulario.validarEmail(it)
},
label = { Text("E-mail Profissional") },
placeholder = { Text("usuario@portal.com") },
leadingIcon = { Icon(Icons.Default.Email, contentDescription = null) },
isError = erroEmail != null,
supportingText = { erroEmail?.let { Text(it) } },
singleLine = true,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email,
imeAction = ImeAction.Next
),
keyboardActions = KeyboardActions(
onNext = { senhaFocusRequester.requestFocus() }
),
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
// Campo de Senha com Visibilidade Alternável
OutlinedTextField(
value = senha,
onValueChange = {
senha = it
erroSenha = ValidadorFormulario.validarSenha(it)
},
label = { Text("Senha") },
leadingIcon = { Icon(Icons.Default.Lock, contentDescription = null) },
trailingIcon = {
IconButton(onClick = { senhaVisivel = !senhaVisivel }) {
Icon(
imageVector = if (senhaVisivel) Icons.Default.VisibilityOff else Icons.Default.Visibility,
contentDescription = if (senhaVisivel) "Ocultar senha" else "Mostrar senha"
)
}
},
visualTransformation = if (senhaVisivel) VisualTransformation.None else PasswordVisualTransformation(),
isError = erroSenha != null,
supportingText = { erroSenha?.let { Text(it) } },
singleLine = true,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = { focusManager.clearFocus() }
),
modifier = Modifier
.fillMaxWidth()
.focusRequester(senhaFocusRequester)
)
Spacer(modifier = Modifier.height(32.dp))
// Botão de Submissão Condicional
val formularioValido = email.isNotBlank() && senha.isNotBlank() && erroEmail == null && erroSenha == null
Button(
onClick = {
focusManager.clearFocus()
onLoginSucesso(email, senha)
},
enabled = formularioValido,
shape = RoundedCornerShape(12.dp),
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
) {
Text(text = "Entrar no Sistema")
}
}
}
}
@Preview(showBackground = true)
@Composable
fun FormularioPreview() {
MaterialTheme {
FormularioLoginScreen(onLoginSucesso = { _, _ -> })
}
}