Flow

https://developer.android.com/kotlin/flow
https://developer.android.com/kotlin/flow/stateflow-and-sharedflow
https://kotlinlang.org/docs/flow.html
Flow Marbles: Interactive diagram of Kotlin Flow
Flow

Асинхронный поток данных, который может эмитировать значения в корутинах. Он позволяет работать с последовательностями данных, предоставляя API для обработки событий, которые могут происходить со временем. Flow поддерживает операторы, такие как map, filter, и collect, что позволяет трансформировать и обрабатывать данные. Он также управляет потоком, обеспечивая безопасное выполнение в многопоточной среде.

SharedFlow

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

MutableSharedFlow

Позволяет эмиттировать значения в поток, делая его изменяемым.

class MyViewModel: ViewModel() {

    private val _eventFlow = MutableSharedFlow<String>(replay = 1)
    val eventFlow = _eventFlow.asSharedFlow()

    fun sendEvent(event: String) {
        viewModelScope.launch {
            _eventFlow.emit(event) // Эмиттируем событие
        }
    }
}
replay

Определяет количество последних значений, которые будут сохраняться и отправляться новым подписчикам при их подключении. Например, если replay = 1, каждый новый подписчик при подключении сразу получает последнее значение, даже если его эмиссия произошла до подписки. Это полезно для доставки критически важной информации, которая должна быть доступна всем подписчикам.

val sharedFlow = MutableSharedFlow<Int>(
    replay = 1 // Хранит последнее значение для новых подписчиков
)
extraBufferCapacity

Указывает, сколько дополнительных значений можно отправить в MutableSharedFlow, пока они ожидают получения подписчиками. Этот параметр добавляет буфер к значению replay, позволяя временно хранить больше значений, если поток данных быстрее, чем подписчики могут их обрабатывать. Например, если replay = 1 и extraBufferCapacity = 2, в буфере могут храниться три значения, что помогает избежать блокировки эмиттера при быстром потоке данных.

val sharedFlow = MutableSharedFlow<Int>(
    extraBufferCapacity = 2 // Дополнительно хранит до 2 значений в буфере
)
onBufferOverflow

Определяет, что делать при переполнении буфера.

val sharedFlow = MutableSharedFlow<Int>(
    onBufferOverflow = BufferOverflow.DROP_OLDEST // Удаляет самое старое значение при переполнении
)
BufferOverflow.SUSPEND

Приостанавливает эмиттер, пока в буфере не освободится место.

BufferOverflow.DROP_OLDEST

Удаляет самое старое значение в буфере, чтобы освободить место для нового.

BufferOverflow.DROP_LATEST

Отбрасывает новое значение, не добавляя его в буфер, если буфер заполнен.

asSharedFlow

Преобразует MutableSharedFlow в неизменяемый SharedFlow, предоставляя доступ только для подписки без возможности эмиссии значений.

private val _eventFlow: MutableSharedFlow<String> = MutableSharedFlow<String>()
val eventFlow: SharedFlow<String> = _eventFlow.asSharedFlow()
shareIn

Преобразует Flow в SharedFlow, делая его горячим и позволяя вещать значения одновременно нескольким подписчикам.

val sharedFlow: SharedFlow<Int> = flow
    .shareIn(
        scope = viewModelScope,
        started = SharingStarted.Lazily,
        replay = 0
    )
scope

Определяет CoroutineScope, в котором будет активен SharedFlow. Этот параметр необходим для контроля жизненного цикла потока: SharedFlow начнет испускать значения при активации scope и прекратит работу, когда scope завершится. В Android обычно используется viewModelScope, чтобы поток был активен в течение жизни ViewModel.

started

Определяет, когда поток начинает испускать значения.

SharingStarted.Eagerly

Немедленное начало эмиссии, независимо от подписчиков.

SharingStarted.Lazily

Эмиссия начнется только после появления первого подписчика.

