https://kotlinlang.org logo
k

kevin.cianfarini

09/03/2019, 8:28 PM
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

Pablichjenkov

09/03/2019, 9:02 PM
you mean: I don't want those jobs to not be cancelled or I don't want those jobs to be cancelled ?
k

kevin.cianfarini

09/03/2019, 9:17 PM
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

Pablichjenkov

09/03/2019, 9:19 PM
Ak ok, I thought you wanted to extend the life of the already started imageLoading tasks.
k

kevin.cianfarini

09/03/2019, 9:19 PM
nope
just want to make sure all jobs are reaped and nothing is leaked
p

Pablichjenkov

09/03/2019, 9:20 PM
I see
k

kevin.cianfarini

09/03/2019, 9:20 PM
I'm just not quite sure how to get the corresponding
Job
from an already existing CorotuineScope, eg
lifecycleScope
p

Pablichjenkov

09/03/2019, 9:32 PM
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

kevin.cianfarini

09/03/2019, 9:45 PM
thanks!
g

gildor

09/04/2019, 4:58 AM
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

Pablichjenkov

09/04/2019, 11:33 PM
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

kevin.cianfarini

09/05/2019, 12:38 AM
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

Pablichjenkov

09/05/2019, 1:07 AM
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

kevin.cianfarini

09/05/2019, 1:09 AM
Do you have a reference I can look at for that?
p

Pablichjenkov

09/05/2019, 1:36 AM
"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
3 Views