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

В чем разница между горячим и холодным Flow?

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

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

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

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

В чем разница между горячим и холодным Flow

Определение

Холодный Flow (cold flow) — каждый подписчик получает свою копию потока данных, начиная с начала. Горячий Flow (hot flow) — все подписчики получают один и тот же поток, независимо от того, когда они подписались.

1. Холодный Flow (Cold Flow)

Холодный flow создает новый поток для каждого подписчика. Это Flow по умолчанию в Kotlin.

fun getColdFlow(): Flow<Int> = flow {
  println("Flow started")
  emit(1)
  emit(2)
  emit(3)
  println("Flow ended")
}

launchIO {
  println("Подписчик 1:")
  getColdFlow().collect { value ->
    println("Подписчик 1 получил: $value")
  }
}

launchIO {
  println("Подписчик 2:")
  getColdFlow().collect { value ->
    println("Подписчик 2 получил: $value")
  }
}

// Вывод:
// Подписчик 1:
// Flow started
// Подписчик 1 получил: 1
// Подписчик 1 получил: 2
// Подписчик 1 получил: 3
// Flow ended
// Подписчик 2:
// Flow started  <- Новый flow для второго подписчика!
// Подписчик 2 получил: 1
// Подписчик 2 получил: 2
// Подписчик 2 получил: 3
// Flow ended

Характеристики:

  • Каждая подписка создает новый поток
  • Подписчик получает все данные с начала
  • Ленивый (lazy) — не выполняется, пока нет подписчика
  • Низкое потребление ресурсов — выполняется по требованию

2. Горячий Flow (Hot Flow)

Горячий flow существует независимо от подписчиков. Используется StateFlow или SharedFlow.

val hotFlow = MutableSharedFlow<Int>()

launchIO {
  println("Подписчик 1:")
  hotFlow.collect { value ->
    println("Подписчик 1 получил: $value")
  }
}

launchIO {
  emit(1)
  emit(2)
  emit(3)
}

delay(100)  // Ждем перед второй подпиской

launchIO {
  println("Подписчик 2:")
  hotFlow.collect { value ->
    println("Подписчик 2 получил: $value"
  }
}

// Вывод:
// Подписчик 1:
// Подписчик 1 получил: 1
// Подписчик 1 получил: 2
// Подписчик 1 получил: 3
// Подписчик 2:  <- Запоздал, не получит старые значения!

Характеристики:

  • Один поток для всех подписчиков
  • Подписчики получают только новые данные после подписки
  • Горячий (hot) — выполняется независимо от подписчиков
  • Высокое потребление ресурсов — выполняется всегда

3. StateFlow (особый случай горячего потока)

StateFlow — это горячий поток, который хранит текущее состояние и отправляет его новым подписчикам.

val stateFlow = MutableStateFlow(0)

// Новый подписчик СРАЗУ получит текущее значение!
stateFlow.collect { value ->
  println("Получено: $value")
}

stateFlow.value = 1  // Все подписчики получат обновление

Таблица сравнения:

АспектCold FlowHot Flow (SharedFlow)Hot Flow (StateFlow)
Создание потокаКаждому подписчикуОдин на всехОдин на всех
Новый подписчик получаетВсе данные с началаТолько новыеТекущее значение + новые
Потребление ресурсовНизкоеВысокоеВысокое
ПримерЗапрос к APIСобытия пользователяСостояние UI

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

Холодный Flow: Загрузка данных

class UserRepository {
  fun getUsers(): Flow<List<User>> = flow {
    val users = apiClient.fetchUsers()  // Каждый подписчик запросит API!
    emit(users)
  }
}

class UserViewModel : ViewModel() {
  private val _users = MutableStateFlow<List<User>>(emptyList())
  val users = _users.asStateFlow()
  
  fun loadUsers() {
    viewModelScope.launch {
      // Каждый раз при loadUsers() будет новый запрос
      repository.getUsers().collect { users ->
        _users.value = users
      }
    }
  }
}

Горячий Flow: События пользователя

class EventBus {
  private val _events = MutableSharedFlow<Event>()
  val events = _events.asSharedFlow()
  
  suspend fun sendEvent(event: Event) {
    _events.emit(event)  // Отправляется всем подписчикам
  }
}

class EventFragment : Fragment() {
  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    viewLifecycleOwner.lifecycleScope.launch {
      eventBus.events.collect { event ->
        handleEvent(event)
      }
    }
  }
}

StateFlow: Состояние приложения

class AuthViewModel : ViewModel() {
  private val _authState = MutableStateFlow<AuthState>(AuthState.Idle)
  val authState = _authState.asStateFlow()  // Читаемый StateFlow
  
  fun login(email: String, password: String) {
    viewModelScope.launch {
      _authState.value = AuthState.Loading
      try {
        val user = apiClient.login(email, password)
        _authState.value = AuthState.Success(user)
      } catch (e: Exception) {
        _authState.value = AuthState.Error(e.message ?: "Unknown error")
      }
    }
  }
}

class LoginFragment : Fragment() {
  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    viewLifecycleOwner.lifecycleScope.launch {
      viewModel.authState.collect { state ->
        when (state) {
          AuthState.Idle -> {}
          AuthState.Loading -> showProgressBar()
          is AuthState.Success -> navigateToHome()
          is AuthState.Error -> showError(state.message)
        }
      }
    }
  }
}

5. Когда использовать

Используй холодный Flow когда:

  • Запрос к API или базе данных
  • Трансформация данных
  • Каждый подписчик должен получить полный результат
  • Нужно экономить ресурсы (lazy evaluation)
fun getFilteredUsers(searchQuery: String): Flow<List<User>> = flow {
  val users = repository.getAllUsers()
  val filtered = users.filter { it.name.contains(searchQuery) }
  emit(filtered)
}

Используй горячий Flow когда:

  • Буду работать с событиями (клики, навигация)
  • Несколько подписчиков должны получить одно событие
  • Потребление ресурсов не критично
val navigationEvents = MutableSharedFlow<NavigationEvent>()

button.setOnClickListener {
  viewModelScope.launch {
    navigationEvents.emit(NavigationEvent.OpenProfile)
  }
}

Используй StateFlow когда:

  • Хранишь состояние UI
  • Новому подписчику нужно текущее значение
  • Нужен один источник истины для состояния
val uiState = MutableStateFlow(UiState())
val currentState get() = uiState.value

6. Оптимизация: shareIn() для превращения холодного в горячий

class CachedUserRepository {
  private val apiFlow = flow {
    emit(apiClient.getUsers())
  }.shareIn(  // Преобразуем холодный в горячий
    scope = viewModelScope,
    started = SharingStarted.Lazily,  // Начать при первой подписке
    replay = 1  // Сохранить последний результат
  )
  
  fun getUsers(): Flow<List<User>> = apiFlow
}

// Теперь несколько подписчиков поделят один запрос к API!

Итог

Холодный Flow:

  • Каждый подписчик = новый поток
  • Получает все данные с начала
  • Ленивый, экономный
  • По умолчанию в Kotlin

Горячий Flow (SharedFlow/StateFlow):

  • Один поток на всех
  • Только новые данные
  • Горячий, всегда работает
  • Используй для событий и состояния

StateFlow особенный:

  • Горячий поток с памятью
  • Новый подписчик получит текущее значение
  • Лучший выбор для UI состояния