androidkotlinabstract-classandroid-livedatamediatorlivedata

How to use MediatorLiveData with an abstract source


I have an abstract class, with a MediatorLiveData object in it. This object has a number of sources, one of which depends on the childs class, and is abstract in the parent class. Adding the sources in an init block causes a NullPointerException at runtime, because at the time the init block adds the source, it is still abstract (or so I have been led to believe).

Is there a way to use an abstract LiveData as a source for a MediatorLiveData without having to set that source in a child class? I just want to override val and be done with it, since I definitely will forget to call the addSources() function at some time in the future.

(I am aware that this example is not the most useful way to do this exact thing, but I didn't want to add unneccesary complexity)

Example:

abstract class MyClass: ViewModel(){
    private val _myMediator = MediatorLiveData<String>()
    
    protected abstract val mySource: LiveData<String>
    
    val myObservable: LiveData<String>
        get() = _myMediator
    
    // This will cause a NullPointerException at runtime
    init{
        _myMediator.addSource(mySource){ _myMediator.value = it }
    }
    
    //This should work, but requires this to be called in child class
    protected fun addSources(){
        _myMediator.addSource(mySource){ _myMediator.value = it }
    }
}

class myChild: MyClass(){
    override val mySource = Transformations.map(myRepository.someData) { it.toString() }
    
    // This is where init { addSources() } would be called
}

After reading Stachu's anwser, I decided to go with this, which I didn't test butI think should work:

abstract class MyFixedClass: ViewModel(){
    private val _myMediator: MediatorLiveData<String> by lazy{
        MediatorLiveData<String>().apply{
            addSource(mySource){ this.value = it }
        }
    }

    protected abstract val mySource: LiveData<String>

    val myObservable: LiveData<String>
        get() = _myMediator
}

class MyChild: MyFixedClass(){
    override val mySource = Transformations.map(myRepository.someData) { it.toString() }
}

Solution

  • how about using lazy evaluation, e.g. something like this

    abstract class MyClass : ViewModel() {
        private val _myMediator = MediatorLiveData<String>()
        private val _mySource: LiveData<String> by lazy { mySource() }
        protected abstract fun mySource(): LiveData<String>
        val myObservable: LiveData<String>
            get() = _myMediator
        init {
            _myMediator.addSource(_mySource) { _myMediator.value = it }
        }
    }
    
    class myChild : MyClass() {
        override fun mySource() = Transformations.map(myRepository.someData) { it.toString() }
    }