Собирать из нескольких потоков состояний

У меня в viewModel есть 2 stateFlow. Чтобы собрать их во фрагмент, мне нужно запустить сопрограммы 2 раза, как показано ниже:

    lifecycleScope.launchWhenStarted {
        stocksVM.quotes.collect {
            if (it is Resource.Success) {
                it.data?.let { list ->
                    quoteAdapter.submitData(list)
                }
            }
        }
    }

    lifecycleScope.launchWhenStarted {
        stocksVM.stockUpdate.collect {
            log(it.data?.data.toString())
        }
    }

Если у меня больше stateFlow, мне придется соответственно запускать сопрограммы. Есть ли лучший способ обрабатывать несколько stateFlow в моем фрагменте / действии или где-то еще?


person Azim Salimov    schedule 02.06.2021    source источник
comment
Что вам мешает собрать в один прицел сам по себе? Например, если вы запускаете lifecycleScope.launchWhenStarted {}, вы не можете просто stocksVM.quotes.collect{} и stocksVM.stockUpdate.collect {} только внутри него.   -  person che10    schedule 02.06.2021
comment
К сожалению, не могу. Поскольку collect () является приостанавливающей функцией внутри сопрограммы, и она будет приостановлена ​​до тех пор, пока мой поток не остановится, поэтому мой следующий collect () не будет вызываться до тех пор, пока предыдущий поток не завершится @ che10   -  person Azim Salimov    schedule 03.06.2021


Ответы (3)


Вам потребуются разные сопрограммы, поскольку collect() - это функция приостановки, которая приостанавливается, пока ваш Flow не завершится.

В настоящее время рекомендуемый способ сбора нескольких потоков:

lifecycleScope.launch {
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
        launch {
          stocksVM.quotes.collect { ... }   
        }
    
        launch {
            stocksVM.stockUpdate.collect { ... }
        }
    }
}

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

Я бы определенно прочитал это, так как он очень хорошо объясняет текущие передовые практики: https://medium.com/androiddevelopers/a-safer-way-to-collect-flows-from-android-uis-23080b1f8bda

person Róbert Nagy    schedule 02.06.2021
comment
Спасибо за помощь! Я обязательно прочитаю это - person Azim Salimov; 02.06.2021

Вы можете смешивать несколько потоков.

Используйте функцию merge или combine в kotlin. Конечно, эти две функции используются по-разному.


Добавлять:

Если Flow не обрабатывается, откройте несколько сопрограмм для collect ():

fun main() {
    collectFlow()
}

fun emitStringElem(): Flow<String> = flow {
    repeat(5) {
        delay(10)
        emit("elem_$it")
    }
}

fun emitIntElem(): Flow<Int> = flow {
    repeat(10) {
        delay(10)
        emit(it)
    }
}

Результат открытия двух коллекций сопрограмм:

From int Flow: item is: 0
From string Flow: item is: elem_0
From int Flow: item is: 1
From string Flow: item is: elem_1
From int Flow: item is: 2
From string Flow: item is: elem_2
From int Flow: item is: 3
From string Flow: item is: elem_3
From int Flow: item is: 4
From string Flow: item is: elem_4
From int Flow: item is: 5
From int Flow: item is: 6
From int Flow: item is: 7
From int Flow: item is: 8
From int Flow: item is: 9

Слить два потока

fun margeFlow() = runBlocking {
    merge(
        emitIntElem().map {
            it.toString()
        }, emitStringElem()
    ).collect {
        println(it)
    }
}

результат:

0
elem_0
1
elem_1
2
elem_2
3
elem_3
4
elem_4
5
6
7
8
9

объединить два потока:

fun combineFlow() = runBlocking {
    combine(emitIntElem(), emitStringElem()) { int: Int, str: String ->
        "$int combine $str"
    }.collect {
        println(it)
    }
}

результат:

0 combine elem_0
1 combine elem_0
1 combine elem_1
2 combine elem_2
3 combine elem_3
4 combine elem_4
5 combine elem_4
6 combine elem_4
7 combine elem_4
8 combine elem_4
9 combine elem_4
person Future Deep Gone    schedule 02.06.2021
comment
Спасибо за помощь. Я буду считать что - person Azim Salimov; 02.06.2021

Как сказал @ RóbertNagy, вы не должны использовать launchWhenStarted. Но есть альтернативный синтаксис для правильного выполнения без вложенных launches:

stocksVM.quotes
    .flowOnLifecycle(Lifecycle.State.STARTED)
    .onEach { 
        if (it is Resource.Success) {
            it.data?.let { list ->
                quoteAdapter.submitData(list)
            }
        }
    }.launchIn(lifecycleScope)

stocksVM.stockUpdate
    .flowOnLifecycle(Lifecycle.State.STARTED)
    .onEach { 
        log(it.data?.data.toString())
    }.launchIn(lifecycleScope)
person Tenfour04    schedule 02.06.2021
comment
Спасибо за помощь! - person Azim Salimov; 02.06.2021
comment
@AzimSalimov См. Мой ответ здесь о вспомогательной функции, которая может сделать синтаксис очень лаконичный. - person Tenfour04; 02.06.2021
comment
хорошо спасибо @ Tenfour04 - person Azim Salimov; 03.06.2021