//From Kotlin code (Compose View), using CoroutineScope
scope.launch {
viewModel.getDataOne(key)
viewModel.data_one.collect{ one ->
when(one){
is DataState.Success->{
viewModel.getDataTwo(one.data.value1, one.data.value2)
viewModel.data_two.collect{ two ->
when(two){
is DataState.Success->{
viewModel.getDataThree(two.data.valueID, two.data.valueName)
viewModel.data_three.collect{ three ->
when(three){
is DataState.Success->{
//Continue with your calls
/**
* But then, this doesn't look clean beacuse of the nested api calls.
* So is the a clean way to write this calls on your Views?
*/
}
}
}
}
}
}
}
}
}
}
// Function from ViewModel
fun getDataOne(key:String){
viewModelScope.launch {
repository.getDataOne(key = key).collect{
valueDataState.tryEmit(it)
}
}
}
Please help me to write a clean Code than having this long nested calls, if there's a proper way or a cleaner way to write that. Thanks in advance .....
Yes, this can be flattened out. You have a lot of nested collect calls, which can be flattened using flat mapping. You need to do this anyway if any of those inner flows are SharedFlows/StateFlows/from repositories. Those kinds of flows are infinite, so if you do a nested collect, it will be stuck on the first value of the outer flow forever. The "latest" part of flatMapLatest
gives it an opportunity to restart the inner flow if the outer flow has a new value.
scope.launch {
viewModel.getDataOne(key)
viewModel.data_one
.filter { it is DataState.Success }
.flatMapLatest { one ->
viewModel.getDataTwo(one.data.value1, one.data.value2)
viewModel.data_two
}
.filter { it is DataState.Success }
.flatMapLatest { two ->
viewModel.getDataThree(two.data.valueID, two.data.valueName)
viewModel.data_three
}
.filter { it is DataState.Success }
.collect { three ->
//...
}
}
But to me it looks like this should all be in done in the view model and exposed as a single flow for the UI to consume. This stuff is working with the data, not displaying it.
The following is something else you can do to make it fit conventions better and make the code easier to read.
Instead of the getDataOne()
function, I would change it to a dataOneKey
property, because that's all this function really does. It sets the key that's used as the dependency of the data_one
flow. Functions with get
at the start usually return the thing they are describing. This is kind of a universal OOP convention, although in Kotlin there are properties, so you usually only see getter functions if they are suspend functions. So, here's how I would change the view model. I have to make some guesses here because I don't really know what the other flows in your view model are currently designed as. You could convert each of your get
functions something like this:
private val dataOneKeyFlow = MutableStateFlow<String?>(null)
var dataOneKey: String?
get() = dataOneKeyFlow.value
set(value) { dataOneKeyFlow.value = value }
val valueDataState = dataOneKeyFlow
.flatMapLatest { key -> if (key == null) emptyFlow() else repository.getDataOne(key) }
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), replay = 1)
Then on the other end it would look like:
scope.launch {
viewModel.dataOneKey = key
viewModel.data_one
.filter { it is DataState.Success }
//...