https://kotlinlang.org logo
#coroutines
Title
# coroutines
n

Noushad Chullian

07/06/2021, 6:41 AM
Copy code
override suspend fun getProducts()=flow<State<List<ProductListFromEntity>>> {
            try {
                val it = productDao.getProducts()
                if (it.isEmpty()) {
                    loadProducts()
                } else {
                   emit(State.success(it))
                }
            }catch(e:Exception){
                emit(State.error(e.localizedMessage.toString()))
                e.printStackTrace()
            }
}



//Dao function
 @Query("SELECT product.productId, product.productCode, product.name FROM Product product")
    abstract suspend fun getProducts(): List<ProductListFromEntity>
    

//viewModel
suspend fun getProducts() {
        productRepository.getProducts().collectLatest {
            when (it) {
                is State.Error -> {
                    _productListStates.value =
                        ProductListStates.Error(it.message, UIComponentType.Dialog())
                }
                is State.Success -> {
                    parseGivenProductList(it.data)
                }
            }
        }
    }
i am calling this function from viewmodel (android), But it is always throwing error saying,
Child of the scoped flow was cancelled
without any stacktrace. Am i doing anything wrong here...
g

gildor

07/06/2021, 6:52 AM
without any stacktrace
It’s strange, it should have at least some stacktrace, could you show full error message
I’m not quite sure how you use
productDao.getProducts()
as I understand from your example it should return Flow, but you use it a way that it returns List
n

Noushad Chullian

07/06/2021, 6:56 AM
When i am copying here i was trying to change somthing else that is why it is returning a flow here, i will edit the question
g

gildor

07/06/2021, 6:59 AM
When you run this code, could you show full error message
For example instead of doing
State.error(e.localizedMessage.toString()))
pass whole exception:
Copy code
State.error(e)
n

Noushad Chullian

07/06/2021, 7:00 AM
Okay
g

gildor