SharingStarted.WhileSubscribed(replayExpirationMillis)

Поток начнет эмиссию при появлении подписчиков и остановится после указанного времени (replayExpirationMillis), если все подписчики отписались. Это значение полезно для контроля ресурсов, так как поток приостанавливается, когда нет активных подписчиков.

replay

Указывает количество последних значений, которые сохраняются и передаются новым подписчикам, даже если они подключились после эмиссии. Например, если replay = 1, новые подписчики сразу получают последнее значение. Этот параметр удобен, когда важные данные должны быть доступны новым подписчикам, независимо от момента их подключения.

tryEmit

Используется для попытки отправки значения в SharedFlow без блокировки. Возвращает true, если значение успешно отправлено, или false, если подписчик не может принять значение (например, когда поток завершен). Это позволяет избежать выброса исключений при переполнении буфера.

val flow = MutableSharedFlow<String>()

val success = flow.tryEmit("Hello") // Вернет true, если подписчик активен

flow.collect { value ->
    println(value)
}
StateFlow

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

MutableStateFlow

Позволяет обновлять текущее значение состояния.

class MyViewModel: ViewModel() {
    private val _stateFlow = MutableStateFlow("Начальное состояние")
    val stateFlow: StateFlow<String> = _stateFlow
    
    fun updateState(newState: String) {
        _stateFlow.value = newState
    }
}
value

Позволяет получить или установить текущее значение состояния.

compareAndSet

Используется для атомарного обновления значения. Он сравнивает текущее значение с ожидаемым, и если они совпадают, то устанавливает новое значение. Этот метод возвращает true, если обновление произошло успешно, и false — если текущее значение отличается от ожидаемого.

class MyViewModel: ViewModel() {
    private val _stateFlow = MutableStateFlow(0)
    val stateFlow: StateFlow<Int> = _stateFlow

    // Увеличивает значение, если текущее значение совпадает с ожидаемым
    fun incrementIfExpected(expected: Int) {
        _stateFlow.compareAndSet(expected, expected + 1)
    }
}
updateAndGet

Способ атомарного обновления значения с использованием переданной лямбды и получения нового значения после обновления. Он берет текущее значение, применяет к нему функцию-обработчик и устанавливает результат в MutableStateFlow, возвращая новое значение.

class MyViewModel: ViewModel() {
    private val _clickCount = MutableStateFlow(0)
    val clickCount: StateFlow<Int> = _clickCount

    // Увеличивает счетчик кликов и возвращает обновленное значение
    fun incrementAndGetClickCount(): Int {
        return _clickCount.updateAndGet { current -> current + 1 }
    }
}
getAndUpdate

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

class MyViewModel: ViewModel() {
    private val _clickCount = MutableStateFlow(0)
    val clickCount: StateFlow<Int> = _clickCount

    // Увеличивает счетчик и возвращает старое значение
    fun incrementAndGetOldClickCount(): Int {
        return _clickCount.getAndUpdate { current -> current + 1 }
    }
}
update

Атомарный способ изменить текущее значение MutableStateFlow с использованием переданной функции. В отличие от методов, которые возвращают старое или новое значение, update просто применяет функцию обновления к текущему значению и устанавливает его в MutableStateFlow, уведомляя подписчиков только если значение изменилось.

class MyViewModel: ViewModel() {
    private val _clickCount = MutableStateFlow(0)
    val clickCount: StateFlow<Int> = _clickCount

    // Увеличивает счетчик кликов
    fun incrementClickCount() {
        _clickCount.update { current -> current + 1 }
    }
}
asStateFlow

Преобразует изменяемый MutableStateFlow в неизменяемый StateFlow.

private val _stateFlow: MutableStateFlow<String> = MutableStateFlow("Начальное состояние")
val stateFlow: StateFlow<String> = _stateFlow.asStateFlow()
stateIn

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

val stateFlow: StateFlow<Int> = flow
    .stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5_000),
        initialValue = 0
    )
