0Pricing
Kotlin Academy · Lezione

Convertire un Flow cold in hot con shareIn e stateIn

Trasformi Flow cold in stream hot con gli operatori di condivisione.

Convertire un Flow cold in hot con shareIn e stateIn è una lezione Kotlin Academy gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Kotlin Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Kotlin Academy include 4 lezioni in totale.

Flussi freddi e caldi

I flussi freddi ripartono per ogni collettore, mentre i flussi caldi condividono un'unica sottoscrizione upstream. Convertire un flusso freddo in uno caldo evita lavoro ridondante quando si sottoscrivono più collettori.

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()
}

Fondamenti di shareIn

flow.shareIn(scope, started, replay) converte un Flow freddo in uno SharedFlow, condividendo un'unica sottoscrizione 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()
}

Fondamenti di stateIn

flow.stateIn(scope, started, initialValue) converte il flusso in uno StateFlow, che ha sempre un valore e riproduce 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: l'upstream si avvia immediatamente quando viene chiamato shareIn/stateIn, indipendentemente dai sottoscrittori.

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: l'upstream si avvia con il primo sottoscrittore e non si arresta mai, anche se il numero di sottoscrittori scende a zero.

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): l'upstream si avvia quando si unisce il primo sottoscrittore e si arresta dopo stopTimeout ms dall'uscita dell'ultimo. È perfetto per i ViewModel.

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()
}

Pattern tipico per ViewModel

Nei ViewModel Android, converta i flussi del repository con stateIn(viewModelScope, WhileSubscribed(5000), initialValue) per condividerli e mantenerli durante i cambiamenti di configurazione.

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 e stateIn a confronto

shareIn → SharedFlow (nessun valore corrente, replay configurabile). stateIn → StateFlow (ha sempre un valore, replay=1). Scelga in base alla necessità dei consumer di accedere allo stato corrente.

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()
}

Compromessi della cache di replay

Un replay più elevato consente ai sottoscrittori che arrivano in ritardo di vedere una cronologia più ampia, ma richiede più memoria. Per lo stato dell'interfaccia, replay=1 (stateIn) è sufficiente. Per i log degli eventi potrebbe essere necessario un replay più elevato.

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") }

Rischi dei flussi non condivisi

Senza condivisione, ogni collettore Compose o osservatore del ViewModel esegue di nuovo l'upstream, causando chiamate di rete e query al database ridondanti. Condivida sempre i flussi costosi.

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()
}

Pulizia delle risorse durante la condivisione

Quando lo scope del flusso condiviso viene annullato, il flusso upstream viene annullato e vengono eseguiti i suoi blocchi finally: le risorse vengono pulite correttamente.

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)
}

Verifica rapida

Quale strategia SharingStarted è consigliata per i ViewModel Android?

Riepilogo

shareIn converte in SharedFlow, mentre stateIn converte in StateFlow. Utilizzi WhileSubscribed nei ViewModel per risparmiare risorse. La condivisione impedisce esecuzioni upstream ridondanti quando si sottoscrivono più collettori.

Domande Frequenti

La lezione «Convertire un Flow cold in hot con shareIn e stateIn» è gratuita?

Sì — il testo completo di «Convertire un Flow cold in hot con shareIn e stateIn» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Kotlin Academy, passa a CoddyKit PRO. Il corso Kotlin Academy include 4 lezioni in totale.

Cosa imparerò in «Convertire un Flow cold in hot con shareIn e stateIn»?

Trasformi Flow cold in stream hot con gli operatori di condivisione. Eserciti Kotlin Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Kotlin Academy?

Non è richiesta alcuna esperienza precedente. Kotlin Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.

Quanto tempo richiede la lezione «Convertire un Flow cold in hot con shareIn e stateIn»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Kotlin Academy?

Sì. Ogni lezione Kotlin Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. StateFlow: contenitore hot dello stato per l'interfaccia
  2. SharedFlow: event bus ed eventi one-shot
  3. Convertire un Flow cold in hot con shareIn e stateIn
  4. Testare StateFlow e SharedFlow con Turbine
← Torna a Kotlin Academy