Reputation: 837
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
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
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