0Pricing
Kotlin Academy · Lekcja

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

  1. StateFlow: gorący kontener stanu dla interfejsu
  2. SharedFlow: magistrale zdarzeń i zdarzenia jednorazowe
  3. Konwersja zimnego Flow na gorący za pomocą shareIn i stateIn
  4. Testowanie StateFlow i SharedFlow z Turbine
← Powrót do Kotlin Academy