```override suspend fun getProducts()=flow<Stat...
# coroutines
n
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
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
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
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
Okay
g
otherwise for now it looks that it can hide some error (for example just by hiding
cause
n
kotlinx.coroutines.flow.internal.ChildCancelledException: Child of the scoped flow was cancelled
this is the Exception returning, no stackTrace found
g
It doesn’t look as full exceptiuon
it looks as exception.toString()
n
yes, But that is the only thing iam getting
g
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
message has been deleted
g
So which part of code catches this exception?
Open
cause
and check, maybe there is stacktrace
n
i thought it is because of any error in Room library, but i tried with network call in the same and same error
g
Just checking, what
loadProducts
is doing, there is no related code in your example
n
"So which part of code catches this exception?" when executing
productDao.getProducts()
g
Ah, I probably got it, it just means that flow where you run this code is already cancelled
n
loadProducts
is a network call to fetch from server and it is working, because iam using that one to store into my sqlite
g
on practice it probably means that scope where you call viewModel.getProducts is already cacncelled
n
"Ah, I probably got it, it just means that flow where you run this code is already cancelled" can you please explain that
g
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
Okay thanks, LEt me try
so meaning this getProducts() from repository is running viewModelScope?
g
It’s opposite, viewModel.getProducts collecting your productRepository.getProducts, so it depends on where this viewModel.getProducts is called
n
confused now, so which of my scope is cancelled?? the flow from repository?
g
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
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
Well, if dao is flowable, it will be very different code
and I just don’t understand how it supose to work
n
yes
g
probably you need something like thius:
Copy code
if (it.isEmpty()) {
                    emit(State.success(loadProducts()))
                } else {
                   emit(State.success(it))
                }
n
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
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
THanaks Brother
g
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
like how??
g
I mean implement this function as mapping of values
n
THanks, I will try
g
depend of course on how productDao.getProducts() is implemented, now there is no reason to use Flow there, it always return one value
n
yea, but i will make the query back to flow
g
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
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
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
no. from fragment/activity to viewmodel
g
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
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
Sorry, it hard to understand what is going on which this example
n
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
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
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
I already told you, it depends on how getProducts implemented
n
THanks, Now I understtod
THank you for clearing my doubts....
👍 1
n
FYI, you should pretty much never call emit from try/catch. See https://kotlinlang.org/docs/flow.html#exception-transparency
n
THanks