initialValue

Начальное значение, которое StateFlow будет содержать до того, как Flow начнет испускать данные. Это значение доступно сразу, даже если данные еще не поступили.

FlowCollector
emit

Используется для отправки значений из Flow в его подписчиков. Является suspend-функцией.

val flow = flow {
    emit("Hello")
    emit("World")
}

flow.collect { value ->
    println(value) // Вывод: Hello, World
}
Builders
flow

Создает асинхронный поток данных.

val numbersFlow: Flow<Int> = flow {
    for (i in 1..3) {
        delay(1000)
        emit(i)
    }
}
asFlow

Преобразует коллекции и последовательности в Flow.

val list = listOf(1, 2, 3)
val flowOfList: Flow<Int> = list.asFlow()
flowOf

Cоздает Flow из одного или нескольких значений.

val numberFlow: Flow<Int> = flowOf(1, 2, 3, 5, 8)
channelFlow

Позволяет создать Flow, используя каналы.

val flow: Flow<String> = channelFlow {
    send("Hello")
    send("World")
}
callbackFlow

Создает Flow, который работает с коллбэками. Он оборачивает асинхронные вызовы, позволяя отправлять значения в поток, когда происходят определенные события.

val flow: Flow<Int> = callbackFlow {
    val listener = object: MyListener {
        override fun onValue(value: Int) {
            trySend(value) // Отправляет значение в поток
        }
    }
    registerListener(listener) // Регистрация слушателя
    awaitClose { unregisterListener(listener) } // Удаление слушателя при закрытии
}
Context Operators
buffer

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

val flow = flowOf(1, 2, 3)

flow
    .buffer()  // Используем буфер
    .collect { value ->
        delay(100)  // Эмулируем длительную обработку
        println(value)
    }
// Выведет:
// 1
// 2
// 3
conflate
https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/conflate.html

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

flowOf(1, 2, 3, 4, 5)
    .conflate()  // Пропускает промежуточные значения
    .collect { value -> 
        delay(100)  // Задержка для имитации обработки
        println(value)
    }
// 1
// 3
// 5
flowOn

Изменяет контекст выполнения Flow. Используется для переключения на другой CoroutineDispatcher. Например, можно использовать flowOn для выполнения операций, которые требуют более длительного времени, в фоновом потоке, а затем возвращаться к основному потоку для сбора значений.

flow {
    emit("Start")
    delay(1000) // Долгая операция
    emit("End")
}
.flowOn(Dispatchers.IO) // Переключаем на фоновый поток
.collect { value ->
    println(value) // Собираем значения
}
Delay Operators
debounce

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

val flow = flowOf(1, 2, 3, 4, 5)

flow
    .debounce(100)
    .collect { println(it) }
// Выведет последнее значение после паузы в 100 мс между элементами.
sample

Позволяет получать последнее значение потока через определённые интервалы времени.

val flow = flowOf(1, 2, 3, 4, 5)

flow
    .sample(100)
    .collect { println(it) }
// Выведет последнее значение, которое поступило за каждый 100 мс.
Distinct Operators
distinctUntilChanged

Пропускает только уникальные последовательные значения, игнорируя дубликаты.

flowOf(1, 2, 2, 3, 3, 3, 4)
    .distinctUntilChanged()
    .collect { println(it) } // Результат: 1 2 3 4
Emitters Operators
transform

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

val flow = flowOf(1, 2, 3)

flow.transform { value ->
    emit(value * 2) // Эмитирует удвоенное значение
    emit(value + 1) // Эмитирует значение, увеличенное на 1
}.collect { println(it) }
// Вывод:
// 2
// 2
// 4
// 3
// 6
// 4
onStart

Выполняет заданное действие перед началом сбора данных из потока.

flowOf(1, 2, 3)
    .onStart { emit(0) } // Добавляет 0 перед остальными элементами
    .collect { println(it) } // Вывод: 0, 1, 2, 3
