Search code examples
androidkotlinandroid-livedatalifecycleandroid-viewmodel

How to Implement LifecycleOwner on a RecyclerAdapter ViewHolder?


I am in need of observing a LiveData from ViewModel inside a RecyclerAdapter ViewHolder. The reasons behind it doesn't come to the point. For that I created a custom ViewHolder that implements LifeycleOwner and managed to properly call the lifecycle events EXCEPT for the "Lifecycle.State.DESTROYED"

Here is the code I ended up with:

Custom Adapter:

abstract class LifecyclePagingDataAdapter<T: Any, VH: LifecycleViewHolder>(diffCallback: DiffUtil.ItemCallback<T>) : PagingDataAdapter<T, VH>(diffCallback) {

    override fun onViewAttachedToWindow(holder: VH) {
        super.onViewAttachedToWindow(holder)
        holder.onAppear()
    }

    override fun onViewDetachedFromWindow(holder: VH) {
        super.onViewDetachedFromWindow(holder)
        holder.onDisappear()
    }
}

Custom ViewHolder:

abstract class LifecycleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), LifecycleOwner {
    private val lifecycleRegistry = LifecycleRegistry(this)

    init {
        lifecycleRegistry.currentState = Lifecycle.State.INITIALIZED
        lifecycleRegistry.currentState = Lifecycle.State.CREATED
    }

    fun onAppear() {
        lifecycleRegistry.currentState = Lifecycle.State.STARTED
        lifecycleRegistry.currentState = Lifecycle.State.RESUMED
    }

    fun onDisappear() {
        lifecycleRegistry.currentState = Lifecycle.State.STARTED
        lifecycleRegistry.currentState = Lifecycle.State.CREATED
    }

    fun onDestroy(){
        lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
    }

    override fun getLifecycle(): Lifecycle {
        return lifecycleRegistry
    }
}

So the question is Where to call the "onDestroy()" method of my custom ViewHolder?

This is how I am using it:

class Observer : LifecycleObserver {
    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    fun onCreate() {}

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroy() {}
}


class CountryViewHolder(
        private val mBinding: ItemCountryBinding,
        onCountryHeaderClicked: (Int, Boolean) -> Unit,
        onNearbyChallengeClicked: (Long?) -> Unit,
    ) : LifecycleViewHolder(mBinding.root){

        private var status = STATUS.COLLAPSED
        private var layoutStatus = HEIGHT.PENDING

        private val viewHolderObserver = Observer()
        private val challengesForCountryObserver = Observer<PagingData<ChallengeFullData>> {
            lifecycle.coroutineScope.launch {
                (mBinding.itemCountryChallengeList.adapter as ChallengesByCountryAdapter).submitData(it)
            }
        }

        init {
            lifecycle.addObserver(viewHolderObserver)
        }

        fun bind(data: TheCounterForCountryRel?, positionExpandedCountry: Int, viewModel: WeakReference<ViewModel>){
            val resources = itemView.context.resources

            if(data == null){
                mBinding.apply {
                    itemCountryIconExpand.rotation = 0F
                    itemCountryLabelChallengesQtd.text = resources.getString(R.string.item_country_challenges_qtd_placeholder)
                    itemCountryLabelName.text = resources.getString(R.string.item_country_label_name_placeholder)
                    

                    ... omitted for simplification ...

                    Picasso.get()
                        .load("")
                        .into(itemCountryPictureFlag)
                }
            }else{
                viewModel.get()?.getLiveDataForCountry(data.country.id)
                    ?.observe(this, challengesForCountryObserver)

                .... omitted for simplification ...
            }
        }
    }

Solution

  • this is my repo you can see there how i did it.first you have to pass parentLifecycleOwner to adapter then inside init block you have to observe the lifecycle of viewholder.

    init {
        lifecycle.lifecycle.addObserver(object : LifecycleObserver {
            @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
            fun onDestroy() {
                recyclerView?.let { parent ->
                    val childCount = parent.childCount
                    for (i in 0 until childCount) {
                        parent.getChildAt(i)?.let {
                            (parent.getChildViewHolder(it) as MyViewHolder)
                                .run {
                                    onDestroy()
                                }
                        }
                    }
                }
    
            }
    
            @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
            fun onStop() {
                recyclerView?.let { parent ->
                    val childCount = parent.childCount
                    for (i in 0 until childCount) {
                        parent.getChildAt(i)?.let {
                            (parent.getChildViewHolder(it) as MyViewHolder)
                                .run {
                                    onStop()
                                }
                        }
                    }
                }
            }
    
    
            @OnLifecycleEvent(Lifecycle.Event.ON_START)
            fun onStart() {
                recyclerView?.run {
                    if (layoutManager is LinearLayoutManager) {
                        val first =
                            (layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
                        val last =
                            (layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
                        if (first in 0..last)
                            for (i in first..last) {
                                findViewHolderForAdapterPosition(i)?.let {
                                    (it as MyViewHolder).onStart()
                                }
                            }
                    }
                }
            }
        })
    
    }
    

    https://github.com/abhinavjiit/baseRecyclerview/blob/master/app/src/main/java/com/example/pristencare/adapter/RecyclerViewImageAdapter.kt