Kotlin Flows — это мощный инструмент для создания реактивных и асинхронных приложений. С помощью потоков вы можете легко обрабатывать потоки данных, которые могут потребляться одним или несколькими сборщиками. Когда нескольким сборщикам нужно использовать один и тот же поток, важно использовать правильную функцию для разделения потока между ними. В этом посте мы сравним shareIn и stateIn, две функции в Kotlin Flows, которые позволяют вам разделить поток между несколькими сборщиками.

Совет. Если вам нужно понять SharedFlow и StateFlow и то, как они работают, пост ниже может быть вам полезен.



Понимание stateIn

stateIn — это функция Kotlin Flow, которая позволяет нам создать общий поток с отслеживанием состояния, то есть он кэширует последнее сгенерированное значение и немедленно предоставляет его новым сборщикам. Это особенно полезно для управления и обмена состояниями в разных частях вашего приложения, например, при разработке пользовательского интерфейса, когда нескольким компонентам может потребоваться доступ к одним и тем же данным.

Функция stateIn создает новый горячий поток с указанным начальным значением и определенным поведением совместного использования. Он возвращает объект StateFlow, тип горячего потока, который имеет текущее значение, доступ к которому можно получить синхронно с помощью свойства value. Это делает его очень полезным, когда вам нужно поделиться потоком, который имеет текущее значение, которое может быть обновлено вышестоящим источником.

Вот как работает stateIn:

fun <T> MutableStateFlow<T>.stateIn(
    scope: CoroutineScope,
    started: SharingStarted = SharingStarted.WhileSubscribed(),
    initialValue: T
): StateFlow<T>
  • scope — это область действия сопрограммы, которую должен использовать общий поток.
  • started — это поведение совместного использования, которое определяет, когда поток должен запускаться и прекращать передачу значений. Доступно несколько вариантов, например WhileSubscribed, Eagerly и Lazily.
  • initialValue — это начальное значение StateFlow.

При использовании stateIn вам необходимо предоставить область сопрограммы, в которой должно происходить совместное использование состояния, стратегию совместного использования, которая определяет, когда и как поток начинает совместно использовать свои данные, и начальное состояние, которое генерируется, когда значение еще не создано.

Понимание стратегий обмена для StateFlow

Вы можете использовать три разные стратегии обмена для StateFlow :

  1. SharingStarted.Lazily: Поток начинает обмен данными, когда первый сборщик начинает сбор, и останавливается, когда останавливается последний сборщик. Это полезно, когда вы хотите поделиться состоянием только при наличии активного коллектора.
  2. SharingStarted.WhileSubscribed: Поток начинает обмениваться данными, когда первый сборщик начинает сбор, и останавливается по истечении заданного периода бездействия (т. е. когда нет активных сборщиков). Это полезно, когда вы хотите поделиться состоянием во время его использования, но остановить через некоторое время, когда оно больше не нужно.
  3. SharingStarted.Eagerly: Поток начинает обмен данными немедленно и продолжает делиться ими бесконечно, независимо от того, есть ли какие-либо активные сборщики. Это полезно, когда вы хотите постоянно делиться состоянием.

Как использовать `stateIn`

Чтобы использовать stateIn в своем приложении для Android, вы можете просто связать его со своим потоком и передать область сопрограммы, где должен происходить обмен, стратегию обмена и начальное состояние. Вот пример использования stateIn для создания общего StateFlow, который выдает случайное число каждую секунду:

val randomNumbers = MutableStateFlow(0)

val sharedFlow = randomNumbers.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(),
    initialValue = 0
)

fun generateRandomNumbers() {
    viewModelScope.launch {
        while (true) {
            delay(1000)
            randomNumbers.value = (0..100).random()
        }
    }
}

В этом примере мы создаем новый MutableStateFlow с именем randomNumbers с начальным значением 0. Затем мы используем stateIn для создания общего объекта StateFlow с именем sharedFlow, который использует один и тот же поток randomNumbers. Мы указываем, что обмен должен начаться, пока есть подписчики на поток, и мы используем viewModelScope в качестве области сопрограммы.