onCompletion

Выполняет действие, когда поток завершается, независимо от того, завершился он успешно или из-за исключения.

flowOf(1, 2, 3)
    .onCompletion { println("Completed") }
    .collect { println(it) } // Вывод: 1, 2, 3, Completed
onEmpty

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

val flow = emptyFlow<Int>()

flow
    .onEmpty { println("Поток пустой") } // Действие при пустом потоке
    .collect { value -> println(value) } // Поток пустой
Errors Operators
catch

Перехватывает ошибки в Flow, позволяя продолжить выполнение после их обработки.

flow {
    emit("Loading data")
    throw Exception("Error")
}.catch { e ->
    emit("Error: ${e.message}")
}.collect { value ->
    println(value)
}
Limit Operators
drop

Пропускает первые N элементов потока и эмитирует оставшиеся.

val flow = flowOf(1, 2, 3, 4, 5)

val result = flow.drop(3)
result.collect { println(it) }
// 4
// 5
dropWhile

Пропускает элементы потока, пока условие не станет ложным, и эмитирует оставшиеся.

val flow = flowOf(1, 2, 3, 4, 5)

val result = flow.dropWhile { it < 4 }
result.collect { println(it) }
// 4
// 5
take

Ограничивает поток первым n элементами.

val flow = flowOf(1, 2, 3, 4, 5)

flow.take(3)
    .collect { println(it) }
// 1
// 2
// 3
takeWhile

Берет элементы из потока до тех пор, пока условие выполняется.

val flow = flowOf(1, 2, 3, 4, 5)

flow.takeWhile { it < 4 }
    .collect { println(it) }
// 1
// 2
// 3
transformWhile

Позволяет преобразовывать элементы потока, пока не выполнится заданное условие.

val flow = flowOf(1, 2, 3, 4)

flow.transformWhile { value ->
    emit(value * 2)
    value != 3 // прекращаем преобразование, если значение равно 3
}.collect { println(it) }
// Вывод:
// 2
// 4
Merge Operators
flatMapConcat

Позволяет преобразовать элементы потока в новые потоки, обрабатывая их последовательно, один за другим.

val flow = flowOf(1, 2, 3)

flow
    .flatMapConcat { value ->
        flow {
            emit(value * 2)
        }
    }
    .collect { println(it) }
// Выведет: 2, 4, 6
flatMapMerge

Позволяет преобразовать элементы потока в новые потоки и сливать их в один поток, обрабатывая параллельно.

val flow = flowOf(1, 2, 3)

flow
    .flatMapMerge { value ->
        flow {
            emit(value * 2)
        }
    }
    .collect { println(it) }
// Выведет: 2, 4, 6
flatMapLatest

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

val flow = flowOf(1, 2, 3)

flow
    .flatMapLatest { value ->
        flow {
            emit(value * 2)
        }
    }
    .collect { println(it) }
// Выведет: 6
merge

Объединяет несколько потоков, позволяя им испускать значения одновременно, сохраняя порядок их эмиссий.

val flow1 = flowOf(1, 2)
val flow2 = flowOf(3, 4)

merge(flow1, flow2).collect { println(it) } // Вывод: 1, 2, 3, 4 (порядок зависит от исполнения)
mapLatest

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

flowOf(1, 2, 3)
    .mapLatest { value ->
        delay(100) // Длительная операция
        "Transformed $value"
    }
    .collect { println(it) } // Вывод: Transformed 3 (1 и 2 прерываются)
transformLatest

Позволяет преобразовывать элементы потока, при этом если приходит новый элемент, предыдущая операция отменяется.

val flow = flowOf(1, 2, 3)

flow.transformLatest { value ->
    emit(value * 2)
    delay(100) // имитация долгой работы
    emit(value + 1)
}.collect { println(it) }
// Вывод:
// 2
// 3
// 6
Transform Operators
filter

