Kotlin Flow проти StateFlow проти SharedFlow: питання на Android-співбесіді у 2026
Питання про Kotlin Flow проти StateFlow проти SharedFlow, які Android-інтерв'юери ставлять у 2026 році, з чіткими відповідями, таблицею порівняння та готовим до продакшену кодом.

Kotlin Flow проти StateFlow проти SharedFlow — одна з найпоширеніших тем на Android-співбесідах у 2026 році, і плутанина між цими трьома типами швидко провалює раунд із корутинами. StateFlow і SharedFlow — це гарячі потоки, побудовані поверх Flow, але вони існують для різних задач: зберігання стану проти трансляції подій. Питання нижче — саме ті, які Android-інтерв'юери ставлять насправді, з точними відповідями та готовим до продакшену кодом.
Flow є холодним і запускає свій продюсер окремо для кожного колектора. StateFlow — це гарячий, зконфльований (conflated) потік, який завжди тримає одне поточне значення, ідеальний для стану UI. SharedFlow — це гарячий потік без обов'язкового початкового значення, ідеальний для одноразових подій на кшталт навігації чи снекбару.
Kotlin Flow проти StateFlow проти SharedFlow: ключові відмінності
StateFlow і SharedFlow є спеціалізаціями SharedFlow, який сам по собі є гарячим Flow. Інтерв'юери зазвичай перевіряють різницю між холодним і гарячим, чи зберігається поточне значення та як кожен тип поводиться з повторюваними емісіями. Стисла ментальна модель: Flow — це рецепт, StateFlow — одне мутабельне значення, а SharedFlow — шина подій.
| Властивість | Flow (холодний) | StateFlow | SharedFlow |
|---|---|---|---|
| Температура | Холодний | Гарячий | Гарячий |
| Початкове значення | Немає | Обов'язкове | Опційне (через replay) |
| Тримає поточне значення | Ні | Так, через .value | Ні |
| Емітить дублікати | Так | Ні (згортає + дедуплікує) | Налаштовується |
| Додаткові колектори | Щоразу новий потік | Спільний | Спільний |
| Найкраще підходить для | Асинхронних конвеєрів даних | Стану UI | Одноразових подій |
Офіційна документація Kotlin coroutines розглядає цю холодну за замовчуванням поведінку як визначальну властивість Flow, тож сильна відповідь починається саме звідти.
Чому Kotlin Flow за замовчуванням холодний?
Холодний потік нічого не робить, доки не викликано collect(), і повторно виконує свій блок-продюсер для кожного колектора. Два колектори на тому самому холодному потоці спричиняють два незалежні мережеві виклики. Це найчастіше перевірювана концепція, тож короткий приклад закріплює відповідь.
fun searchResults(query: String): Flow<List<Result>> = 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
}Оскільки продюсер перезапускається для кожного колектора, холодні потоки — правильний вибір за замовчуванням для конвеєрів даних, які мають виконуватися на вимогу. Вони стають проблемою лише тоді, коли значення потрібно розділити між усім екраном, і саме тут у гру вступають StateFlow і SharedFlow.
Питання про StateFlow: стан, який завжди емітить
StateFlow — це гарячий потік, що завжди має значення й відтворює це останнє значення для кожного нового колектора. Він вимагає початкового значення, надає .value для синхронного читання і згортає емісії: швидкі послідовні оновлення можуть пропускати проміжні значення, а встановлення того самого значення двічі не емітить нічого, бо він дедуплікує за рівністю. Це робить його сучасною заміною LiveData у ViewModel.
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>(SearchUiState.Idle)
val uiState: StateFlow<SearchUiState> = _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) }
}
}
}Поширене продовження: навіщо надавати asStateFlow() замість мутабельного поля? Воно передає UI лише для читання, тож стан може змінюватися тільки через ViewModel, що зберігає однонаправлений потік даних недоторканим. Власний гайд Android про StateFlow і SharedFlow рекомендує саме цей патерн.
SharedFlow проти StateFlow: вибір правильного типу для подій
Класичне питання-пастка: «чи може StateFlow доставляти одноразові події?» Чесна відповідь — ні, принаймні не безпечно. Оскільки StateFlow згортає й дедуплікує, подія навігації може загубитися під час швидких оновлень, а ще вона повторно спрацьовує під час зміни конфігурації, бо новий колектор відтворює збережене значення. SharedFlow із replay = 0 виправляє обидві проблеми: нічого не зберігається, і кожна емісія досягає лише тих колекторів, що активні на момент емісії.
class CheckoutViewModel : ViewModel() {
// replay = 0: a late collector must NOT re-receive a past navigation event.
private val _events = MutableSharedFlow<CheckoutEvent>(replay = 0)
val events: SharedFlow<CheckoutEvent> = _events.asSharedFlow()
fun onPaymentConfirmed() {
viewModelScope.launch {
// emit() suspends if the buffer is full; tryEmit() is the non-suspending variant.
_events.emit(CheckoutEvent.NavigateToReceipt)
}
}
}Для питань про архітектуру глибша логіка розділення стану й подій розкривається в порівнянні MVVM проти MVI, де MVI трактує події як явний потік, а не як мутабельний стан.
Перетворення холодного Flow на гарячий StateFlow за допомогою stateIn
Інтерв'юери часто питають, як перетворити холодний потік репозиторію на стан UI. Відповідь — stateIn (для одного збереженого значення) або shareIn (для трансляції без поточного значення). Параметр SharingStarted контролює, коли upstream активний, а WhileSubscribed(5_000) є стандартним вибором, бо він тримає потік живим під час повороту екрана, але не витікає, коли екран закривається.
val profile: StateFlow<Profile> = 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
)Різниця між stateIn і shareIn — це рівно та сама різниця, що й між StateFlow і SharedFlow: stateIn потребує initialValue і тримає стан, тоді як shareIn приймає кількість replay і транслює. Огляд ширшої моделі корутин у цьому гайді з опанування Kotlin coroutines допомагає пов'язати ці оператори зі скоупами й скасуванням.
Готовий до співбесід з Android?
Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.
Безпечне збирання потоків у життєвому циклі Android
Питання рівня senior — як збирати потік без витоку роботи, поки екран у фоні. Збирання всередині звичайного launch продовжує працювати, коли UI зупинено, марно витрачаючи CPU й ризикуючи падіннями. repeatOnLifecycle(STARTED) скасовує збирання на STOP і перезапускає його на START.
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) }
}
}У Jetpack Compose еквівалентом є collectAsStateWithLifecycle(), який припиняє збирання, коли застосунок іде у фон, і відновлює його при поверненні.
@Composable
fun ProfileScreen(viewModel: ProfileViewModel) {
// collectAsStateWithLifecycle stops collecting when the app is backgrounded.
val state by viewModel.uiState.collectAsStateWithLifecycle()
ProfileContent(state)
}Саме ця обізнаність із життєвим циклом робить StateFlow у парі з collectAsStateWithLifecycle патерном стану за замовчуванням у сучасних Compose-застосунках — момент, який детальніше розкрито в цих питаннях на співбесіді з Jetpack Compose.
Як тестувати емісії StateFlow і SharedFlow
Тестування — саме те, чим senior-кандидати вирізняються. Наївний підхід читає stateFlow.value один раз, але це пропускає проміжні стани й узагалі не може спостерігати за SharedFlow. Стандартна відповідь називає бібліотеку Turbine, яка призупиняється, доки надійде кожна емісія, і провалює тест, якщо очікуване значення так і не приходить.
@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()
}
}Поєднання Turbine з runTest і тестовим диспетчером тримає асерти детермінованими, а згадка про StandardTestDispatcher проти UnconfinedTestDispatcher сигналізує про справжній досвід тестування корутин.
Поширені додаткові питання про Kotlin Flow
Інтерв'юери завершують швидкими перевірками. Що робить SharingStarted.WhileSubscribed(5_000)? Він запускає upstream на першому підписнику й зупиняє його через 5 секунд після відписки останнього — цього достатньо, щоб пережити поворот екрана. Чому StateFlow пропускає повторювані значення? Він порівнює через equals(), тож емісія рівного значення нічого не робить, і саме тому data-класи важливі для стану. Чи може SharedFlow поводитися як StateFlow? Встановлення replay = 1 змушує його зберігати останнє значення, але йому все одно бракує синхронного .value і він ніколи не дедуплікує. Що контролює back-pressure у SharedFlow? Параметри extraBufferCapacity та onBufferOverflow, де BufferOverflow.DROP_OLDEST є поширеним вибором для подій. Глибша практика з цими операторами є в модулі співбесід з Kotlin coroutines і Flow.
Використання StateFlow для подій навігації чи снекбару. Оскільки він відтворює своє останнє значення для нових колекторів, подія повторно спрацьовує після повороту екрана й користувача перенавігує двічі. Слід брати SharedFlow із replay = 0 або моделювати одноразову подію, яка очищається після обробки.
Вихідний код kotlinx.coroutines і його довідник пакета flow на GitHub варто переглянути перед співбесідою, оскільки KDoc для StateFlow і SharedFlow точно описує гарантії згортання й відтворення.
Висновок
Розрізнення Kotlin Flow проти StateFlow проти SharedFlow винагороджує точність формулювань на співбесіді. Ключові висновки, які варто взяти з собою:
Flowхолодний: продюсер перезапускається для кожного колектора, тож він підходить для конвеєрів даних на вимогу.StateFlowгарячий, завжди тримає значення, згортає й дедуплікує, що робить його інструментом для стану UI.SharedFlowгарячий, без обов'язкового початкового значення, що робить його правильним вибором для одноразових подій.- Перетворюйте холодне на гаряче через
stateInабоshareInі використовуйтеSharingStarted.WhileSubscribed(5_000), щоб пережити поворот екрана. - Збирайте через
repeatOnLifecycle(STARTED)абоcollectAsStateWithLifecycle(), щоб уникнути фонової роботи й витоків. - Ніколи не доставляйте події через StateFlow: його поведінка відтворення повторно спрацьовує їх після зміни конфігурації.
Починай практикувати!
Перевір свої знання з нашими симуляторами співбесід та технічними тестами.
Теги
Поділитися
Пов'язані статті

Kotlin 2.3 для Android: іменована деструктуризація, KMP та співбесідні питання 2026
Питання на співбесіду з Kotlin 2.3, що охоплюють іменовану деструктуризацію, Kotlin Multiplatform, контекстні параметри, корутини та Flow. Підготовка до співбесід Android-розробника у 2026 з реальними прикладами коду.

Room Database в Android 2026: Міграції, Зв'язки та Coroutines
Повний посібник з Room Database в Android 2026 - від міграцій схеми, через зв'язки між сутностями, до інтеграції з Kotlin Coroutines та найкращих практик.

Android 16 у 2026 році: Нові API, десктопний режим та питання на співбесідах
Глибокий аналіз Android 16 (API 36): десктопний режим, сповіщення ProgressStyle, предиктивна навігація назад, обов'язковий рендеринг від краю до краю та питання для технічних співбесід.