← Назад к вопросам

Какие знаешь горячие Flow в Coroutines?

2.3 Middle🔥 171 комментариев
#Многопоточность и асинхронность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Горячие Flow в Kotlin Coroutines

Горячие потоки (Hot Flows) — это потоки данных, которые эмитят значения независимо от наличия подписчиков. В отличие от холодных Flow, горячие потоки работают всегда и переиспользуют данные между разными подписчиками.

Cold vs Hot Flow

Cold Flow — каждый подписчик получает свою копию данных:

val flow = flow {
    emit(1)
    emit(2)
    emit(3)
}

flow.collect { println("Collector1: $it") }
// Output: 1, 2, 3

flow.collect { println("Collector2: $it") }
// Output: 1, 2, 3 (ещё раз! Функция flow выполнилась дважды)

Hot Flow — данные эмитят один раз для всех подписчиков:

val hotFlow = MutableSharedFlow<Int>()

launch {
    hotFlow.emit(1)  // Один раз!
    hotFlow.emit(2)
    hotFlow.emit(3)
}

launch { hotFlow.collect { println("Collector1: $it") } }
launch { hotFlow.collect { println("Collector2: $it") } }
// Оба получат 2, 3 (1 пропущена — они подписались поздно)

1. StateFlow — Состояние UI

StateFlow хранит одно состояние и эмитит его всем новым подписчикам.

class UserViewModel : ViewModel() {
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()
    
    fun loadUsers() {
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            _uiState.value = UiState.Success(users)
        }
    }
}

Ключевые свойства:

  • Всегда имеет значение (initial state)
  • Новые подписчики получают текущее значение
  • Не эмитит дубли (value == old)
  • Thread-safe
  • Идеален для MVVM UI состояния

2. SharedFlow — События

SharedFlow эмитит события без сохранения состояния.

class EventViewModel : ViewModel() {
    private val _events = MutableSharedFlow<Event>()
    val events: SharedFlow<Event> = _events.asSharedFlow()
    
    fun deleteUser(id: Int) {
        viewModelScope.launch {
            repository.deleteUser(id)
            _events.emit(Event.UserDeleted(id))
        }
    }
}

class UserFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewLifecycleOwner.lifecycleScope.launch {
            viewModel.events.collect { event ->
                when (event) {
                    is Event.UserDeleted -> navController.popBackStack()
                }
            }
        }
    }
}

Параметры:

  • replay: Сколько прошлых значений переиспользовать
  • extraBufferCapacity: Буфер для переполнения
  • onBufferOverflow: Поведение при переполнении

3. shareIn() — Преобразование Flow в горячий

Обычный cold Flow можно преобразовать в горячий:

val locationFlow: Flow<Location> = flow {
    while (true) {
        emit(locationService.getCurrentLocation())
        delay(1000)
    }
}

val sharedLocation: SharedFlow<Location> = locationFlow
    .shareIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        replay = 1
    )

// Несколько подписчиков переиспользуют один поток
launch { sharedLocation.collect { updateMap(it) } }
launch { sharedLocation.collect { updateUI(it) } }

4. stateIn() — StateFlow из Flow

Преобразуй Flow в StateFlow:

val userFlow: Flow<User> = userRepository.observeUser(userId)

val userState: StateFlow<User?> = userFlow
    .stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(),
        initialValue = null
    )

val currentUser: User? = userState.value  // Быстрый доступ

Сравнение StateFlow, SharedFlow, Flow

СвойствоStateFlowSharedFlowFlow
Горячий/ХолодныйГорячийГорячийХолодный
Хранит состояниеДа (1 значение)НетНет
Новый подписчик получаетТекущее значениеReplayed значенияНичего
Удаляет дублиДаНетНет
ИспользованиеUI состояниеСобытияAPI запросы

Практические примеры

Поиск пользователей

class SearchViewModel(repo: UserRepository) : ViewModel() {
    private val _searchQuery = MutableStateFlow("")
    
    val searchResults: StateFlow<List<User>> = _searchQuery
        .debounce(300)
        .flatMapLatest { query ->
            if (query.isEmpty()) flowOf(emptyList())
            else repo.searchUsers(query)
        }
        .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
    
    fun updateQuery(query: String) {
        _searchQuery.value = query
    }
}

Навигационные события

class LoginViewModel : ViewModel() {
    private val _navigationEvents = MutableSharedFlow<NavigationEvent>()
    val navigationEvents: SharedFlow<NavigationEvent> = _navigationEvents
    
    fun login(email: String, password: String) {
        viewModelScope.launch {
            try {
                authService.login(email, password)
                _navigationEvents.emit(NavigationEvent.GoToHome)
            } catch (e: Exception) {
                _navigationEvents.emit(NavigationEvent.ShowError(e.message))
            }
        }
    }
}

Best Practices

  1. StateFlow для UI состояния
  2. SharedFlow для событий
  3. Всегда делай flows read-only
  4. Используй viewModelScope для автоматической очистки
  5. Обрабатывай ошибки в flow
  6. Используй debounce() для поиска
  7. Используй distinctUntilChanged() чтобы не повторяться

Вывод: Горячие потоки — это эволюция LiveData. StateFlow для состояния, SharedFlow для событий, они дают полный контроль над потоком данных в Android приложении.