Ejemplo práctico 10: Login en un cuadro de diálgo

Objetivo

Este ejemplo permite crear un AlertDialog personalizado para solicitar usuario y contraseña.

Recursos en string.xml

Como en la versión anterior, se tratará de evitar lo máximo posible el hardcoded text, aunque en ocasiones, verás que se omite por razones didácticas.

 1<resources>
 2    <string name="app_name">ExampleT2_10</string>
 3
 4    <string name="txt_title">Login</string>
 5    <string name="txt_user">Usuario</string>
 6    <string name="txt_password">Contraseña</string>
 7
 8    <string name="txt_login_error">Credenciales incorrectas</string>
 9    <string name="txt_login_ok">Credenciales correctas</string>
10</resources>

Compose LoginDialog

Se creará el siguiente compose para mostrar el cuadro de diálogo personalizado, en esta ocasión se utiliza AlertDialog ya que la personalización es mínima y simplifica el código, pero para los cuadros de diálogo personalizados se recomienda el uso de Dialog.

 1@Composable
 2fun LoginDialog(onLogin: (String, String) -> Unit = { _, _ -> }) {
 3    val ctxt = LocalContext.current
 4    val openDialog = remember { mutableStateOf(false) }
 5    var user by remember { mutableStateOf("") }
 6    var pass by remember { mutableStateOf("") }
 7
 8    Box(
 9        modifier = Modifier.fillMaxSize(),
10        contentAlignment = Alignment.Center
11    ) {
12        Button(onClick = { openDialog.value = true }) {
13            Text(text = ctxt.getString(R.string.txt_title))
14        }
15
16        if (openDialog.value) {
17            AlertDialog(
18                onDismissRequest = { openDialog.value = true }, // Se mantiene el diálogo abierto.
19                title = { Text(text = ctxt.getString(R.string.txt_title)) },
20                text = {
21                    Column {
22                        OutlinedTextField(
23                            value = user,
24                            onValueChange = { user = it },
25                            singleLine = true,
26                            label = { Text(ctxt.getString(R.string.txt_user)) }
27                        )
28                        OutlinedTextField(
29                            value = pass,
30                            onValueChange = { pass = it },
31                            singleLine = true,
32                            label = { Text(ctxt.getString(R.string.txt_password)) },
33                            visualTransformation = PasswordVisualTransformation()
34                        )
35                    }
36                },
37                confirmButton = {
38                    TextButton(onClick = {
39                        if (user.isNotBlank() && pass.isNotBlank()) {
40                            onLogin(user, pass)
41                            openDialog.value = false
42                            // Limpiar los campos después del inicio de sesión.
43                            user = ""
44                            pass = ""
45                        }
46                    }) {
47                        Text(ctxt.getString(android.R.string.ok))
48                    }
49                },
50                dismissButton = {
51                    TextButton(onClick = {
52                        openDialog.value = false
53                        user = ""
54                        pass = ""
55                    }) {
56                        Text(ctxt.getString(android.R.string.cancel))
57                    }
58                }
59            )
60        }
61    }
62}

Observa el uso de visualTransformation en el OutlinedTextField, este permite la ocultación del password.

Compose MainScreen

Siguiendo con la reutilización y actualización del estado, se creará el siguiente composable para mostrar el botón de login o el mensaje de login correcto.

 1@Composable
 2fun MainScreen() {
 3    var isLoggedIn by remember { mutableStateOf(false) }
 4    val ctxt = LocalContext.current
 5
 6    if (!isLoggedIn) {
 7        // Se muestra el diálogo de inicio de sesión
 8        LoginDialog(
 9            onLogin = { user, pass ->
10                // Aquí se maneja la lógica de inicio de sesión
11                // Por ejemplo, verificar las credenciales
12                if (user == "admin" && pass == "1234") {
13                    isLoggedIn = true // Simulación de inicio de sesión exitoso
14                    println("Inicio de sesión correcto. Usuario: $user, Contraseña: $pass")
15                } else {
16                    // Se muestra un mensaje de error o manejar el fallo de inicio de sesión
17                    Toast.makeText(
18                        ctxt,
19                        ctxt.getString(R.string.txt_login_error),
20                        Toast.LENGTH_SHORT
21                    ).show()
22                }
23            }
24        )
25    } else {
26        // Contenido principal de la aplicación
27        Text(text = ctxt.getString(R.string.txt_login_ok))
28    }
29}

Resultado de la MainActivity

Ahora, la actividad principal tendrá el siguiente aspecto.

 1class MainActivity : ComponentActivity() {
 2    @OptIn(ExperimentalMaterial3Api::class)
 3    override fun onCreate(savedInstanceState: Bundle?) {
 4        super.onCreate(savedInstanceState)
 5        enableEdgeToEdge()
 6        setContent {
 7            ExampleT2_10Theme {
 8                Scaffold(
 9                    topBar = { TopAppBar(title = { Text(getString(R.string.app_name)) }) }
10                ) { innerPadding ->
11                    Box(
12                        modifier = Modifier
13                            .fillMaxSize()
14                            .padding(innerPadding),
15                        contentAlignment = Alignment.Center
16                    ) {
17                        MainScreen()
18                    }
19                }
20            }
21        }
22    }
23}