Пропускает элементы потока, не удовлетворяющие заданному условию, и эмитирует только те, которые удовлетворяют.

val flow = flowOf(1, 2, 3, 4, 5)

val result = flow.filter { it % 2 == 0 }
result.collect { println(it) }
// 2
// 4
filterNot

Пропускает элементы потока, которые удовлетворяют заданному условию, и эмитирует только те, которые ему не соответствуют.

val flow = flowOf(1, 2, 3, 4, 5)

val result = flow.filterNot { it % 2 == 0 }
result.collect { println(it) }
// 1
// 3
// 5
filterIsInstance

Фильтрует элементы потока, оставляя только те, которые могут быть приведены к указанному типу.

val flow = flowOf("Hello", 1, "World", 2)

val result = flow.filterIsInstance<String>()
result.collect { println(it) }
// Hello
// World
filterNotNull

Отфильтровывает все элементы, которые равны null, и эмитирует только ненулевые значения.

val flow = flowOf(1, null, 2, null, 3)

val result = flow.filterNotNull()
result.collect { println(it) }
// 1
// 2
// 3
map

Применяет заданную функцию к каждому элементу потока и возвращает новый поток с результатами.

val flow = flowOf(1, 2, 3)

val result = flow.map { it * 2 }
result.collect { println(it) }
// 2
// 4
// 6
mapNotNull

Применяет функцию к каждому элементу потока и фильтрует null значения из результата.

val flow = flowOf("1", "2", "a", "4")

val result = flow.mapNotNull { it.toIntOrNull() }
result.collect { println(it) }
// 1
// 2
// 4
withIndex

Добавляет индекс к каждому элементу потока, превращая его в IndexedValue.

flowOf("a", "b", "c")
    .withIndex()
    .collect { println("Index: ${it.index}, Value: ${it.value}") }
// Вывод:
// Index: 0, Value: a
// Index: 1, Value: b
// Index: 2, Value: c
onEach

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

val flow = flowOf(1, 2, 3)

flow.onEach { println("Value: $it") }
    .collect()
// Value: 1
// Value: 2
// Value: 3
scan

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

flowOf(1, 2, 3)
    .scan(0) { accumulator, value -> accumulator + value }
    .collect { println(it) }
// Вывод:
// 0 (начальное значение)
// 1 (0 + 1)
// 3 (1 + 2)
// 6 (3 + 3)
runningReduce

Накапливает значения, эмитируя промежуточные результаты, но без начального значения.

flowOf(1, 2, 3)
    .runningReduce { accumulator, value -> accumulator + value }
    .collect { println(it) }
// Вывод:
// 1
// 3 (1 + 2)
// 6 (3 + 3)
runningFold

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

flowOf(1, 2, 3)
    .runningFold(0) { accumulator, value -> accumulator + value }
    .collect { println(it) }
// Вывод:
// 0 (начальное значение)
// 1 (0 + 1)
// 3 (1 + 2)
// 6 (3 + 3)
chunked
https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/chunked.html

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

flowOf("a", "b", "c", "d", "e")
    .chunked(2) // ["a", "b"], ["c", "d"], ["e"]
    .map { it.joinToString(separator = "") }
    .collect {
        println(it) // Prints "ab", "cd", e"
    }
Zip Operators
combine

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

val flow1 = flowOf(1, 2, 3)
val flow2 = flowOf(10, 20, 30)

val result = flow1.combine(flow2) { a, b -> a + b }
result.collect { println(it) }
// 11
// 22
// 33
combineTransform

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

val flow1 = flowOf(1, 2, 3)
val flow2 = flowOf(10, 20, 30)

val result = flow1.combineTransform(flow2) { a, b ->
    emit(a + b) // Можно применить любую логику трансформации
}
result.collect { println(it) }
// 11
// 22
// 33
zip

Позволяет комбинировать два потока, соединяя элементы в пары.

val flow1 = flowOf(1, 2, 3)
val flow2 = flowOf("A", "B", "C")

