# Kotlin Flow vs StateFlow vs SharedFlow: pytania rekrutacyjne Android w 2026 > Pytania o Kotlin Flow vs StateFlow vs SharedFlow, które rekruterzy Androida zadają w 2026 roku, z jasnymi odpowiedziami, tabelą porównawczą i kodem gotowym do produkcji. - Published: 2026-07-01 - Updated: 2026-07-07 - Author: SharpSkill - Tags: android, kotlin, coroutines, flow, interview - Reading time: 9 min --- Kotlin Flow vs StateFlow vs SharedFlow to jeden z najczęstszych tematów rozmów kwalifikacyjnych dla programistów Androida w 2026 roku, a pomylenie tych trzech typów potrafi szybko przekreślić szanse na rundzie poświęconej korutynom. StateFlow i SharedFlow to strumienie gorące zbudowane na bazie Flow, jednak każdy z nich rozwiązuje inny problem: przechowywanie stanu kontra rozgłaszanie zdarzeń. Poniższe pytania rzeczywiście padają na rozmowach z rekruterami technicznymi Androida, wraz z precyzyjnymi odpowiedziami i kodem gotowym do produkcji. > **Odpowiedź w 20 sekund** > > `Flow` jest zimny i uruchamia swojego producenta osobno dla każdego kolektora. `StateFlow` to gorący, sklejający (conflated) strumień, który zawsze przechowuje jedną bieżącą wartość, idealny do stanu UI. `SharedFlow` to gorący strumień bez wymaganej wartości początkowej, idealny do jednorazowych zdarzeń, takich jak nawigacja czy snackbar. ## Kotlin Flow vs StateFlow vs SharedFlow: kluczowe różnice StateFlow i SharedFlow to specjalizacje `SharedFlow`, który sam w sobie jest gorącym `Flow`. Rekruterzy sprawdzają rozróżnienie na strumienie zimne i gorące, to, czy bieżąca wartość jest zachowywana, oraz jak każdy z typów zachowuje się przy zduplikowanych emisjach. Zwięzły model myślowy: `Flow` to przepis, `StateFlow` to pojedyncza zmienna wartość, a `SharedFlow` to szyna zdarzeń (event bus). | Właściwość | Flow (zimny) | StateFlow | SharedFlow | |---|---|---|---| | Temperatura | Zimny | Gorący | Gorący | | Wartość początkowa | Brak | Wymagana | Opcjonalna (przez `replay`) | | Przechowuje bieżącą wartość | Nie | Tak, przez `.value` | Nie | | Emituje duplikaty | Tak | Nie (sklejanie + deduplikacja) | Konfigurowalne | | Dodatkowe kolektory | Za każdym razem nowy strumień | Współdzielony | Współdzielony | | Najlepszy do | Asynchroniczne potoki danych | Stan UI | Jednorazowe zdarzenia | [Oficjalna dokumentacja korutyn Kotlina](https://kotlinlang.org/docs/flow.html) traktuje tę domyślną zimność jako cechę definiującą `Flow`, więc mocna odpowiedź powinna zacząć się właśnie od tego. ## Dlaczego Kotlin Flow jest domyślnie zimny? Zimny flow nie robi nic, dopóki nie zostanie wywołane `collect()`, a swój blok producenta wykonuje ponownie dla każdego kolektora. Dwa kolektory podpięte do tego samego zimnego flow wywołają dwa niezależne zapytania sieciowe. To najczęściej sprawdzana koncepcja, dlatego krótki przykład dobrze ilustruje odpowiedź. ```kotlin // SearchRepository.kt fun searchResults(query: String): Flow> = flow { // This block runs fresh for every collector. // Nothing executes until a collector calls collect(). val results = api.search(query) // suspending network call emit(results) // pushed downstream to the collector } ``` Ponieważ producent uruchamia się od nowa dla każdego kolektora, zimne flow są właściwym domyślnym wyborem dla potoków danych, które mają działać na żądanie. Stają się problemem dopiero wtedy, gdy jedna wartość musi być współdzielona przez cały ekran, i dokładnie tu wkraczają StateFlow oraz SharedFlow. ## Pytania o StateFlow: stan, który zawsze emituje `StateFlow` to gorący flow, który zawsze ma wartość i odtwarza tę najnowszą wartość każdemu nowemu kolektorowi. Wymaga wartości początkowej, udostępnia `.value` do synchronicznego odczytu i skleja emisje: szybkie kolejne aktualizacje mogą pomijać wartości pośrednie, a ustawienie tej samej wartości dwukrotnie nie emituje nic, ponieważ deduplikacja odbywa się przez porównanie równości. Czyni to z niego nowoczesne zastępstwo dla `LiveData` w `ViewModel`. ```kotlin // SearchViewModel.kt class SearchViewModel(private val repo: SearchRepository) : ViewModel() { // MutableStateFlow demands an initial value, so the UI always has something to render. private val _uiState = MutableStateFlow(SearchUiState.Idle) val uiState: StateFlow = _uiState.asStateFlow() fun onQueryChanged(query: String) { _uiState.value = SearchUiState.Loading viewModelScope.launch { val results = repo.search(query) // update {} applies the change atomically, safe under concurrent callers. _uiState.update { SearchUiState.Success(results) } } } } ``` Częste pytanie uzupełniające: dlaczego udostępniać `asStateFlow()` zamiast pola mutowalnego? Przekazuje ono interfejsowi widok tylko do odczytu, dzięki czemu stan może zmieniać się wyłącznie przez ViewModel, co zachowuje jednokierunkowy przepływ danych. [Przewodnik Androida po StateFlow i SharedFlow](https://developer.android.com/kotlin/flow/stateflow-and-sharedflow) zaleca dokładnie ten wzorzec. ## SharedFlow vs StateFlow: wybór właściwego typu do zdarzeń Klasyczne pytanie-pułapka brzmi: „czy StateFlow może dostarczać jednorazowe zdarzenia?". Szczera odpowiedź to nie, nie w bezpieczny sposób. Ponieważ StateFlow skleja i deduplikuje, zdarzenie nawigacji może zostać pominięte przy szybkich aktualizacjach, a przy zmianie konfiguracji uruchamia się ponownie, gdyż nowy kolektor odtwarza zachowaną wartość. `SharedFlow` z `replay = 0` rozwiązuje oba problemy: nic nie jest zachowywane, a każda emisja dociera wyłącznie do kolektorów aktywnych w chwili emisji. ```kotlin // CheckoutViewModel.kt class CheckoutViewModel : ViewModel() { // replay = 0: a late collector must NOT re-receive a past navigation event. private val _events = MutableSharedFlow(replay = 0) val events: SharedFlow = _events.asSharedFlow() fun onPaymentConfirmed() { viewModelScope.launch { // emit() suspends if the buffer is full; tryEmit() is the non-suspending variant. _events.emit(CheckoutEvent.NavigateToReceipt) } } } ``` W pytaniach o architekturę głębsze uzasadnienie oddzielania stanu od zdarzeń pojawia się w [porównaniu MVVM i MVI](/blog/android/mvvm-vs-mvi-architecture), gdzie MVI traktuje zdarzenia jako jawny strumień, a nie jako stan mutowalny. ## Konwersja zimnego Flow na gorący StateFlow za pomocą stateIn Rekruterzy często pytają, jak zamienić zimny flow z repozytorium na stan UI. Odpowiedzią jest `stateIn` (dla pojedynczej zachowywanej wartości) lub `shareIn` (dla rozgłaszania bez bieżącej wartości). Parametr `SharingStarted` kontroluje, kiedy strumień źródłowy jest aktywny, a `WhileSubscribed(5_000)` to standardowy wybór, ponieważ utrzymuje flow przy życiu podczas obrotu ekranu, nie powodując przy tym wycieku po zamknięciu ekranu. ```kotlin // ProfileViewModel.kt val profile: StateFlow = repo.profileStream() .stateIn( scope = viewModelScope, // Keep the upstream alive 5s after the last collector leaves, surviving rotation. started = SharingStarted.WhileSubscribed(5_000), initialValue = Profile.EMPTY ) ``` Różnica między `stateIn` a `shareIn` to dokładnie różnica między StateFlow a SharedFlow: `stateIn` potrzebuje `initialValue` i przechowuje stan, natomiast `shareIn` przyjmuje liczbę `replay` i rozgłasza. Przegląd szerszego modelu korutyn w [tym przewodniku po opanowaniu korutyn Kotlina](/blog/android/mastering-kotlin-coroutines) pomaga powiązać te operatory z zakresami (scope) i anulowaniem. ## Bezpieczne zbieranie flow w cyklu życia Androida Pytanie na poziomie seniora dotyczy tego, jak zbierać flow bez powodowania wycieku pracy, gdy ekran znajduje się w tle. Zbieranie wewnątrz zwykłego `launch` działa dalej, gdy UI jest zatrzymane, marnując CPU i grożąc awariami. `repeatOnLifecycle(STARTED)` anuluje zbieranie przy `STOP` i wznawia je przy `START`. ```kotlin // ProfileFragment.kt (View system) viewLifecycleOwner.lifecycleScope.launch { // Collection restarts on STARTED and cancels on STOPPED: no wasted work in the background. viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState.collect { state -> render(state) } } } ``` W Jetpack Compose odpowiednikiem jest `collectAsStateWithLifecycle()`, które przerywa zbieranie, gdy aplikacja trafia do tła, i wznawia je po powrocie. ```kotlin // ProfileScreen.kt (Jetpack Compose) @Composable fun ProfileScreen(viewModel: ProfileViewModel) { // collectAsStateWithLifecycle stops collecting when the app is backgrounded. val state by viewModel.uiState.collectAsStateWithLifecycle() ProfileContent(state) } ``` Ta świadomość cyklu życia sprawia, że StateFlow w połączeniu z `collectAsStateWithLifecycle` jest domyślnym wzorcem obsługi stanu w nowoczesnych aplikacjach Compose, co szerzej omawiają te [pytania rekrutacyjne o Jetpack Compose](/blog/android/jetpack-compose-interview-questions). ## Jak testować emisje StateFlow i SharedFlow To właśnie na testach seniorzy pokazują swoją przewagę. Naiwne podejście odczytuje `stateFlow.value` jeden raz, ale pomija stany pośrednie i w ogóle nie pozwala obserwować SharedFlow. Standardowa odpowiedź wskazuje bibliotekę Turbine, która zawiesza wykonanie do momentu nadejścia każdej emisji i oznacza test jako nieudany, jeśli oczekiwana wartość nigdy nie nadejdzie. ```kotlin // SearchViewModelTest.kt @Test fun `emits Loading then Success on query`() = runTest { val viewModel = SearchViewModel(fakeRepo) viewModel.uiState.test { assertEquals(SearchUiState.Idle, awaitItem()) // initial value viewModel.onQueryChanged("kotlin") assertEquals(SearchUiState.Loading, awaitItem()) // intermediate state assertEquals(SearchUiState.Success(fakeResults), awaitItem()) cancelAndIgnoreRemainingEvents() } } ``` Połączenie Turbine z `runTest` i testowym dyspozytorem sprawia, że asercje są deterministyczne, a wspomnienie różnicy między `StandardTestDispatcher` a `UnconfinedTestDispatcher` świadczy o realnym doświadczeniu w testowaniu korutyn. ## Częste pytania uzupełniające o Kotlin Flow Rekruterzy kończą serią szybkich pytań. Co robi `SharingStarted.WhileSubscribed(5_000)`? Uruchamia strumień źródłowy przy pierwszym subskrybencie i zatrzymuje go 5 sekund po odejściu ostatniego, co wystarcza, by przetrwać obrót ekranu. Dlaczego StateFlow pomija zduplikowane wartości? Porównuje je za pomocą `equals()`, więc emisja równej wartości nie robi nic, i dlatego klasy danych (data class) mają znaczenie dla stanu. Czy SharedFlow może zachowywać się jak StateFlow? Ustawienie `replay = 1` sprawia, że zachowuje ostatnią wartość, ale nadal brakuje mu synchronicznego `.value` i nigdy nie deduplikuje. Co kontroluje przeciwciśnienie (back-pressure) w SharedFlow? Parametry `extraBufferCapacity` i `onBufferOverflow`, przy czym `BufferOverflow.DROP_OLDEST` to częsty wybór dla zdarzeń. Więcej praktyki z tymi operatorami znajduje się w [module rekrutacyjnym o korutynach i Flow w Kotlinie](/technologies/android/interview-questions/android-kotlin-coroutines-flow). > **Błąd, który przekreśla kandydatów** > > Używanie `StateFlow` do zdarzeń nawigacji lub snackbara. Ponieważ odtwarza on swoją ostatnią wartość nowym kolektorom, zdarzenie uruchamia się ponownie po obrocie ekranu i użytkownik zostaje przeniesiony dwukrotnie. Lepiej sięgnąć po `SharedFlow` z `replay = 0` albo zamodelować jednorazowe zdarzenie, które jest czyszczone po obsłużeniu. Warto przed rozmową przejrzeć źródła kotlinx.coroutines oraz ich [dokumentację pakietu flow na GitHubie](https://github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-core), ponieważ KDoc dla `StateFlow` i `SharedFlow` precyzyjnie opisuje gwarancje sklejania i odtwarzania. ## Podsumowanie Rozróżnienie Kotlin Flow vs StateFlow vs SharedFlow nagradza precyzyjny język na rozmowie kwalifikacyjnej. Najważniejsze wnioski do zapamiętania: - `Flow` jest zimny: producent uruchamia się ponownie dla każdego kolektora, więc pasuje do potoków danych działających na żądanie. - `StateFlow` jest gorący, zawsze przechowuje wartość oraz skleja i deduplikuje, co czyni go narzędziem do obsługi stanu UI. - `SharedFlow` jest gorący i nie wymaga wartości początkowej, co czyni go właściwym wyborem do jednorazowych zdarzeń. - Zamień zimny na gorący za pomocą `stateIn` lub `shareIn` i użyj `SharingStarted.WhileSubscribed(5_000)`, aby przetrwać obrót ekranu. - Zbieraj za pomocą `repeatOnLifecycle(STARTED)` lub `collectAsStateWithLifecycle()`, aby uniknąć pracy w tle i wycieków. - Nigdy nie dostarczaj zdarzeń przez StateFlow: jego mechanizm odtwarzania uruchamia je ponownie po zmianie konfiguracji. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/android/kotlin-flow-vs-stateflow-vs-sharedflow-interview