Объект StateFlow, возвращаемый stateIn, может собираться несколькими сборщиками, и каждый сборщик будет получать одни и те же обновления из вышестоящего источника. Когда значение randomNumbers изменится, все сборщики sharedFlow немедленно получат обновленное значение.

Теперь, когда у нас есть общий StateFlow, мы можем создать несколько коллекторов, которые будут получать одни и те же выбросы. Мы также можем обновить значение переменной counter, и все сборщики получат обновленное значение. Например:

sharedFlow.collect {
    Log.d("SharedFlow", it)
}

sharedFlow.collect {
    textView.text = it.toString()
}

generateRandomNumbers()

В этом примере мы создаем два коллектора, которые будут получать одинаковые выбросы от sharedFlow. Первый сборщик записывает каждую эмиссию в консоль, а второй сборщик устанавливает текст TextView для каждой эмиссии. Мы также вызываем функцию generateRandomNumbers(), которая обновляет значение переменной randomNumber и запускает новые выбросы.

Понимание shareIn

shareIn — это функция Kotlin Flow, которая позволяет нам делиться одним потоком данных между несколькими сборщиками. Проще говоря, это позволяет «совместно использовать» поток, чтобы несколько частей вашего приложения могли получать одни и те же выбросы данных, при этом поток не выполнял работу несколько раз. Это повышает производительность и использование ресурсов в вашем приложении. Это может быть полезно, например, когда нам нужно отобразить одни и те же данные в разных частях пользовательского интерфейса или когда мы хотим выполнить несколько операций с одним и тем же потоком данных.

shareIn создает новый горячий поток с указанным поведением совместного использования. Он возвращает объект SharedFlow, который является типом горячего потока, который может иметь несколько подписчиков и делиться выбросами со всеми из них.

Эта функция полезна, когда вам нужно поделиться потоком, который не имеет текущего значения и не нуждается в нем.

Вот как работает shareIn:

fun <T> Flow<T>.shareIn(
    scope: CoroutineScope,
    started: SharingStarted = SharingStarted.WhileSubscribed(),
    replay: Int = 0
): SharedFlow<T>
  • scope — это область сопрограммы, которую должен использовать общий поток.
  • started — это поведение совместного использования, которое определяет, когда поток должен запускаться и прекращать передачу значений. Доступно несколько вариантов, например WhileSubscribed, Eagerly и Lazily.
  • replay — это количество предыдущих выпусков, которые нужно проигрывать новым подписчикам. Это полезно, когда мы хотим предоставить новым подписчикам определенное количество прошлых выпусков.

Понимание стратегий совместного использования для SharedFlow

Вы можете использовать три разные стратегии обмена для SharedFlow :

  1. SharingStarted.Lazily: Поток начинает совместное использование данных, когда первый сборщик начинает сбор, и останавливается, когда останавливается последний сборщик. Это полезно, когда вы хотите поделиться данными потока только при наличии активного коллектора.
  2. SharingStarted.WhileSubscribed: Поток начинает обмен данными, когда первый сборщик начинает сбор, и останавливается после определенного периода бездействия (т. е. когда нет активных сборщиков). Это полезно, когда вы хотите поделиться данными потока во время его использования, но остановить через некоторое время, когда он больше не нужен.
  3. SharingStarted.Eagerly: Поток немедленно начинает обмен данными и продолжает делиться ими бесконечно, независимо от того, есть ли какие-либо активные сборщики. Это полезно, когда вы хотите постоянно делиться данными потока.

Как использовать `shareIn`

Чтобы использовать shareIn в своем приложении, вы можете просто связать его со своим потоком и передать область сопрограммы, в которой должен происходить обмен, вместе со стратегией обмена:

val sharedFlow = originalFlow.shareIn(
  scope = viewModelScope, 
  started = SharingStarted.Lazily
)