flow1.zip(flow2) { a, b -> "$a$b" }
    .collect { println(it) }
// Вывод:
// 1A
// 2B
// 3C
Collect Operators
collect

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

• Блокирует текущий поток до тех пор, пока все данные не будут собраны или поток не завершится.

val flow = flowOf(1, 2, 3)

flow.collect { value ->
    println(value)
}
collectIndexed

Собирает значения из Flow и предоставляет индекс каждого элемента в коллекторе.

flowOf("a", "b", "c")
    .collectIndexed { index, value ->
        println("Index: $index, Value: $value")
    }
collectLatest

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

val flow = flow {
    emit("First")
    delay(100) // Имитация задержки
    emit("Second")
}

flow.collectLatest { value ->
    println(value) // Напечатает только "Second"
}
collectAsState

Собирает данные из Flow или StateFlow и превращает их в State. Обновляет UI автоматически, когда поток отправляет новые данные.

class MyViewModel: ViewModel() {
    private val _stateFlow = MutableStateFlow("Hello, Compose!")
    val stateFlow: StateFlow<String> = _numberFlow.asStateFlow()
}

@Composable
fun ExampleScreen(viewModel: MyViewModel) {
    val uiState by viewModel.stateFlow.collectAsState()
    Text(text = "State: $uiState")
}
collectAsStateWithLifecycle

Собирает данные из Flow или StateFlow, учитывая жизненный цикл Composable. Автоматически приостанавливает сбор, когда UI неактивен, и возобновляет, когда UI снова в фокусе.

class MyViewModel: ViewModel() {
    private val _stateFlow = MutableStateFlow("Hello, Compose!")
    val stateFlow = _stateFlow.asStateFlow()
}

@Composable
fun ExampleScreen(viewModel: MyViewModel) {
    val uiState by viewModel.stateFlow.collectAsStateWithLifecycle()
    Text(text = "State: $uiState")
}
Collection Operators
toList

Преобразует элементы Flow в список List.

val numbersFlow: Flow<Int> = flowOf(1, 2, 3, 4, 5)

runBlocking {
    val numbersList: List<Int> = numbersFlow.toList()
    println(numbersList) // [1, 2, 3, 4, 5]
}
toSet

Преобразует элементы Flow в Set, собирая уникальные значения из потока. Игнорирует дубликаты.

val numbersFlow: Flow<Int> = flowOf(1, 2, 2, 3, 4, 5, 5)

runBlocking {
    val numbersSet: Set<Int> = numbersFlow.toSet()
    println(numbersSet) // [1, 2, 3, 4, 5]
}
toCollection

Преобразует элементы Flow в заданную коллекцию, например, ArrayList или LinkedList. Позволяет настраивать тип коллекции, в которую будут собираться элементы.

val numbersFlow: Flow<Int> = flowOf(1, 2, 3, 4)

runBlocking {
    val numbersList: ArrayList<Int> = numbersFlow.toCollection(ArrayList())
    println(numbersList) // [1, 2, 3, 4]
}
Reduce Operators
reduce

Применяет операцию к каждому элементу потока, аккумулируя результат.

val result = flowOf(1, 2, 3).reduce { acc, value -> acc + value }
println(result) // 6
fold

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

val result = flowOf(1, 2, 3).fold(0) { acc, value -> acc + value }
println(result) // 6
single

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

val result = flowOf(1).single()
println(result) // 1
singleOrNull

Возвращает единственный элемент из потока или null, если поток пуст или содержит больше одного элемента.

val result = flowOf(1).singleOrNull()
println(result) // 1

val resultEmpty = flowOf<Int>().singleOrNull()
println(resultEmpty) // null

val resultMultiple = flowOf(1, 2).singleOrNull()
println(resultMultiple) // null
first

Возвращает первый элемент из потока, либо бросает исключение, если поток пуст.

