2

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!

Laufwunder
  • 773
  • 10
  • 21
  • Yes, your last assumption is correct. Your LiveData will only trigger if you set new value to it. if you change some nested variables on your class, then it won't notify that changes for you unless you provide new value to it explicitly. Advantage of using LiveData is that it's lifecycle aware component. It respects lifecycle of activity/fragment. – Jeel Vankhede Oct 20 '20 at 14:14
  • Yes that is correct, but unless you do the same (`_obj.value = classAObj`) in future it won't notify observers. – Jeel Vankhede Oct 20 '20 at 14:28

2 Answers2

1

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.

Laufwunder
  • 773
  • 10
  • 21
0

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

Louis
  • 364
  • 3
  • 11