Search code examples
androidkotlinandroid-livedata

How to observe LiveData inside Custom View


How should I observe a LiveData in a Custom View. I tried to cast it's context as lifecycleOwner but it makes some problems and doesn't work in all cases. I tried to put a setter but it doesn't work either


Solution

  • Views do not have lifecycle on their own. There are 3 approaches that I personally use, they are actually the same thing but one of them is adding a lifecycle while the others are without a lifecycle.

    class MyCustomView  @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
    ): View(context, attrs, defStyleAttr){
        
        
        val myObserver = Observer<Long>{
            //whatever
        }
    
        override fun onAttachedToWindow() {
            super.onAttachedToWindow()
            liveData.observeForever(myObserver)
        }
    
        override fun onDetachFromWindow() {
            super.onDetachFromWindow()
            liveData.removeObserver(myObserver)
        }
    }
    

    This method manually observe/remove on attach/detach to window. I prefer it when I'm observing few livedata and it's simple/limited


    Another option is to turn our custom view into a LifecycleOwner. I recommend this method for BaseCustomViews and some extremely huge and complicated views (like a Map navigation view). Also, remember that you need to manually notify the view about its parent getting destroyed (you can call the method directly or use a view tree navigation to call this function for all view which are the child of BaseCustomView).

    abstract class BaseCustomView  @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
    ): View(context, attrs, defStyleAttr), LifecycleOwner {
        protected val lifecycleRegistry = LifecycleRegistry(this);
    
        override fun getLifecycle() = lifecycleRegistry
        override fun onAttachedToWindow() {
            super.onAttachedToWindow()
            lifecycleRegistry.currentState = Lifecycle.State.RESUMED
        }
    
        override fun onDetachedFromWindow() {
            super.onDetachedFromWindow()
            lifecycleRegistry.currentState = Lifecycle.State. CREATED
        }
    
        @CallSuper
        open fun destroyLifecycle(){
            lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
        }
    
        
        val myObserver = Observer<Long>{
            //whatever
        }
    
        init{
            liveData.observe(this, myObserver}
        }
    }
    

    If you prefer the first method, another option can be combining this 2 ideas, having a BaseCustomView which enables its children to observe LiveData easily.

    abstract class BaseCustomView  @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
    ): RelativeLayout(context, attrs, defStyleAttr) {
    
        //a list to hold the list of observers and their LiveData
        private val autoObservers = ArrayList<Pair<LiveData<*>, Observer<*>>>()
    
        override fun onAttachedToWindow() {
            super.onAttachedToWindow()
            for((liveData, observer) in autoObservers){
                liveData.observeForever(observer as Observer<in Any>)
            }
        }
    
        override fun onDetachedFromWindow() {
            super.onDetachedFromWindow()
            for((liveData, observer) in autoObservers){
                liveData.removeObserver(observer as Observer<in Any>)
            }
        }
    
        protected fun<T : Any> LiveData<T>.observe( observer: Observer<T> ){
            autoObservers.add(this to observer)
    
            //if it's not attached, onAttachedToWindow will do the observation
            if(isAttachedToWindow){
                observeForever(observer)
            }
        }
    }