val result = flowOf(1, 2, 3).first()
println(result) // 1
firstOrNull

Возвращает первый элемент из потока или null, если поток пуст.

val result = flowOf<Int>().firstOrNull()
println(result) // null
last

Возвращает последний элемент из потока, либо бросает исключение, если поток пуст.

val result = flowOf(1, 2, 3).last()
println(result) // 3
lastOrNull

Возвращает последний элемент из потока или null, если поток пуст.

val result = flowOf<Int>().lastOrNull()
println(result) // null
Count Operators
count

Возвращает количество элементов в потоке.

val result = flowOf(1, 2, 3).count()
println(result) // 3
Вопросы на собесе (16)
  • Operators (5)
    1. Какие есть типы функций над потоками?

      В зависимости от того, возвращают они конкретное значение или обработанный поток функции делятся на: терминальные и промежуточные. Терминальные функции потоков представляют suspend-функции, которые позволяют получать объекты из потока или возвращают какое-то конечное значение: collect toList toSet first firstOrNull last lastOrNull single singleOrNull count reduce fold. Промежуточные функции не являются suspend, принимают поток и возвращают обработанный поток: combine drop filter filterNot filterNotNull map onEach take transform zip.

    1. С какими операторами Flow работал?

      combine debounce filter flatMapLatest

    1. Как работает оператор flowOn?

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

    1. Как работает оператор collect?

      Оператор collect в Kotlin подписывается на Flow и получает эмитируемые значения, передавая каждый элемент в обработчик. Это асинхронный приостановленный метод, который можно использовать только в корутинах. Когда поток завершается, collect также завершает свою работу.

    1. Какие есть способы объединить 2 источника данных в Flow?

      combine объединяет последние значения из двух Flow и эмитит пары значений, когда один из них обновляется.

      zip объединяет Flow, эмитируя пары значений, только когда оба источника испускают новые значения одновременно.

  • Другие (11)
    1. Для чего применяется Flow?

      Flow используется для асинхронной обработки последовательностей данных во времени, позволяя работать с потоками данных, которые могут изменяться и обновляться, обеспечивая реактивное программирование в Kotlin.

    1. Какие бывают виды Flow?

      Flow StateFlow SharedFlow

    1. Как можно создавать Flow и какие есть билдеры?

      flow эмитирует значения внутри корутины.

      flowOf создает Flow из заранее заданных значений.

      asFlow преобразует коллекции или последовательности в Flow.

      channelFlow использует Channel для асинхронной эмиссии значений.

      callbackFlow создает Flow который работает с коллбэками.

    1. Какие операторы есть у Flow?

      combine filter debounce transform zip

    1. Какие источники Flow считаются горячими?

      StateFlow и SharedFlow

    1. Разница между холодным и горячим потоками в Kotlin?

      Холодный поток в Kotlin создается с помощью Flow, который начинает эмитировать данные только при подписке. Горячий поток реализуется с помощью SharedFlow или StateFlow, который продолжает эмитировать данные независимо от подписчиков.

    1. Как из холодного потока сделать горячий?

      Использовать операторы stateIn и shareIn.

    1. Разница между StateFlow и SharedFlow?

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

      SharedFlow не сохраняет состояние, а просто транслирует эмитированные значения всем подписчикам, без сохранения их в истории.

    1. Как обрабатывать ошибки в Flow?

      catch перехватывает исключения и позволяет выполнить альтернативные действия.

      onCompletion выполняет действия при завершении потока, независимо от результата.

      retry позволяет повторить операции в случае ошибки.

    1. Для чего используется SharedFlow?

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

    1. Какие параметры есть у SharedFlow?

      replay количество последних значений, которые будут доступны новым подписчикам.

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

      onBufferOverflow поведение при переполнении буфера. Удалять старые значения при добавлении новых (BufferOverflow.DROP_OLDEST) или новые ждут пока буфер освободится (BufferOverflow.SUSPEND).