kevin.cianfarini

    kevin.cianfarini

    3 years ago
    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.
    Pablichjenkov

    Pablichjenkov

    3 years ago
    you mean:I don't want those jobs to not be cancelled orI don't want those jobs to be cancelled ?
    kevin.cianfarini

    kevin.cianfarini

    3 years ago
    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
    Pablichjenkov

    Pablichjenkov

    3 years ago
    Ak ok, I thought you wanted to extend the life of the already started imageLoading tasks.
    kevin.cianfarini

    kevin.cianfarini

    3 years ago
    nope
    just want to make sure all jobs are reaped and nothing is leaked
    Pablichjenkov

    Pablichjenkov

    3 years ago
    I see
    kevin.cianfarini

    kevin.cianfarini

    3 years ago
    I'm just not quite sure how to get the corresponding
    Job
    from an already existing CorotuineScope, eg
    lifecycleScope
    Pablichjenkov

    Pablichjenkov

    3 years ago
    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
    kevin.cianfarini

    kevin.cianfarini

    3 years ago
    thanks!
    gildor

    gildor

    3 years ago
    It’s strange relationship. ViewModel has longer lifecycle than Fragment, so it should has own scope not related on Fragment
    also RecyclerView should just use viewModelLifecle.coroutineScope (but even better to avoid this too, just in terms of architecture)
    Pablichjenkov

    Pablichjenkov

    3 years ago
    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.
    kevin.cianfarini

    kevin.cianfarini

    3 years ago
    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.
    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
    Pablichjenkov

    Pablichjenkov

    3 years ago
    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. 😉
    kevin.cianfarini

    kevin.cianfarini

    3 years ago
    Do you have a reference I can look at for that?
    Pablichjenkov

    Pablichjenkov

    3 years ago
    "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