Esta pagina se ve mejor con JavaScript habilitado

Gestión Avanzada de Estado en Compose: Effects y Flows

 ·  ☕ 5 minutos lectura  ·  ✍️ Ignacio Carrión

Gestión Avanzada de Estado en Compose: Effects y Flows

Este artículo explora patrones avanzados de gestión de estado en Jetpack Compose, centrándose en Effects e integración de Flows. Para conceptos fundamentales como mutableStateOf y state hoisting, consulta nuestro artículo complementario Gestión Básica de Estado en Jetpack Compose.

Entendiendo los Effects en Compose

Los Effects en Compose son herramientas para manejar efectos secundarios y eventos del ciclo de vida de manera compatible con los composables. Exploremos los diferentes tipos de effects y sus casos de uso:

LaunchedEffect: Efectos Secundarios Basados en Corrutinas

LaunchedEffect lanza una corrutina que se cancela automáticamente cuando el composable sale de la composición:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@Composable
fun AutoRefreshingList(viewModel: ListViewModel) {
    // Inicia una corrutina que actualiza datos cada 30 segundos
    LaunchedEffect(Unit) {
        while(true) {
            viewModel.refreshData()
            delay(30_000)
        }
    }
    
    // El parámetro key controla cuándo debe reiniciarse el efecto
    LaunchedEffect(viewModel.searchQuery) {
        viewModel.performSearch()
    }
}

SideEffect: Sincronización con Código No-Compose

SideEffect se llama en cada recomposición exitosa para sincronizar el estado de Compose con código no-Compose:

1
2
3
4
5
6
7
@Composable
fun AnalyticsScreen(screenName: String) {
    SideEffect {
        // Se llama después de cada recomposición exitosa
        AnalyticsTracker.trackScreen(screenName)
    }
}

DisposableEffect: Operaciones de Limpieza

DisposableEffect maneja la limpieza cuando un composable sale de la composición:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Composable
fun NetworkMonitor(onConnectionChange: (Boolean) -> Unit) {
    DisposableEffect(Unit) {
        val listener = NetworkListener(onConnectionChange)
        listener.register()
        
        onDispose {
            listener.unregister()
        }
    }
}

derivedStateOf: Estado Computado

derivedStateOf crea un estado que se actualiza automáticamente cuando sus dependencias cambian. Es particularmente útil para cálculos basados en umbrales y derivaciones de estado de UI que no deberían disparar recomposiciones en cada pequeño cambio:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.derivedStateOf

// Ejemplo que muestra cómo derivedStateOf ayuda a prevenir 
// recomposiciones innecesarias al trabajar con estado de scroll
@Composable
fun ScrollableNewsScreen() {
    val listState = rememberLazyListState()

    // ❌ Sin derivedStateOf
    // Estas propiedades se recalcularían en cada cambio de píxel del scroll,
    // causando recomposiciones innecesarias de cualquier composable que las lea
    // val showScrollToTop = listState.firstVisibleItemIndex > 0
    // val isScrollInProgress = listState.isScrollInProgress

    // ✅ Con derivedStateOf
    // Estos cálculos solo se disparan al cruzar umbrales específicos,
    // reduciendo significativamente las recomposiciones innecesarias
    val showScrollToTop by remember {
        derivedStateOf {
            // Solo muestra el botón cuando se ha desplazado más allá del primer elemento
            listState.firstVisibleItemIndex > 0
        }
    }
    
    val shouldLoadMore by remember {
        derivedStateOf {
            val lastVisibleItem = listState.layoutInfo.visibleItemsInfo.lastOrNull()
            // Dispara la paginación cuando el usuario está cerca del final
            lastVisibleItem?.index != null && 
                lastVisibleItem.index >= listState.layoutInfo.totalItemsCount - 3
        }
    }
    
    val isScrollingUp by remember {
        derivedStateOf {
            // Rastrea la dirección del scroll basado en el primer elemento visible
            listState.firstVisibleItemScrollOffset < 100
        }
    }

    // Usa estos estados derivados para controlar elementos de UI como:
    // - Visibilidad del botón de scroll hacia arriba (showScrollToTop)
    // - Carga de paginación (shouldLoadMore)
    // - Barra superior colapsable/expandible (isScrollingUp)
    NewsScreenContent(
        showScrollButton = showScrollToTop,
        isScrollingUp = isScrollingUp,
        shouldLoadMore = shouldLoadMore
    )
}

