Hello! I have some doubts regarding CoroutineScope...
# coroutines
e
Hello! I have some doubts regarding CoroutineScopes. Due to how we use Apollo Kotlin and DataBinding in our Android app, we realised while migrating from LiveData to Flows that we need to share the Flow that is returned by our repository with
replay = 1
to prevent re-running the same query for each downstream flow. My doubt is: does the scope passed to
shareIn()
operator need to be canceled at some point, to avoid leaking resources? Or if using
WhileSubscribed
for the started strategy it's fine if it's not closed? We assumed that it needs closing, so in our first iteration we are passing
viewModelScope
in the repository function, but we would actually prefer the repository to have its own scope instead, so we don't have to pass this all the time. But if it has its own scope, we need to manually cancel the scope from somewhere (probably
ViewModel.onCleared()
?), which also is not very convenient. Here's some sample code to better illustrate this situation: https://gist.github.com/eduardb/8916ac63d7a279b48857d9abc1f4546e. What are your thoughts?
k
Due to how we use Apollo Kotlin and DataBinding in our Android app, we realised while migrating from LiveData to Flows that we need to share the Flow that is returned by our repository with
replay = 1
to prevent re-running the same query for each downstream flow.
Is there any reason you’re using the flow variant over the single-shot suspend funs? If you’re using the normalized cache, I’d recommend having a strict line between a request that hits the network and a request that reads from the cache. That means not using
CacheFirst
or
NetworkFirst
at all, and only ever using
NetworkOnly
and
CacheOnly
. A shared/state flow will always live in memory if something is referencing it, including a coroutinescope or any other non-coroutines related object. If you are using
Flow.shareIn
or
Flow.stateIn
that scope will have to get cancelled to garbage collect the associated shared/state flow.
e
Is there any reason you’re using the flow variant over the single-shot suspend funs? If you’re using the normalized cache, I’d recommend having a strict line between a request that hits the network and a request that reads from the cache. That means not using
CacheFirst
or
NetworkFirst
at all, and only ever using
NetworkOnly
and
CacheOnly
.
We are doing the equivalent of
CacheOnly
, but instead of
Query.Data
, we read
Fragment.Data
from the normalized cache, it's a longer story. But at the same time, we have some
NetworkOnly
queries that run in the background to refresh the cache. We do all of this because it's very important for us for the app to work while offline because of poor connectivity conditions in our markets, but we also want data on the screen to be refreshed instantly when something in the normalized cache is updated (e.g. as a result of successful refresh).
A shared/state flow will always live in memory if something is referencing it, including a coroutinescope or any other non-coroutines related object. If you are using
Flow.shareIn
or
Flow.stateIn
that scope will have to get cancelled to garbage collect the associated shared/state flow.
This confirms my assumption, thanks! Any novel ideas on how to make it less cumbersome to cancel the repository's scope when it's not used anymore? 😄
k
I would highly suggest that you just re-run the query for
CacheOnly
. It’s not expensive to do so since it never hits the network. It would save you a lot of heartache for managing a scope for your repositories.