Laufwunder
Laufwunder

Reputation: 837

Android LiveData - observe complex/nested objects

I am working on a android project with MVVM structure. I want to use LiveData as recommended. In the samples there are always just simple objecttypes e.g. String. But I want to put an more complex/nested objecttype into LiveData.
For example an objectstructure like this:

class ClassA {
    private var testVarB = ClassB()

    fun getTestVarB(): ClassB {
        return this.testVarB
    }

    fun setTestVarB(classB: ClassB) {
        this.testVarB = classB
    }

    fun setTxt(str: String) {
        this.testVarB.getTestVarC().setStr(str)
    }

}

class ClassB {
    private var testVarC = ClassC()

    fun getTestVarC(): ClassC {
        return this.testVarC
    }

    fun setTestVarB(classC: ClassC) {
        this.testVarC = classC
    }
}

class ClassC {
    private var str: String = "class C"

    fun getStr(): String {
        return this.str
    }

    fun setStr(str: String) {
        if (str != this.str) {
            this.str = str
        }
    }
}

and my ViewModel looks like this:

class MyViewModel : ViewModel() {

    var classAObj= ClassA()

    private var _obj: MutableLiveData<ClassA> = MutableLiveData()
    val myLiveData: LiveData<ClassA> = _obj

    init {
        _obj.value = classAObj
    }
    
}

and the LiveDataObject is observed in the fragment:

class FirstFragment : Fragment() {

    private var viewModel = MyViewModel()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        ...
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewModel.myLiveData.observe(
            requireActivity(),
            Observer<ClassA>() {
                // should get fired!
                Log.d("TAG", "update view")
            })

    }
}

So if the variable str of ClassC changes the callback should get executed. I am looking for a smart and simple solution.

I just found this similar post: LiveData update on object field change
This example got a depth of 1. But I am looking for a solution with arbitrarily depth.

The fact that I can not find a sample of the solution for my problem makes me suspicious. So I guess my approach is kind of wrong or bad practice anyway. Maybe I should look for a way breaking things down and observe just simple objects.

Has anyone a solution or opinion to this?

Thanks for your help!

Upvotes: 3

Views: 1967

Answers (2)

Laufwunder
Laufwunder

Reputation: 837

Here is the solution i have worked out:

I am using the PropertyAwareMutableLiveData class from here: LiveData update on object field change

class PropertyAwareMutableLiveData<T : BaseObservable> : MutableLiveData<T>() {
    private val callback = object : Observable.OnPropertyChangedCallback() {
        override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
            value = value
        }
    }

    override fun setValue(value: T?) {
        super.setValue(value)

        value?.addOnPropertyChangedCallback(callback)
    }
}

Based on this I extended the model with an iterface/abstract class.

abstract class InterfaceObservable : BaseObservable() {

    open fun setNewString(s: String) {
        notifyPropertyChanged(BR.str)
    }

}

class ClassA : InterfaceObservable() {
    private var testVarB = ClassB()

    fun getTestVarB(): ClassB {
        return this.testVarB
    }

    fun setTestVarB(classB: ClassB) {
        this.testVarB = classB
    }

    override fun setNewString(s: String) {
        super.setNewString(s)
        this.testVarB.setNewString(s)
    }

}

class ClassB {
    private var testVarC = ClassC()

    fun getTestVarC(): ClassC {
        return this.testVarC
    }

    fun setTestVarB(classC: ClassC) {
        this.testVarC = classC
    }

    fun setNewString(s: String) {
        this.testVarC.setStr(s)
    }
}

class ClassC : BaseObservable() {

    @Bindable
    private var str: String = "class C"

    fun getStr(): String {
        return this.str
    }

    fun setStr(str: String) {
        if (str != this.str) {
            this.str = str
        }
    }
}

In my ViewModel I use the PropertyAwareMutableLiveData class.

class MyViewModel() : ViewModel() {

    var classAObj: ClassA = ClassA()

    val myLiveData = PropertyAwareMutableLiveData<ClassA>()

    init {
        myLiveData.value = classAObj
    }

}

In the Fragment I can observe the LiveData object. If ClassC.str changes the observer will get notified and can change the UI.

class MyFragment : Fragment() {

    private lateinit var viewModel: MyViewModel

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.myLiveData.observe(
            viewLifecycleOwner,
            Observer<ClassA> {
                Log.d("TAG", "change your UI here")
            })
    }
}

Every property which is relevant in your UI, should only be changeable over the interface given by the class InterfaceObservable. Thats the reason why this is not a perfect solution. But maybe it is reasonable in your case.

Upvotes: 1

Louis
Louis

Reputation: 374

The issue is from the way you create your ViewModel. You can't directly instantiate it. If you use fragment-ktx artifact you can do like that :

        private val model: SharedViewModel by activityViewModels()

The fragment has his own lifecycle. So you should replace requireActivity() by viewLifeCycleOwner

viewModel.myLiveData.observe(
        viewLifeCycleOwner,
        Observer<ClassA>() {
            // should get fired!
            Log.d("TAG", "update view")
        })

More information here: https://developer.android.com/topic/libraries/architecture/viewmodel#sharing

Upvotes: 0

Related Questions