// Nota: Implementación de NewsScreenContent y otros componentes UI omitidos por brevedad

produceState: Convirtiendo Estado No-Compose a State

produceState convierte fuentes de estado no-Compose en estado de Compose:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Composable
fun UserProfile(userId: String) {
    val user by produceState<User?>(initialValue = null, userId) {
        value = userRepository.fetchUser(userId)
        
        awaitDispose {
            // Limpieza si es necesaria
        }
    }
}

Integración de Flows con Compose

Cuando trabajamos con Flows en Compose, necesitamos convertirlos en estado de Compose. Jetpack Compose proporciona dos funciones principales para este propósito: collectAsState y collectAsStateWithLifecycle.

collectAsState: Recolección Básica de Flow

collectAsState convierte un Flow en un State de Compose:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@Composable
fun UserProfile(viewModel: UserViewModel) {
    // Recolección básica
    val name by viewModel.nameFlow.collectAsState()
    
    // Con valor inicial
    val count by viewModel.countFlow.collectAsState(initial = 0)
    
    // Manejando flows nulables
    val user by viewModel.userFlow.collectAsState(initial = null)
    
    Text("Nombre: $name")
    Text("Contador: $count")
    user?.let { Text("Usuario: ${it.name}") }
}

collectAsStateWithLifecycle: Recolección Consciente del Ciclo de Vida

collectAsStateWithLifecycle es la forma recomendada de recolectar flows en Compose ya que respeta el ciclo de vida del composable. Proporciona varios beneficios sobre collectAsState:

  • Detiene automáticamente la recolección cuando el composable no está visible
  • Reanuda la recolección cuando el composable vuelve a ser visible
  • Reduce el procesamiento innecesario y el consumo de batería
  • Previene fugas de memoria limpiando adecuadamente los recursos
  • Permite un control preciso sobre cuándo debe ocurrir la recolección

Aquí te mostramos cómo usarlo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Tipos de datos para el ejemplo
data class UserUpdate(
    val id: String,
    val message: String,
    val timestamp: Long
)

@Composable
fun UserUpdates(viewModel: UserViewModel) {
    // Recolección básica consciente del ciclo de vida
    val updates by viewModel.userUpdatesFlow
        .collectAsStateWithLifecycle(initialValue = emptyList())
    
    // Estado de ciclo de vida personalizado
    val notifications by viewModel.notificationsFlow
        .collectAsStateWithLifecycle(
            initialValue = emptyList(),
            lifecycle = lifecycle,
            minActiveState = Lifecycle.State.RESUMED // Solo recolecta cuando está RESUMED
        )
    
    LazyColumn {
        items(updates) { update: UserUpdate ->
            UpdateItem(update)
        }
    }
}

Conclusión

La gestión avanzada de estado en Compose requiere entender tanto los Effects como la integración de Flows. Los Effects ayudan a manejar efectos secundarios y eventos del ciclo de vida, mientras que la integración adecuada de Flows asegura una recolección eficiente de estado que respeta el ciclo de vida de Android.

Recuerda:

  • Elegir el Effect apropiado para tu caso de uso
  • Usar collectAsStateWithLifecycle para la integración de Flows al recolectar de flows
  • Probar exhaustivamente tu implementación de gestión de estado

Para conceptos fundamentales de gestión de estado, consulta nuestro artículo complementario Gestión Básica de Estado en Jetpack Compose.

compartir en

Ignacio Carrión
Escrito por
Ignacio Carrión
Android Developer