Convertir Cold Flow en Hot con shareIn y stateIn
Transforme Flows fríos en flujos activos mediante operadores de compartición.
Convertir Cold Flow en Hot con shareIn y stateIn es una lección gratuita de Kotlin Academy en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Kotlin Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Kotlin Academy incluye 4 lecciones en total.
Flows fríos y calientes
Los flows fríos se reinician para cada colector; los flows calientes comparten una única suscripción al upstream. Convertir un flow frío en uno caliente evita trabajo redundante cuando se suscriben varios colectores.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun expensiveFlow() = flow {
println("Starting upstream work") // runs once if shared
repeat(3) { delay(100); emit(it) }
}
fun main() = runBlocking {
val cold = expensiveFlow()
// Two collectors = two executions:
launch { cold.collect { } }
launch { cold.collect { } }
delay(500)
coroutineContext.cancelChildren()
}Conceptos básicos de shareIn
flow.shareIn(scope, started, replay) convierte un Flow frío en un SharedFlow y comparte una única suscripción al upstream.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val cold = flow {
println("Upstream started once")
repeat(5) { delay(100); emit(it) }
}
val hot = cold.shareIn(this, SharingStarted.Eagerly, replay = 0)
launch { hot.collect { println("A: $it") } }
launch { hot.collect { println("B: $it") } }
delay(600)
coroutineContext.cancelChildren()
}Conceptos básicos de stateIn
flow.stateIn(scope, started, initialValue) lo convierte en un StateFlow: siempre tiene un valor y reproduce 1 elemento.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val cold = flow { delay(100); emit(42) }
val state: StateFlow<Int> = cold.stateIn(
scope = this,
started = SharingStarted.Eagerly,
initialValue = 0
)
println(state.value) // 0 immediately
delay(200)
println(state.value) // 42 after upstream emits
coroutineContext.cancelChildren()
}SharingStarted.Eagerly
Eagerly: el upstream se inicia inmediatamente cuando se llama a shareIn/stateIn, independientemente de que haya suscriptores.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val flow = flow {
println("Eagerly started")
emit(1)
}.shareIn(this, SharingStarted.Eagerly)
// Upstream already running even before any collect
delay(50)
launch { flow.collect { println(it) } }
delay(100)
coroutineContext.cancelChildren()
}SharingStarted.Lazily
Lazily: el upstream se inicia con el primer suscriptor y nunca se detiene, aunque el número de suscriptores llegue a cero.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val flow = flow {
println("Lazily started on first subscriber")
repeat(3) { delay(100); emit(it) }
}.shareIn(this, SharingStarted.Lazily, replay = 1)
delay(50) // no subscriber yet — not started
launch { flow.collect { println(it) } } // triggers start
delay(400)
coroutineContext.cancelChildren()
}SharingStarted.WhileSubscribed
WhileSubscribed(stopTimeout): el upstream se inicia cuando se conecta el primer suscriptor y se detiene stopTimeout milisegundos después de que se va el último. Es perfecto para ViewModels.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val flow = flow {
println("Started")
repeat(10) { delay(100); emit(it) }
}.shareIn(this, SharingStarted.WhileSubscribed(500))
val job = launch { flow.collect { print("$it ") } }
delay(300)
job.cancel() // subscriber left
delay(200) // within 500ms stop timeout — still running
launch { flow.collect { print("resume $it ") } }
delay(500)
coroutineContext.cancelChildren()
}Patrón habitual en ViewModel
En los ViewModels de Android, convierta los flows del repositorio con stateIn(viewModelScope, WhileSubscribed(5000), initialValue) para compartirlos y conservarlos durante los cambios de configuración.
import kotlinx.coroutines.flow.*
// In ViewModel:
// val uiState: StateFlow<UiState> = repository
// .dataFlow()
// .map { UiState.Success(it) }
// .stateIn(
// scope = viewModelScope,
// started = SharingStarted.WhileSubscribed(5_000),
// initialValue = UiState.Loading
// )
fun main() { println("WhileSubscribed(5000) is the recommended ViewModel pattern") }shareIn frente a stateIn
shareIn → SharedFlow (sin valor actual y con replay configurable). stateIn → StateFlow (siempre tiene un valor y usa replay=1). Elija según necesiten los consumidores un estado actual o no.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val cold = (1..3).asFlow().map { it * 10 }
// SharedFlow — no initial value:
val shared: SharedFlow<Int> = cold.shareIn(this, SharingStarted.Eagerly, replay = 1)
// StateFlow — always has a value:
val state: StateFlow<Int> = cold.stateIn(this, SharingStarted.Eagerly, 0)
delay(100)
println("shared cache: ${shared.replayCache}")
println("state value: ${state.value}")
coroutineContext.cancelChildren()
}Ventajas y desventajas de la caché de replay
Un replay más alto permite que los suscriptores tardíos vean más historial, pero utiliza más memoria. Para el estado de la UI, replay=1 (stateIn) es suficiente. Para registros de eventos, puede ser necesario un replay más alto.
import kotlinx.coroutines.flow.*
// replay=0: no history, only future events
// replay=1: last value (equivalent to stateIn)
// replay=N: last N events — use for message feeds, logs
// Trade-off: memory vs subscriber freshness
fun main() { println("Choose replay based on late-subscriber requirements") }Riesgo de no compartir un Flow
Sin compartirlo, cada colector de Compose o cada observador del ViewModel vuelve a ejecutar el upstream, lo que provoca llamadas de red y consultas a la base de datos redundantes. Comparta siempre los flows costosos.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun expensiveApi() = flow {
println("API CALL") // without sharing: once per collector
emit("data")
}
fun main() = runBlocking {
val shared = expensiveApi().shareIn(this, SharingStarted.Lazily, replay = 1)
launch { shared.collect { } } // one API call, shared
launch { shared.collect { } } // same emission
delay(200)
coroutineContext.cancelChildren()
}Liberación de recursos al compartir
Cuando se cancela el scope del flow compartido, el flow upstream se cancela y se ejecutan sus bloques finally; los recursos se liberan correctamente.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun resourceFlow() = flow {
try { repeat(10) { delay(100); emit(it) } }
finally { println("Upstream cleaned up") }
}
fun main() = runBlocking {
val scope = CoroutineScope(SupervisorJob())
val shared = resourceFlow().shareIn(scope, SharingStarted.Eagerly)
launch { shared.collect { print("$it ") } }
delay(250)
scope.cancel() // upstream cleanup runs
delay(100)
}Comprobación rápida
¿Qué estrategia de SharingStarted se recomienda para los ViewModels de Android?
Resumen
shareIn convierte el flow en un SharedFlow; stateIn lo convierte en un StateFlow. Use WhileSubscribed en los ViewModels para ahorrar recursos. Compartir evita ejecuciones redundantes del upstream cuando se suscriben varios colectores.
Preguntas frecuentes
¿La lección «Convertir Cold Flow en Hot con shareIn y stateIn» es gratis?
Sí — el texto completo de «Convertir Cold Flow en Hot con shareIn y stateIn» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Kotlin Academy, actualiza a CoddyKit PRO. El curso de Kotlin Academy incluye 4 lecciones en total.
¿Qué aprenderé en «Convertir Cold Flow en Hot con shareIn y stateIn»?
Transforme Flows fríos en flujos activos mediante operadores de compartición. Practicas Kotlin Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar Kotlin Academy?
No se requiere experiencia previa. Kotlin Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.
¿Cuánto tiempo toma la lección «Convertir Cold Flow en Hot con shareIn y stateIn»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de Kotlin Academy?
Sí. Cada lección de Kotlin Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- StateFlow: contenedor de estado activo para la interfaz
- SharedFlow: buses de eventos y eventos de una sola emisión
- Convertir Cold Flow en Hot con shareIn y stateIn
- Probar StateFlow y SharedFlow con Turbine