from a structured concurrency standpoint, does it ...
# android-architecture
k
from a structured concurrency standpoint, does it make sense for each
CorotuineScope
job be descended from the job in a fragment (and maybe the fragment is descended from the activity)? I'm thinking something like this.
What sprung this question is that in my viewholders, I'm loading some images with Coil. If an activity is destroyed for whatever reason, I don't want those jobs to not be cancelled. This is my current thought, but I don't know if there's an already fleshed out pattern for this that might be more robust than mine.
p
you mean: I don't want those jobs to not be cancelled or I don't want those jobs to be cancelled ?
k
If, say the activity gets destroyed then I would want all of the jobs below it to get cancelled
the only way to do that without manually doing it would be to pass a reference to a job all the way down since
SupervisorJob
has an optional parameter of
parent: Job
p
Ak ok, I thought you wanted to extend the life of the already started imageLoading tasks.
k
nope
just want to make sure all jobs are reaped and nothing is leaked
p
I see
k
I'm just not quite sure how to get the corresponding
Job
from an already existing CorotuineScope, eg
lifecycleScope
p
When you cancel a parent coroutine (which in effects cancels the coroutine Job associated with this coroutine), its descendant children's Jobs get canceled along with it. Unless you did override the child coroutine Job with a Custom Job eg: a
SupervisorJob
to intercept exception propagation from child to parent. To get the Job of a given scope this should work:
scope.coroutineContext[Job]
For the parent-child Job relationship read this: https://medium.com/@elizarov/coroutine-context-and-scope-c8b255d59055
k
thanks!
g
It’s strange relationship. ViewModel has longer lifecycle than Fragment, so it should has own scope not related on Fragment
☝️ 1
also RecyclerView should just use viewModelLifecle.coroutineScope (but even better to avoid this too, just in terms of architecture)
p
I agree, I think creating a scope per recyclerAdapter and recyclerViewHolder will create more trouble than benefit. Also pay attention the difference in ViewModel and Fragment lifecycle mentioned above. I would: 1- Decouple ViewModelScope from FragmentScope. Not being child-parent. 2- Have only 1 Scope for UI, FragmentScope in this case. Then use this same UI scope from the recyclerAdapter or recyclerViewHolder. You can pass it in the adapter constructor.
k
I can get behind that. Thanks!
@Pablichjenkov and the use case I have for creating a scope per viewholder is so that I can cancel tasks when a viewholder gets recycled.
Copy code
abstract class CoroutineViewHolder(
    itemView: View,
    scope: CoroutineScope
) : RecyclerView.ViewHolder(itemView), CoroutineScope by scope {

    /**
     * This establishes a lifecycle for recycling of views for
     * coroutine aware ViewHolders. Cancel the work that's being
     * done if this view is recycled and no longer needed.
     */
    @CallSuper
    fun onRecycled() {
        this.cancel("${this}.onRecycled called")
    }
}
previously what I was experiencing before this was data was loading and since views were being recycled, when multiple coroutines finished they all updated a view in the recyclerview which no longer held their data
so cancelling that work when the viewholder is recycled solved that problem
This has been particularly useful for loading images where the object being bound to the view holds a url for that image
p
I see your point. I normally let the ImageManager take care of the image lifecycle, then subscribe/unsubscribe from the ViewHolder, as the ViewHolder gets into/off the screen. I think controlling the image loading cycle from a viewholder may be tricky, like in the case of viewholder reuse in a quick scroll swipe. I normally complete loading the image even if the viewHolder unsubscribe from the image update. In many cases the user will scroll back and it will be there. 😉
k
Do you have a reference I can look at for that?
p
"No" with coroutines. What I described is the regular procedure I use when not using Glide/Picasso/UniversalImageLoader. These libraries do that work for you. I got to search if I have some project where I don't use above mentioned libs. Anyways, for your initial issue you basically need two things: 1- Clear the ImageView in the viewHolder everytime this is recycled. 2- In your ImageLoader instance keep a list of current image update callbacks. Mark each Image request with the associated Callback or ViewHolder itself. You can create a unique key if don't want to keep the reference to the viewholder from the request. If the viewholder gets recycled then remove the Callback from the list. When the image io request completes check that the associated ViewHolder is still in the list. You can lookup by the previously mentioned key. Only in the case that the ViewHolder still valid then update the image by invoking the callback or directly setting the Image in the imageView.
Or better instead of keeping a key that identifies the ImageView/ViewHolder that started the request. Keep a weakReference of the imageView/Viewholder that started the request. That does not leak too and perfoms better