Efectos y ciclo de vida: DisposableEffect
Al terminar: Registrar y deshacer un efecto atado al ciclo de vida de la pantalla con DisposableEffect, e instalar lifecycle-runtime-compose.
Al terminar esta lección habrás escrito tú un efecto que registra algo y lo quita al salir (DisposableEffect). Reconocerás el error de compilación que da olvidar el onDispose. Y lo verás funcionar en Marcador: un contador de las veces que la pantalla pasa a segundo plano.
Importa porque un observador (un objeto al que te apuntas para que Android le avise cada vez que pasa algo) que se registra y no se quita sigue vivo aunque la pantalla ya no exista (una fuga), y no da ningún error a la vista. Tema: DisposableEffect. Instalas una librería, lifecycle-runtime-compose, en el primer apartado del paso (es la que da acceso al ciclo de vida desde Compose).
DisposableEffect: registrar y deshacer
DisposableEffect(key) { … } sirve cuando lo que empiezas hay que deshacerlo: registras algo al entrar y lo quitas en onDispose { … }, que se ejecuta cuando la clave cambia o cuando el composable sale de la composición. Sin quitarlo, el objeto registrado seguiría vivo (una fuga).
Los dos ingredientes que aparecen en el ejemplo (Log.d escribe en Logcat, la ventana de registros de Android Studio, como en El ciclo de vida de la Activity):
LifecycleEventObserver: un objeto que recibe cada evento del ciclo de vida (ON_START,ON_STOP…) y se engancha conlifecycle.addObserver(…). Es una interfaz (un contrato de métodos, como las de Herencia, interfaces y composición) con un solo método, y por eso Kotlin permite construirla escribiendo solo una lambda:LifecycleEventObserver { _, evento -> … }. La lambda recibe dos parámetros (el propietario y el evento) y el guion bajo_significa «no uso este».LocalLifecycleOwner.current: otroCompositionLocal, como el que usaMaterialTheme(Compose: Scaffold, slots y tema);.currentlo lee. Este da el propietario del ciclo de vida (la Activity). Vive enlifecycle-runtime-compose, la librería que instalas en el paso de abajo: en el código llevaimport androidx.lifecycle.compose.LocalLifecycleOwner.
@Composable
fun EscuchaElCiclo() {
val propietario = LocalLifecycleOwner.current // la Activity que aloja esta composición
DisposableEffect(propietario) { // clave: el propietario
val observador = LifecycleEventObserver { _, evento -> // _: el propietario, que no se usa aquí
Log.d("Ciclo", "evento: $evento") // ON_CREATE, ON_START, ON_RESUME… (se ve en Logcat)
if (evento == Lifecycle.Event.ON_START) Log.d("Ciclo", "ya se ve") // reaccionar a UN evento concreto
}
propietario.lifecycle.addObserver(observador) // al entrar: registrar
onDispose { propietario.lifecycle.removeObserver(observador) } // al salir: quitar (siempre la última línea)
}
}Lifecycle.Event es la lista cerrada de eventos posibles (un enum, como en Enum y when exhaustivo), escrita dentro de Lifecycle; por eso se nombra Lifecycle.Event.ON_START.
La clave es el propietario. Casi nunca cambia (la Activity es la misma mientras la pantalla vive); se pone como clave por seguridad: si cambiara, Compose ejecutaría onDispose para desenganchar el observador del viejo y volvería a ejecutar el bloque para engancharlo al nuevo.
El ObservadorDelCiclo de Marcador es este mismo ejemplo con dos añadidos: cuando el evento es ON_STOP avisa con una lambda (alPararse) y escribe cada paso en Logcat. Esa lambda pasa por rememberUpdatedState, por la razón de Efectos en Compose: LaunchedEffect: el observador vive mucho tiempo y no debe quedarse con una lambda vieja. Lo escribirás tú en el paso de más abajo.
Antes, el mismo mecanismo con algo que se ve en pantalla, en un edificio inventado. Un objeto Timbre, de fuera de Compose, guarda una lista de quién quiere enterarse cuando suena. El portero se apunta al llegar y se borra al irse:
object Timbre { // algo de fuera de Compose
val oyentes = mutableListOf<() -> Unit>() // quién quiere enterarse
fun sonar() = oyentes.forEach { it() } // avisa a todos
}
@Composable
fun Recepcion() {
var hayPortero by remember { mutableStateOf(true) }
Column {
Row(verticalAlignment = Alignment.CenterVertically) {
Switch(checked = hayPortero, onCheckedChange = { hayPortero = it })
Text("Portero en la puerta")
}
Button(onClick = { Timbre.sonar() }) { Text("Llamar al timbre") }
if (hayPortero) Portero() // apagado: Portero sale de la composición
}
}
@Composable
fun Portero() {
var visitas by remember { mutableIntStateOf(0) }
DisposableEffect(Unit) { // Unit: se registra una sola vez
val oyente: () -> Unit = { visitas++ } // lo que hace al oír el timbre
Timbre.oyentes.add(oyente) // al entrar: apuntarse
onDispose { Timbre.oyentes.remove(oyente) } // al salir: borrarse (siempre la última línea)
}
Text("Visitas: $visitas")
}Lo que ves en pantalla: el interruptor encendido, el botón y Visitas: 0. Tras dos llamadas al timbre, Visitas: 2.
Si apagas el interruptor, llamas tres veces al timbre y lo vuelves a encender, ves Visitas: 0, no 5. Al apagar, Portero salió de la composición, onDispose lo borró del timbre y nadie contó esas tres llamadas; al encender entra un Portero nuevo, con su remember en blanco. Sin el onDispose, el portero viejo seguiría apuntado al timbre, contando visitas para una pantalla que ya no existe: eso es la fuga. El ejercicio de abajo te deja comprobarlo contando los oyentes.
Error típico: acabar el bloque con otra cosa
DisposableEffect(propietario) {
val observador = LifecycleEventObserver { _, evento -> Log.d("Efectos", "evento: $evento") }
propietario.lifecycle.addObserver(observador)
onDispose { propietario.lifecycle.removeObserver(observador) }
Log.d("Efectos", "observador registrado") // ← la última línea ya no es onDispose
}DisposableEffect(propietario) {
val observador = LifecycleEventObserver { _, evento -> Log.d("Efectos", "evento: $evento") }
propietario.lifecycle.addObserver(observador)
Log.d("Efectos", "observador registrado")
onDispose { propietario.lifecycle.removeObserver(observador) }
}Si la última línea del bloque no es onDispose, el compilador falla, y el mensaje habla de tipos, no de «falta onDispose»:
e: file:///…/ui/Efectos.kt:103:9 Return type mismatch: expected 'DisposableEffectResult', actual 'Int'.El bloque tiene que devolver un DisposableEffectResult, y eso solo lo produce onDispose. Log.d(…) devuelve un Int, y por eso el mensaje dice actual 'Int'. La ruta y los números de línea del mensaje son los de tu fichero, y varían.
Con este ya tienes las seis herramientas de efectos de Compose, las de Efectos en Compose: LaunchedEffect, cada una en su lección:
| Herramienta | Para qué | Se deshace | Cuándo corre |
|---|---|---|---|
LaunchedEffect(claves) | Trabajo suspendido (corrutina) | Se cancela al salir o al cambiar una clave | Al entrar y cuando cambia una clave |
rememberUpdatedState(x) | Que un efecto largo vea siempre el último valor | No hace falta | Se actualiza en cada recomposición |
rememberCoroutineScope() | Corrutinas lanzadas desde un onClick | Se cancela al salir | Cuando tú la lanzas |
derivedStateOf { … } | Un estado calculado que avisa solo si cambia su resultado | No hace falta | Cuando cambia lo que lee |
DisposableEffect(claves) | Registrar algo y quitarlo | onDispose { … } | Al entrar y cuando cambia una clave |
SideEffect | Publicar estado a un objeto ajeno | No se deshace | Tras cada pasada completada |
Reto · Ejercicio: la emisora de radio
Es la misma mecánica del portero, pero comprobada: ahora ves cuántos oyentes quedan apuntados. Lo haces en la vista previa interactiva (la de Estado en Compose).
En la carpeta ejercicios (la que creaste en La rotación y la muerte del proceso): clic derecho sobre com.sebastiannarvaez.marcador → New → Package → escribe .ejercicios.emisora_de_radio. Luego clic derecho sobre esa carpeta → New → Kotlin Class/File → File → Radio.
Escribe tres piezas en Radio.kt:
Emisora, un objeto de fuera de Compose con una lista de oyentes (funciones que reciben un texto) y una función que emite una noticia a todos, como el timbre de arriba.Radio, que recuerda la última noticia (empieza en «nada todavía»). Al entrar en la composición se apunta a la emisora con un oyente que guarda la noticia; al salir se borra. Escribe «La radio dice: …».SalaDeRadio, con un interruptor «Radio encendida» (encendido al empezar), un botón «Emitir noticia» que emite «noticia 1», «noticia 2»…, un botón «¿Cuántos escuchan?» que copia en pantalla el número de oyentes apuntados («Escuchan: N») y laRadiosolo si el interruptor está encendido.
Añade una vista previa con SalaDeRadio. En el modo interactivo: pulsa «¿Cuántos escuchan?» y emite una noticia; apaga la radio, pregunta otra vez y emite dos; enciéndela y pregunta otra vez. Debes ver:
- «Escuchan: 1» y «La radio dice: noticia 1».
- Con la radio apagada, «Escuchan: 0».
- Al encenderla, «La radio dice: nada todavía» (es un
remembernuevo) y «Escuchan: 1», no 2: elonDisposeborró al oyente viejo.
Pista para onDispose: dentro va lo contrario de lo que hiciste al entrar; si añadiste el oyente a la lista de la emisora, ahí lo quitas de esa lista. Si te olvidas de onDispose, no compila (es el error típico de más arriba). Y si lo dejas vacío (onDispose { }), compila, pero al encender la radio saldría «Escuchan: 2»: eso es la fuga.
Ver solución
package com.sebastiannarvaez.marcador.ejercicios.emisora_de_radio
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
object Emisora { // algo de fuera de Compose
val oyentes = mutableListOf<(String) -> Unit>()
fun emitir(noticia: String) = oyentes.forEach { it(noticia) }
}
@Composable
fun Radio() {
var ultima by remember { mutableStateOf("nada todavía") }
DisposableEffect(Unit) {
val oyente: (String) -> Unit = { noticia -> ultima = noticia }
Emisora.oyentes.add(oyente) // al entrar: apuntarse
onDispose { Emisora.oyentes.remove(oyente) } // al salir: borrarse
}
Text("La radio dice: $ultima")
}
@Composable
fun SalaDeRadio(modifier: Modifier = Modifier) {
var encendida by remember { mutableStateOf(true) }
var noticias by remember { mutableIntStateOf(0) }
var escuchan by remember { mutableIntStateOf(0) }
Column(modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Switch(checked = encendida, onCheckedChange = { encendida = it })
Text("Radio encendida")
}
Button(onClick = { noticias++; Emisora.emitir("noticia $noticias") }) { Text("Emitir noticia") }
Button(onClick = { escuchan = Emisora.oyentes.size }) { Text("¿Cuántos escuchan?") }
Text("Escuchan: $escuchan")
if (encendida) Radio() // apagada: Radio sale de la composición
}
}
@Preview(showBackground = true)
@Composable
private fun SalaDeRadioPreview() {
MaterialTheme { SalaDeRadio() }
}Paso · el contador de «segundo plano»
Objetivo. Instalar lifecycle-runtime-compose, escribir tú en Efectos.kt el composable ObservadorDelCiclo y llamarlo desde PantallaMarcador, con la variable pausas que lo cuenta. Al terminar, la línea «Veces que la pantalla pasó a segundo plano: N» sube cada vez que pulsas Home y vuelves, y Logcat cuenta cuándo se registra y se quita el observador.
Instala lifecycle-runtime-compose
La receta de Los ficheros de Gradle de tu proyecto, en corto.
1 · Qué instalas. lifecycle-runtime-compose conecta el ciclo de vida de la Activity (El ciclo de vida de la Activity) con Compose: de ella sale LocalLifecycleOwner, lo primero que escribes hoy. Ya llega sola con la librería androidx.compose.ui (una dependencia transitiva: la trae otra librería que ya tienes, como foundation en Compose: composables y modifiers); la declaras tú porque tu código la usa directamente.
2 · La versión. Nada en [versions]: comparte número con lifecycle-runtime-ktx (lifecycleRuntimeKtx). Duplicarlo repetiría la trampa de Los ficheros de Gradle de tu proyecto: el catálogo tenía dos números para lo mismo, kotlin y jetbrainsKotlinJvm, y si no coinciden Gradle se queda con el más alto sin avisar.
3 · La librería. En Gradle Scripts → libs.versions.toml (Version Catalog "libs"), al final de [libraries], antes de la cabecera [bundles]:
# version.ref = lifecycleRuntimeKtx (2.11.0): el número de toda la familia lifecycle
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleRuntimeKtx" }4 · Un plugin, no. Es una librería: no hay [plugins] ni apply false.
5 · La dependencia. En build.gradle.kts (Module :app), dentro de dependencies { … }, debajo de libs.androidx.lifecycle.runtime.ktx:
// implementation: el código de la app la usa al compilar y al ejecutar
implementation(libs.androidx.lifecycle.runtime.compose)6 · Sync Now. Debe terminar sin errores rojos. Si falla (la caja «Si Sync sale en rojo» de Los ficheros de Gradle de tu proyecto), lo más probable es un alias con puntos donde el catálogo lleva guiones.
Compilar no demuestra nada, porque la librería ya llegaba sola: lo que cambia es que ahora la declaras tú. En la Terminal de Android Studio, pide a Gradle el árbol de dependencias y filtra las de androidx.lifecycle:
./gradlew :app:dependencies --configuration gratisDebugCompileClasspath | grep "^+--- androidx.lifecycle"Qué hace cada parte:
--configuration gratisDebugCompileClasspath: de todas las listas de dependencias que tiene el proyecto, pide la que se usa para compilar la variantegratisDebug(la de Gradle: flavors y variantes).| grep "…": se queda solo con las líneas que cumplen el patrón.^dentro del patrón: «al principio de la línea».+---es como Gradle empieza las dependencias de primer nivel, las que declaras tú enbuild.gradle.kts; las que traen otras librerías van más a la derecha, con sangría, y el^las descarta.
Debes ver exactamente estas dos (sin tu línea, solo la primera; (*) avisa de que esa rama ya se imprimió):
+--- androidx.lifecycle:lifecycle-runtime-ktx:2.11.0 (*)
+--- androidx.lifecycle:lifecycle-runtime-compose:2.11.0 (*)Escribe el observador
Punto de partida. Para abrir un fichero, en el panel Project (con el selector en Android) despliega app → kotlin+java → com.sebastiannarvaez.marcador → feature → marcador → ui y haz doble clic. Tras la lección anterior, Efectos.kt tiene ETIQUETA, MINUTOS_DEL_PARTIDO, RelojDelPartido, TituloDeLaActividad y PanelDelCronometro.
Piezas nuevas de este paso:
DisposableEffect(clave) { … onDispose { … } },LifecycleEventObserveryLocalLifecycleOwner.current: arriba.lifecycle.addObserver(o)ylifecycle.removeObserver(o): enganchan y sueltan un observador del ciclo de vida.Lifecycle.Event.ON_STOP: el evento que llega cuando la pantalla deja de verse (por ejemplo, con Home).;: separa dos órdenes escritas en la misma línea, como en elonReiniciarde Efectos en Compose: LaunchedEffect.
Cómo se hace. Primero la instalación de arriba (catálogo, build.gradle.kts y Sync Now). Después, intenta escribirlo tú, en este orden:
- Abre
Efectos.kty añade a su lista deimportlos de la primera pieza de abajo (Android Studio también los propone con Option+Enter). - Entre
RelojDelPartidoyTituloDeLaActividad, escribeObservadorDelCiclo: un composable que recibe qué hacer cuando la pantalla se para. Al entrar en la composición engancha un observador al ciclo de vida de la pantalla, que anota en Logcat cada evento que ve y, cuando la pantalla deja de verse, ejecuta esa acción (siempre la más reciente); al salir, lo suelta. Anota también cuándo lo engancha y cuándo lo suelta. - En
PanelDelCronometro, añade el parámetropausas(justo después deultimoAviso) y una línea más: «Veces que la pantalla pasó a segundo plano: …». - Abre
PantallaMarcador.kt. Declarapausasjusto después deultimoAviso(sobrevive a la rotación y empieza en 0), añade la llamada aObservadorDelCiclojusto después deRelojDelPartido(…) { … }y antes deTituloDeLaActividad(…)(la acción que le pasas suma uno apausascada vez que la pantalla se para), y pasapausasal panel. - Ejecuta la app (el apartado «Ejecútalo y míralo en Logcat» de abajo te dice cómo).
Todas las líneas de Logcat van con la etiqueta Efectos. Lo que debes ver al abrir la app:
D Efectos: el composable ve ON_CREATE
D Efectos: el composable ve ON_START
D Efectos: el composable ve ON_RESUME
D Efectos: observador REGISTRADO
D Efectos: título publicado: Marcador 0-0Y al volver de Home, la línea «Veces que la pantalla pasó a segundo plano» en 1; al cambiar de pestaña, observador QUITADO.
Intenta primero. Cuando termines, compara con la solución.Mostrar el código
Imports nuevos de Efectos.kt, mezclados con los que ya tiene y en su sitio alfabético:
import androidx.compose.runtime.DisposableEffect
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner(Log, getValue, rememberUpdatedState y Composable ya los tiene, de las lecciones anteriores.) La función, en Efectos.kt entre RelojDelPartido y TituloDeLaActividad:
@Composable
fun ObservadorDelCiclo(alPararse: () -> Unit) {
val propietario = LocalLifecycleOwner.current
val alPararseActual by rememberUpdatedState(alPararse)
// key = propietario: si cambiara el LifecycleOwner, se des-registra del viejo y se
// registra en el nuevo.
DisposableEffect(propietario) {
val observador = LifecycleEventObserver { _, evento ->
Log.d(ETIQUETA, "el composable ve $evento")
if (evento == Lifecycle.Event.ON_STOP) alPararseActual()
}
propietario.lifecycle.addObserver(observador)
Log.d(ETIQUETA, "observador REGISTRADO")
onDispose {
propietario.lifecycle.removeObserver(observador)
Log.d(ETIQUETA, "observador QUITADO")
}
}
}El panel con pausas (sustituye a PanelDelCronometro; es el parámetro nuevo y su línea de texto):
@Composable
fun PanelDelCronometro(
minuto: Int,
corriendo: Boolean,
ultimoAviso: String,
pausas: Int,
onAlternar: () -> Unit,
onReiniciar: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(modifier, verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text("Minuto $minuto'", style = MaterialTheme.typography.headlineMedium)
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
Button(onClick = onAlternar) { Text(if (corriendo) "Pausa" else if (minuto == 0) "Empezar" else "Reanudar") }
Button(onClick = onReiniciar) { Text("Reiniciar") }
}
Text("Último aviso (cada 15'): $ultimoAviso")
Text("Veces que la pantalla pasó a segundo plano: $pausas")
}
}En PantallaMarcador.kt, la variable, justo después de var ultimoAviso:
var pausas by rememberSaveable { mutableIntStateOf(0) }La llamada, justo después de RelojDelPartido(…) { … } y antes de TituloDeLaActividad(estado.marcador):
ObservadorDelCiclo(alPararse = { pausas++ })Y pausas en la llamada al panel, que queda así:
PanelDelCronometro(
minuto, corriendo, ultimoAviso, pausas,
onAlternar = { corriendo = !corriendo },
onReiniciar = { corriendo = false; minuto = 0; ultimoAviso = "ninguno" },
)Qué hace cada pieza
ObservadorDelCicloes el ejemplo mínimo con dos añadidos. Escribe cada evento en Logcat y, cuando llegaON_STOP, llama aalPararseActual(). La pantalla le pasa{ pausas++ }, y por eso el contador sube.rememberUpdatedStateevita que el observador se quede con una lambda vieja, como en el reloj.- El
DisposableEffectno declara más claves que el propietario: el observador se registra una vez al entrar y se quita al salir, no cada vez que cambia el minuto o el marcador. pausasestá enrememberSaveable: cuenta las veces que la pantalla pasó a segundo plano, y eso no se pierde al girar el móvil.
Ejecútalo y míralo en Logcat
Ejecuta la app con Run (configuración app, variante gratisDebug como en Gradle: flavors y variantes, emulador arrancado como en La Activity). Abre View → Tool Windows → Logcat y escribe tag:Efectos en el campo de búsqueda de arriba. Si prefieres la terminal, adb logcat -c vacía el registro y adb logcat -s Efectos se queda escuchando la etiqueta (para salir, Ctrl+C).
-
Al abrir la app, Logcat muestra cinco líneas seguidas:
D Efectos: el composable ve ON_CREATE D Efectos: el composable ve ON_START D Efectos: el composable ve ON_RESUME D Efectos: observador REGISTRADO D Efectos: título publicado: Marcador 0-0Las tres primeras sorprenden: nadie ha cambiado de estado, y sin embargo el observador «ve» tres eventos. Es lo normal. Cuando enganchas un observador con
addObservera una pantalla que ya está enRESUMED, Android lo pone al día en el acto: le entrega los eventos que ya pasaron (ON_CREATE,ON_STARTyON_RESUME) para que parta del mismo estado que la pantalla. Salen antes deobservador REGISTRADOporque esa línea la escribe tu código después de volver deaddObserver, y los eventos se entregan dentro de él. SoloON_STOPhace subir el contador. -
Pulsa Home y vuelve a la app (con el botón de recientes del emulador, o abriéndola desde la lista de aplicaciones). En Logcat verás pasar
ON_PAUSEyON_STOPal salir yON_STARTyON_RESUMEal volver; no vuelve a salirON_CREATEniobservador REGISTRADO, porque la pantalla no se destruyó y el observador sigue enganchado. La línea «Veces que la pantalla pasó a segundo plano» vale ahora 1. Si el reloj estaba en marcha (Efectos en Compose: LaunchedEffect), ha seguido corriendo. -
Cambia a la pestaña Partidos, la que hiciste en Pestañas con NavigationBar: aparece
observador QUITADO, porquePantallaMarcadorsalió de la composición y se ejecutóonDispose. Al volver a Marcador, aparece otra vezobservador REGISTRADO, precedido de los tresel composable ve ON_…del paso 1 (es un observador nuevo y se pone al día). No hace falta cerrar la app.
Provoca el error típico
En ObservadorDelCiclo, añade una última línea después de onDispose { … }, por ejemplo Log.d(ETIQUETA, "listo"). Al compilar, Android Studio marca el error con el mensaje del apartado «Error típico» (Return type mismatch: expected 'DisposableEffectResult', actual 'Int'). Bórrala y vuelve a ejecutar.
Punto de control
- La app compila y se ejecuta sin errores en rojo, con
ObservadorDelCicloescrita por ti. - Al abrir la pantalla, Logcat (
tag:Efectos) muestrael composable ve ON_CREATE,ON_STARTyON_RESUME, y despuésobservador REGISTRADOytítulo publicado: Marcador 0-0. - En la Terminal, el comando de
dependenciesde la instalación muestralifecycle-runtime-composeen el primer nivel del árbol, junto alifecycle-runtime-ktx. - Tras Home y volver, «Veces que la pantalla pasó a segundo plano» vale 1; tras otro Home y volver, 2.
- Al cambiar a la pestaña Partidos aparece
observador QUITADO, y al volver a Marcador, otroobservador REGISTRADO.
Comprueba lo que entendiste
5 preguntas · necesitas 4 para completar la lección
1.¿Qué tiene que ser la última sentencia del bloque de un
DisposableEffect?2.Un composable registra un observador dentro de un
DisposableEffecty el usuario cambia a otra pestaña, de modo que ese composable sale de la composición. ¿Qué ocurre?3.Necesitas registrar un listener al entrar en pantalla y quitarlo al salir. ¿Qué efecto usas?
4.Al compilar ves
Return type mismatch: expected 'DisposableEffectResult', actual 'Int'. ¿Qué ha pasado?5.Un
Porterose apunta a unTimbreconDisposableEffect. Apagas el interruptor que lo muestra, llamas tres veces al timbre y lo vuelves a encender. «Visitas» marca 0. ¿Por qué?