Когда несколько сборщиков собирают данные из sharedFlow, все они получают одни и те же данные, а работа внутри потока выполняется только один раз, что повышает производительность вашего приложения.

Вот пример использования shareIn для создания общего SharedFlow, который выдает случайное число каждую секунду:

val sharedFlow = flow {
    while (true) {
        delay(1000)
        emit((0..100).random())
    }
}.shareIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed()
)

В этом примере мы используем конструктор flow для создания нового потока, который выдает случайное число каждую секунду. Затем мы используем shareIn для создания общего объекта SharedFlow с именем sharedFlow, который использует тот же поток. Мы указываем, что обмен должен начаться, пока есть подписчики на поток, и мы используем viewModelScope в качестве области действия сопрограммы.

Объект SharedFlow, возвращаемый shareIn, также может собираться несколькими сборщиками, и каждый сборщик будет получать одни и те же обновления из вышестоящего источника. Когда поток выдает новое значение, все сборщики sharedFlow немедленно получат обновленное значение.

Теперь, когда у нас есть общий поток, мы можем создать несколько коллекторов, которые будут получать одни и те же выбросы. Например:

sharedFlow.collect {
    Log.d("SharedFlow", it)
}

sharedFlow.collect {
    textView.text = it
}

В этом примере мы создаем два коллектора, которые будут получать одинаковые выбросы от sharedFlow. Первый сборщик записывает каждую эмиссию в консоль, а второй сборщик устанавливает текст TextView для каждой эмиссии.

Когда использовать `shareIn` и `stateIn`

Теперь, когда мы рассмотрели основы shareIn и stateIn, давайте обсудим, когда следует использовать каждый из них.

Используйте shareIn, если вы хотите поделиться потоком, который не имеет текущего значения или не нуждается в нем. Это полезно, когда вы хотите поделиться потоком, который выдает значения только тогда, когда на него подписаны. Например, если у вас есть поток, который прослушивает пользовательский ввод, вы можете использовать shareIn, чтобы разделить этот поток между различными частями вашего приложения, которые должны реагировать на этот ввод.

Вот несколько ситуаций, в которых shareIn может быть полезен:

  • Когда нам нужно отобразить одни и те же данные в разных частях пользовательского интерфейса.
  • Когда мы хотим выполнить несколько операций с одним и тем же потоком данных.
  • Когда мы хотим избежать дублирования одного и того же экземпляра Flow в нескольких местах.

Используйте stateIn, когда вам нужно поделиться потоком, который имеет текущее значение, и это значение может быть обновлено вышестоящим источником. Это полезно, когда вы хотите поделиться потоком, представляющим состояние вашего приложения, например статус проверки подлинности пользователя или содержимое его корзины покупок. Вы можете использовать stateIn для создания общего потока, который передает текущее состояние вашего приложения и может обновляться различными частями вашего приложения.

Вот несколько ситуаций, в которых stateIn может быть полезен:

  • Когда нам нужно создать поток с текущим значением, которое может быть обновлено вышестоящим источником.
  • Когда мы хотим разделить поток с текущим значением между несколькими коллекторами.
  • Когда мы хотим предоставить начальное значение для общего потока.

Стоит отметить, что stateIn создан поверх shareIn, поэтому технически возможно использовать shareIn для совместного использования MutableStateFlow. Однако использование stateIn более лаконично и лучше читается, когда вы имеете дело с состоянием.

Заключение

В этом посте мы рассмотрели основы shareIn и stateIn в Kotlin Flows. Мы видели, как shareIn используется для создания общего потока, у которого нет текущего значения, и как stateIn используется для создания общего потока, у которого есть текущее значение. Мы также видели несколько примеров использования каждой функции.

Понимая эти две функции, вы можете создавать в своем приложении более эффективный и пригодный для повторного использования код. Вы можете использовать shareIn, чтобы делиться потоками, которые выдают значения только тогда, когда на них подписаны, и вы можете использовать stateIn, чтобы делиться потоками, которые представляют состояние вашего приложения.