Сбор преобразованного потока состояний в Composable

#android #kotlin-coroutines #android-jetpack-compose

#Android #android-jetpack-compose #kotlin-сопрограммы #kotlin-поток состояний

Вопрос:

Существует функция collectAsState() , применимая к StateFlow свойству, чтобы наблюдать его в Composable .

Для composable требуется a StateFlow , потому StateFlow что гарантирует начальное значение. A Flow не поставляется с такой гарантией.

Теперь, как поступить, если у меня есть StateFlow свойство, но я хочу применить оператор (например map ) перед сбором Flow в Composable ?

Вот пример:

Допустим, репозиторий предоставляет StateFlow<MyClass>

 val myClassStateFlow: StateFlow<MyClass>

data class MyClass(val a: String)
 

… и модель представления зависит от репозитория и хочет предоставить только свойство a its Composable

 val aFlow = myClassState.Flow.map { it.a } // <- this is of type Flow<String>
 

map Оператор изменяет тип с StateFlow<MyClass> на Flow<String> .

  1. Оправдано ли это семантически, что aFlow больше не имеет начального значения? В конце концов, его первое излучение получено из начального значения myClassStateFlow .
  2. Flow В какой-то момент требуется преобразовать обратно StateFlow в. Какое более идиоматичное место для этого?
    1. В модели представления с использованием stateIn() ? Как будет выглядеть код?
    2. В composable использовать collectAsState(initial: MyClass) и придумать начальное значение (хотя myClassStateFlow имело начальное значение)?

Ответ №1:

Смотрите Этот выпуск на GitHub

В настоящее время нет встроенного способа преобразования StateFlow s, только Flow s. Но вы можете написать свой собственный.

В итоге я решил использовать пример из этого сообщения.

Сначала создайте понятие a DerivedStateFlow .

 class DerivedStateFlow<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()

    @InternalCoroutinesApi
    override suspend fun collect(collector: FlowCollector<T>) {
        flow.collect(collector)
    }
}

 

Затем StateFlow map включите расширение, подобное текущему расширению на Flow

 fun <T1, R> StateFlow<T1>.mapState(transform: (a: T1) -> R): StateFlow<R> {
    return DerivedStateFlow(
        getValue = { transform(this.value) },
        flow = this.map { a -> transform(a) }
    )
}
 

Теперь в вашем репозитории или ViewModel вы можете использовать его, как показано ниже.

 
class MyViewModel( ... ) {
    private val originalStateFlow:StateFlow<SomeT>  = ...
    
    val someStateFlowtoExposeToCompose = 
        originalStateFlow
        .mapState { item -> 
            yourTransform(item)
        }
}
 

Теперь вы можете использовать его так, как ожидаете, в Compose без какой-либо специальной работы, поскольку он возвращает a StateFlow .