Ejemplo práctico 1: Estado observable

Objetivo

Entender el funcionamiento de un observable y las diferencias entre utilizar remember { mutableStateOf(false) } y remember { false }.

Desarrollo

La clase MainActivity es la típica configuración de una actividad con Jetpack Compose, donde se establece el tema y el Scaffold, desde donde se llama a la función composable RememberVsMutableStateDemo:

 1class MainActivity : ComponentActivity() {
 2    override fun onCreate(savedInstanceState: Bundle?) {
 3        super.onCreate(savedInstanceState)
 4        enableEdgeToEdge()
 5        setContent {
 6            ExampleT1_01Theme {
 7                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
 8                    RememberVsMutableStateDemo(Modifier.padding(innerPadding))
 9                }
10            }
11        }
12    }
13}

Observa el código del método RememberVsMutableStateDemo, en el se muestran dos bloques similares pero con comportamientos diferentes:

 1@Composable
 2fun RememberVsMutableStateDemo(modifier: Modifier = Modifier) {
 3    Surface(modifier = modifier.fillMaxSize()) {
 4        Column(
 5            modifier = Modifier
 6                .padding(16.dp)
 7                .fillMaxSize(),
 8            verticalArrangement = Arrangement.spacedBy(24.dp)
 9        ) {
10
11            // 1. Estado observable: cambia y la UI se actualiza.
12            Card {
13                Column(
14                    modifier = Modifier.padding(16.dp),
15                    verticalArrangement = Arrangement.spacedBy(12.dp)
16                ) {
17                    Text(
18                        text = "Estado observable con mutableStateOf",
19                        style = MaterialTheme.typography.titleMedium
20                    )
21                    var checked by remember { mutableStateOf(false) }
22                    Row(
23                        verticalAlignment = Alignment.CenterVertically,
24                        horizontalArrangement = Arrangement.spacedBy(12.dp)
25                    ) {
26                        Switch(
27                            checked = checked,
28                            onCheckedChange = { checked = it }
29                        )
30                        Text(
31                            if (checked) "Estado: ACTIVO (se recompone)" else "Estado: INACTIVO (se recompone)"
32                        )
33                    }
34                    Text(
35                        text = "Aquí usamos un State<Boolean>. Cambiar su valor notifica a Compose y la UI se recompone."
36                    )
37                }
38            }
39
40            // 2. Valor recordado NO observable: no hay recomposición al reasignar la variable local.
41            Card {
42                Column(
43                    modifier = Modifier.padding(16.dp),
44                    verticalArrangement = Arrangement.spacedBy(12.dp)
45                ) {
46                    Text(
47                        text = "Valor recordado sin estado observable",
48                        style = MaterialTheme.typography.titleMedium
49                    )
50
51                    // OJO: esto recuerda "false" una vez, pero NO es State<T>.
52                    var naive = remember { false }
53
54                    Row(
55                        verticalAlignment = Alignment.CenterVertically,
56                        horizontalArrangement = Arrangement.spacedBy(12.dp)
57                    ) {
58                        // La UI LEE 'naive' pero cambiarlo no provoca recomposición.
59                        Switch(
60                            checked = naive,
61                            onCheckedChange = { newValue ->
62                                // Esto cambia la variable local, pero NO notifica a Compose.
63                                naive = newValue
64                                println("Desde el Switch: $naive")
65                            }
66                        )
67                        Text(
68                            if (naive) "Valor leído: TRUE (no observable)" else "Valor leído: FALSE (no observable)"
69                        )
70                    }
71
72                    Button(
73                        onClick = {
74                            // También “cambia” la variable local, pero la UI no se enterará.
75                            naive = !naive
76                            println("Desde el Button: $naive")
77                        }
78                    ) {
79                        Text("Intentar alternar (no actualizará la UI)")
80                    }
81
82                    Text(
83                        text = "Este bloque usa un Boolean 'recordado' pero no observable. " +
84                                "Reasignarlo no dispara recomposición, por lo que la UI no refleja los cambios."
85                    )
86                }
87            }
88
89            // Nota pedagógica opcional
90            AssistChip(
91                onClick = {},
92                label = { Text("Consejo: si necesitas UI reactiva, usa State<T> (p. ej., mutableStateOf).") }
93            )
94        }
95    }
96}
  1. Estado observable con mutableStateOf:

    • La variable checked se define como var checked by remember { mutableStateOf(false) }.
    • Esto crea un estado observable. Cuando el usuario interactúa con el Switch y cambia su valor, la UI se recompone automáticamente para reflejar el nuevo estado.
    • El texto del Switch muestra “Estado: ACTIVO” o “Estado: INACTIVO” según el valor de checked, y este texto se actualiza dinámicamente.
    • Cada vez que checked cambia, Compose detecta el cambio y vuelve a ejecutar la función composable, actualizando la interfaz de usuario.
  2. Valor recordado sin estado observable:

    • La variable naive se define como var naive = remember { false }.
    • Esto recuerda el valor “false” una vez, pero no es un estado observable. Cambiar naive no provoca recomposición.
    • Cuando el usuario interactúa con el Switch o el Button, la variable naive cambia internamente, pero la UI no se actualiza para reflejar estos cambios.
    • El texto del Switch muestra “Valor leído: FALSE” o “Valor leído: TRUE” según el valor de naive, pero este texto no se actualizará nunca.

Conclusión

Este ejemplo ilustra claramente la diferencia entre usar un estado observable (mutableStateOf) y un valor recordado no observable (remember { false }). Para que la UI sea reactiva y se actualice automáticamente cuando los datos cambian, es esencial utilizar estados observables en Jetpack Compose.