07/06/2021, 7:00 AM
otherwise for now it looks that it can hide some error (for example just by hiding
cause
n

Noushad Chullian

07/06/2021, 7:04 AM
kotlinx.coroutines.flow.internal.ChildCancelledException: Child of the scoped flow was cancelled
this is the Exception returning, no stackTrace found
g

gildor

07/06/2021, 7:11 AM
It doesn’t look as full exceptiuon
it looks as exception.toString()
n

Noushad Chullian

07/06/2021, 7:11 AM
yes, But that is the only thing iam getting
g

gildor

07/06/2021, 7:12 AM
well, exception.toString() is not a full exception, make sure that you print stacktrace too
actual exception has some stacktrace anyway
With code you provided there is no way to understand what is going on and which part of your code is actually throwing it and who is handling it
n

Noushad Chullian

07/06/2021, 7:13 AM
message has been deleted
g

gildor

07/06/2021, 7:14 AM
So which part of code catches this exception?
Open
cause
and check, maybe there is stacktrace
n

Noushad Chullian

07/06/2021, 7:15 AM
i thought it is because of any error in Room library, but i tried with network call in the same and same error
g

gildor

07/06/2021, 7:16 AM
Just checking, what
loadProducts
is doing, there is no related code in your example
n

Noushad Chullian

07/06/2021, 7:16 AM
"So which part of code catches this exception?" when executing
productDao.getProducts()
g

gildor

07/06/2021, 7:17 AM
Ah, I probably got it, it just means that flow where you run this code is already cancelled
n

Noushad Chullian

07/06/2021, 7:17 AM
loadProducts
is a network call to fetch from server and it is working, because iam using that one to store into my sqlite
g

gildor

07/06/2021, 7:18 AM
on practice it probably means that scope where you call viewModel.getProducts is already cacncelled
n

Noushad Chullian

07/06/2021, 7:18 AM
"Ah, I probably got it, it just means that flow where you run this code is already cancelled" can you please explain that
g

gildor

07/06/2021, 7:18 AM
flow is active only while someone is collecting it
if coroutine which collects this flow is cancelled, you will get this exception every time when you call any suspend function
The right way would be instead of
Copy code
}catch(e:Exception){
do
Copy code
}catch(e: CancellationException){
  throw e
}catch(e:Exception){
so it will propagate cancellation
n

Noushad Chullian

07/06/2021, 7:20 AM
Okay thanks, LEt me try
so meaning this getProducts() from repository is running viewModelScope?
g

gildor

07/06/2021, 7:26 AM
It’s opposite, viewModel.getProducts collecting your productRepository.getProducts, so it depends on where this viewModel.getProducts is called
n

Noushad Chullian

07/06/2021, 7:27 AM
confused now, so which of my scope is cancelled?? the flow from repository?
g

gildor

07/06/2021, 7:29 AM
no, scope on which viewModel.getProducts is called
So, in general there is no issue in code which you show,except that you do not propagate cancellation, and end up with State.Error with cancellation error, which you usually don’t want to have, you want instead propagate cancellation But it doesn’t solve your issue, it looks that your problem not with this code, but with the fact that something cancelling your viewModel.getProducts()
loadProducts
is a network call to fetch from server and it is working, because iam using that one to store into my sqlite
But as I see it doesn’t emit anything, looks that you return nothing there
n

Noushad Chullian

07/06/2021, 7:34 AM
yes the dao call must be flowable, that is how it was, and i was trying to find this thing, so for now i changed not to flow
g

gildor

07/06/2021, 7:34 AM
Well, if dao is flowable, it will be very different code
and I just don’t understand how it supose to work
n

Noushad Chullian

07/06/2021, 7:35 AM
yes
g

gildor

07/06/2021, 7:35 AM
probably you need something like thius:
Copy code
if (it.isEmpty()) {
                    emit(State.success(loadProducts()))
                } else {
                   emit(State.success(it))
                }
n

Noushad Chullian

07/06/2021, 7:36 AM
Copy code
// this is how i am calling viewmodel to load

lifecycleScope.launchWhenStarted {
            vm.action.emit(ProductListActions.FetchProductList)
        }

//the init of viewmodel
init {
        viewModelScope.launch { handleIntent() }
    }

private suspend fun handleIntent() {
        action.collectLatest {
		when (it) {
                ProductListActions.FetchProductList -> {
                    getProducts()
                }
			}
}
is there anything wrong here?
because i am using the same way in another place, and it is working like expected
g

gildor

07/06/2021, 7:39 AM
It can be related on the fact that you use collectLatest everywhere, which cancells coroutine, so every new action will cancell currently running getPRoducts
it’s not an issue if you propagate cancellation correctly, as I suggested above
n

Noushad Chullian

07/06/2021, 7:40 AM
THanaks Brother
g

gildor

07/06/2021, 7:41 AM
Hope it will help
I would also suggest you instead of
flow<State<List<ProductListFromEntity>>> {
to use simple map/catch on flow, it would be more natural code and more flexible and will easily work with flow
n

Noushad Chullian

07/06/2021, 7:44 AM
like how??
g

gildor

07/06/2021, 7:45 AM
I mean implement this function as mapping of values
n

Noushad Chullian

07/06/2021, 7:46 AM
THanks, I will try
g

gildor

07/06/2021, 7:46 AM
depend of course on how productDao.getProducts() is implemented, now there is no reason to use Flow there, it always return one value
n

Noushad Chullian

07/06/2021, 7:47 AM
yea, but i will make the query back to flow
g

gildor

07/06/2021, 7:50 AM
even with current suspend function it can be implemented like:
Copy code
productDao::getProducts.toFlow() // convert reference on suspend function to flow, later you can just use any other flow
  .map { if (it.isEmpty()) {
                    loadProducts() // Suppose it returns State
                } else {
                   State.success(it)
                }
  }
  .catch {
        emit(State.error(..))
  }
n

Noushad Chullian

07/06/2021, 7:50 AM
THanks, Can i ask another question
since i am struggling with this flow/collect thing
private suspend fun handleIntent() {
action.collectLatest {
when (it) {
ProductListActions.FetchProductList -> {
getProducts()
}
}
}
this function has another events, but when i am using just collect, it is not collecting from second time, it is working for only first time? any idea about that
g

gildor

07/06/2021, 8:08 AM
Is it with flow or suspend function on DAO
because when youy change dao to flow it never complete, it will be subscribed forever,. as result getProducts() function never return and can be cancelled
n

Noushad Chullian

07/06/2021, 8:17 AM
no. from fragment/activity to viewmodel
g

gildor

07/06/2021, 8:18 AM
not sure what you mean
I just said that your current implementation will work very different with suspend or with dao
it shouldn’t be an issue about collect if you use suspend function
but it will be with Flow
n

Noushad Chullian

07/06/2021, 8:22 AM
i am passing action via sharedFlow from activity to viewModel, and i am collecting it here
private suspend fun handleIntent() {
action.collect {
when (it) {
ProductListActions.FetchProductList -> {
getProducts()
}
}
}
But for the first time the sharedFlow is collected, and not collecting afterwards
g

gildor

07/06/2021, 8:23 AM
Sorry, it hard to understand what is going on which this example
n

Noushad Chullian

07/06/2021, 8:23 AM
it is not related to my first question
this is a different one
i am passing action via sharedFlow from activity to viewModel, and i am collecting it here
private suspend fun handleIntent() {
action.collect {
when (it) {
ProductListActions.FetchProductList -> {
getProducts()
}
}
}
But for the first time the sharedFlow is collected, and not collecting afterwards
this
handleIntent()
is called from the init of the viewModel, So I am thinking the collecting of action inside ths function will persist till the viewModelScope is alive.
g

gildor

07/06/2021, 8:28 AM
it is not related to my first question
I understand, and I have even less context, so don’t understand what is going on there
n

Noushad Chullian

07/06/2021, 8:29 AM
Emiting action from activity/fragment works for the first time like getProgucts(), but second time when clicked on the product from the list, i am passing another action to viewmodel about this click event, but it is not collecting
g

gildor

07/06/2021, 8:30 AM
I already told you, it depends on how getProducts implemented
n

Noushad Chullian

07/06/2021, 8:41 AM
THanks, Now I understtod
THank you for clearing my doubts....
👍 1
n

Nick Allen

07/06/2021, 8:56 AM
FYI, you should pretty much never call emit from try/catch. See https://kotlinlang.org/docs/flow.html#exception-transparency
n

Noushad Chullian

07/06/2021, 10:06 AM
THanks
2 Views