Konwersja zimnego Flow na gorący za pomocą shareIn i stateIn
Przekształcaj zimne obiekty Flow w gorące strumienie za pomocą operatorów współdzielenia.
Konwersja zimnego Flow na gorący za pomocą shareIn i stateIn to bezpłatna lekcja Kotlin Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Kotlin Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Kotlin Academy zawiera 4 lekcji w sumie.
Zimne i gorące przepływy
Zimne przepływy uruchamiają się ponownie dla każdego odbiorcy, a gorące przepływy współdzielą jedną subskrypcję przepływu źródłowego. Konwersja zimnego przepływu na gorący pozwala uniknąć zbędnej pracy, gdy subskrybuje go wielu odbiorców.
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()
}Podstawy shareIn
flow.shareIn(scope, started, replay) konwertuje zimny Flow na SharedFlow, współdzieląc jedną subskrypcję przepływu źródłowego.
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()
}Podstawy stateIn
flow.stateIn(scope, started, initialValue) konwertuje przepływ na StateFlow — zawsze ma on wartość i odtwarza 1 element.
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: przepływ źródłowy uruchamia się natychmiast po wywołaniu shareIn/stateIn, niezależnie od liczby subskrybentów.
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: przepływ źródłowy uruchamia się przy pierwszym subskrybencie i nigdy się nie zatrzymuje, nawet gdy liczba subskrybentów spadnie do zera.
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): przepływ źródłowy uruchamia się, gdy dołączy pierwszy subskrybent, i zatrzymuje się stopTimeout ms po odejściu ostatniego. To idealne rozwiązanie dla ViewModeli.
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()
}Typowy wzorzec ViewModelu
W ViewModelach systemu Android konwertuj przepływy z repozytorium za pomocą stateIn(viewModelScope, WhileSubscribed(5000), initialValue), aby je współdzielić i zachować podczas zmian konfiguracji.
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 a stateIn
shareIn → SharedFlow (bez bieżącej wartości, konfigurowalny replay). stateIn → StateFlow (zawsze ma wartość, replay=1). Wybierz odpowiednią funkcję w zależności od tego, czy odbiorcy potrzebują bieżącego stanu.
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()
}Kompromisy związane z pamięcią replay
Większy replay oznacza, że odbiorcy dołączający z opóźnieniem zobaczą więcej historii, ale zużywa więcej pamięci. W przypadku stanu interfejsu użytkownika wystarczy replay=1 (stateIn). Dla dzienników zdarzeń może być potrzebna większa wartość replay.
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") }Ryzyko nieudostępnionego przepływu
Bez współdzielenia każdy kolektor Compose lub obserwator ViewModelu ponownie uruchamia przepływ źródłowy — powoduje to zbędne wywołania sieci i zapytania do bazy danych. Zawsze udostępniaj kosztowne przepływy.
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()
}Czyszczenie zasobów podczas współdzielenia
Gdy zakres współdzielonego przepływu zostanie anulowany, przepływ źródłowy również zostanie anulowany, a jego bloki finally zostaną wykonane — zasoby są prawidłowo zwalniane.
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)
}Szybkie sprawdzenie
Która strategia SharingStarted jest zalecana dla ViewModeli systemu Android?
Podsumowanie
shareIn konwertuje przepływ na SharedFlow, a stateIn konwertuje go na StateFlow. Używaj WhileSubscribed w ViewModelach, aby oszczędzać zasoby. Współdzielenie zapobiega wielokrotnemu wykonywaniu przepływu źródłowego, gdy subskrybuje go wielu odbiorców.
Często zadawane pytania
Czy lekcja „Konwersja zimnego Flow na gorący za pomocą shareIn i stateIn” jest bezpłatna?
Tak — pełny tekst „Konwersja zimnego Flow na gorący za pomocą shareIn i stateIn” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Kotlin Academy, przejdź na CoddyKit PRO. Kurs Kotlin Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Konwersja zimnego Flow na gorący za pomocą shareIn i stateIn”?
Przekształcaj zimne obiekty Flow w gorące strumienie za pomocą operatorów współdzielenia. Ćwiczysz Kotlin Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Kotlin Academy?
Nie wymagamy żadnego doświadczenia. Kotlin Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.
Ile czasu zajmuje lekcja „Konwersja zimnego Flow na gorący za pomocą shareIn i stateIn”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Kotlin Academy?
Tak. Każda lekcja Kotlin Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- StateFlow: gorący kontener stanu dla interfejsu
- SharedFlow: magistrale zdarzeń i zdarzenia jednorazowe
- Konwersja zimnego Flow na gorący za pomocą shareIn i stateIn
- Testowanie StateFlow i SharedFlow z Turbine