#kotlin #collections #kotlin-flow
#kotlin #Коллекции #kotlin-flow
Вопрос:
У меня есть два потока состояний. Возможно ли их объединить и получить новый поток состояний? Логически это должно быть возможно, потому что оба потока состояний имеют начальные значения, но, как я вижу, функция объединения возвращает только поток, а не поток состояний.
Комментарии:
1. Я думаю, вы найдете это medium.com/better-programming /…
2. Зачем вам конкретно нужен a
StateFlow
?3. @LouisWasserman В некоторых местах мне нужно просто прочитать текущее значение, не собирая его
Ответ №1:
Пока я создал функцию:
fun <T1, T2, R> combineState(
flow1: StateFlow<T1>,
flow2: StateFlow<T2>,
scope: CoroutineScope = GlobalScope,
sharingStarted: SharingStarted = SharingStarted.Eagerly,
transform: (T1, T2) -> R
): StateFlow<R> = combine(flow1, flow2) {
o1, o2 -> transform.invoke(o1, o2)
}.stateIn(scope, sharingStarted, transform.invoke(flow1.value, flow2.value))
Комментарии:
1. Можете ли вы показать, как вы используете это в коде? Похоже, у меня это не работает
Ответ №2:
Вы могли бы использовать combine
оператор, а затем использовать stateIn
функцию для Flow
того, что из этого получается.
Из stateIn
документации в репозитории сопрограмм kotlinx:
stateIn
Функция «Преобразует холодное Flow
в горячее StateFlow
, которое запускается в заданной области сопрограммы, разделяя самое последнее переданное значение из одного запущенного экземпляра восходящего потока с несколькими нисходящими подписчиками».
На момент написания этой статьи его подпись:
fun <T> Flow<T>.stateIn(
scope: CoroutineScope,
started: SharingStarted,
initialValue: T
): StateFlow<T> (source)
Таким образом, вы должны быть в состоянии выполнять любые необходимые преобразования с вашими Flow
s, включая их объединение, а затем в конечном итоге использовать stateIn
для преобразования их обратно в a StateFlow
.
Это может выглядеть примерно так (возможно, создание калькулятора игровых очков Scrabble):
val wordFlow = MutableStateFlow("Hi")
val pointFlow = MutableStateFlow(5)
val stateString = wordFlow.combine(pointFlow) { word, points ->
"$word is worth $points points"
}.stateIn(viewModelScope, SharingStarted.Eagerly, "Default is worth 0 points")
stateString
будет иметь тип StateFlow<String>
, и вы успешно объединили два других StateFlows
в один StateFlow
.
Комментарии:
1. И, как всегда, примерно через 5 секунд после публикации я понимаю, что вы ответили на свой вопрос, используя
stateIn
. Увы, я оставлю описание для потомков.
Ответ №3:
Аналогичное решение для @Nikola Despotoski, но в виде функции расширения
/**
* Combines two [StateFlow]s into a single [StateFlow]
*/
fun <T1, T2, R> StateFlow<T1>.combineState(
flow2: StateFlow<T2>,
scope: CoroutineScope = GlobalScope,
sharingStarted: SharingStarted = SharingStarted.Eagerly,
transform: (T1, T2) -> R
): StateFlow<R> = combine(this, flow2) { o1, o2 -> transform.invoke(o1, o2) }
.stateIn(scope, sharingStarted, transform.invoke(this.value, flow2.value))
Ответ №4:
Используйте combine
operator, для объединения результатов обоих потоков требуется два потока и функция преобразования.
val int = MutableStateFlow(2)
val double = MutableStateFlow(1.8)
int.combine(double){ i, d ->
i d
}.collect(::println)
Комментарии:
1. Мне нужен конкретный поток состояний, но эта функция возвращает поток
2. Теперь это ошибка в IDE: «Недостаточно информации для вывода переменной типа R», также запрашивается преобразование. Все еще новичок в этом, поэтому, пожалуйста, объясните или измените свой ответ, если библиотека обновилась. Спасибо!
Ответ №5:
Объединить n потоков состояний
@Suppress("CHANGING_ARGUMENTS_EXECUTION_ORDER_FOR_NAMED_VARARGS")
inline fun <reified T, R> combineStateFlow(
vararg flows: StateFlow<T>,
scope: CoroutineScope = GlobalScope,
sharingStarted: SharingStarted = SharingStarted.Eagerly,
crossinline transform: (Array<T>) -> R
): StateFlow<R> = combine(flows = flows) {
transform.invoke(it)
}.stateIn(
scope = scope,
started = sharingStarted,
initialValue = transform.invoke(flows.map {
it.value
}.toTypedArray())
)
Используя:
data class A(val a: String)
data class B(val b: Int)
private val test1 = MutableStateFlow(A("a"))
private val test2 = MutableStateFlow(B(2))
@Suppress("CHANGING_ARGUMENTS_EXECUTION_ORDER_FOR_NAMED_VARARGS")
private val _isValidForm = combineStateFlow(
flows = arrayOf(test1, test2),
scope = viewModelScope
) { combinedFlows: Array<Any> ->
combinedFlows.map {
val doSomething = when (it) {
is A -> true
is B -> false
else -> false
}
}
}
Ответ №6:
Вышеупомянутые решения используют stateIn()
with GlobalScope
и policy as Eagerly
, что означает, что эти потоки состояний никогда не перестанут наблюдаться после создания, что может привести к проблемам.
Я уже упоминал подробности в этом блоге. Вместо этого создайте отдельный класс, который получает новый StateFlow
:
private class TransformedStateFlow<T>(
private val getValue: () -> T,
private val flow: Flow<T>
) : StateFlow<T> {
override val replayCache: List<T> get() = listOf(value)
override val value: T get() = getValue()
override suspend fun collect(collector: FlowCollector<T>): Nothing =
coroutineScope { flow.stateIn(this).collect(collector) }
}
/**
* Returns [StateFlow] from [flow] having initial value from calculation of [getValue]
*/
fun <T> stateFlow(
getValue: () -> T,
flow: Flow<T>
): StateFlow<T> = TransformedStateFlow(getValue, flow)
/**
* Combines all [stateFlows] and transforms them into another [StateFlow] with [transform]
*/
inline fun <reified T, R> combineStates(
vararg stateFlows: StateFlow<T>,
crossinline transform: (Array<T>) -> R
): StateFlow<R> = stateFlow(
getValue = { transform(stateFlows.map { it.value }.toTypedArray()) },
flow = combine(*stateFlows) { transform(it) }
)
/**
* Variant of [combineStates] for combining 3 state flows
*/
inline fun <reified T1, reified T2, reified T3, R> combineStates(
flow1: StateFlow<T1>,
flow2: StateFlow<T2>,
flow3: StateFlow<T3>,
crossinline transform: (T1, T2, T3) -> R
) = combineStates(flow1, flow2, flow3) { (t1, t2, t3) ->
transform(
t1 as T1,
t2 as T2,
t3 as T3
)
}
// Other variants for combining N StateFlows
После этого вы можете реализовать его в своем варианте использования. Например:
private val isLoading = MutableStateFlow(false)
private val loggedInUser = MutableStateFlow<User?>(null)
private val error = MutableStateFlow<String?>(null)
// Combining these states to form a LoginState
val state: StateFlow<LoginState> = combineStates(isLoading, loggedInUser, error) { loading, user, errorMessage ->
LoginState(loading, user, errorMessage)
}
Этот подход безопаснее, чем другие упомянутые подходы, поскольку он будет прослушивать StateFlow
обновления только тогда, когда они фактически собираются (внутри области сопрограммы потребителя)