I was wondering, is it safe/good idea to use `coro...
# coroutines
s
I was wondering, is it safe/good idea to use
coroutineScope
inside a flow operator? I have a flow I’m mapping on, and I want, depending on the value do some multiple suspending calls that I would optimally prefer to be done in parallel. Specific example inside the thread 🧵
The whole class looks like this
Copy code
class AnimalsScreenViewModel(
    private val someRepository: SomeRepository,
    @DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher,
) : ViewModel() {

    private val _animalType: MutableStateFlow<AnimalType> = MutableStateFlow(<http://AnimalType.Cat|AnimalType.Cat>)
    val animalType: StateFlow<AnimalType>
        get() = _animalType.asStateFlow()

    val animalListStateFlow: StateFlow<List<Animal>> = animalType
        .map { animalType: AnimalType ->
            coroutineScope {
                val getAnimalAsyncCalls: List<Deferred<List<Animal>>> = List(4) { index ->
                    async {
                        someRepository.getAnimals(
                            animalType = animalType,
                            page = (index + 1)
                        )
                    }
                }
                val animalList: List<Animal> = getAnimalAsyncCalls
                    .awaitAll()
                    .flatten()
                animalList
            }
        }
        .stateIn(
            scope = viewModelScope + defaultDispatcher, // Not sure about setting a dispatcher here
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = listOf()
        )

    fun setAnimalType(animalType: AnimalType) {
        _animalType.value = animalType
    }
}
I want to bring the emphasis on the map flow operator here
Copy code
.map { animalType: AnimalType ->
    coroutineScope {
        val getAnimalAsyncCalls: List<Deferred<List<Animal>>> = List(4) { index ->
            async {
                petFinderRepository.getAnimals(
                    animalType = animalType,
                    page = (index + 1)
                )
            }
        }
        val animalList: List<Animal> = getAnimalAsyncCalls
            .awaitAll()
            .flatten()
        animalList
    }
}
I am 100% not sure about if this would work alright, and follow structured concurrency and cancel itself if the
animalType
flow emits a different value in the meantime. I would love it if someone could take a peek at this and point out if there’s something obviously bad that I should be aware of, thank you.
d
Depends on the operator but it is safe in general. If you need concurrency... then you need concurrency.
s
I am just completely unfamiliar with the
coroutineScope
function that’s why I just thought I’d share it here to see if there’s something obviously wrong about it, I just recently read about it on Bill Phillips’ blog here https://www.billjings.net/posts/title/foot-marksmanship-with-runblocking/ and it just so happened that I had a case where I needed access to async while I was in a simple suspend function. But when you say depends on the operation, what comes first to your mind where it would be problematic?
d
Operators where you can call emit basically, since it'll be easier to break Flow rules of not switching context. oLike
Like
transform
is one of them.
s
Aha so inside those, if the context switches (which it would with
coroutineScope
I guess?) this would break somehow? Is that documented somewhere, I haven’t seen it mentioned before.
n
s
Wow thank you! Never would have found this in the docs myself.