LiveData update on object field change
Solution 1
I don't think there is any best practice as such recommended by android for this. I would suggest you to use the approach which uses cleaner & less boilerplate code.
If you are using android data binding along with LiveData
you can go with the following approach:
Your POJO object would look something like this
public class User extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
@Bindable
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}
So you would be already having a class which notifies whenever its property changes. So you can just make use of this property change callback in your MutableLiveData to notify its observer. You can create a custom MutableLiveData for this
public class CustomMutableLiveData<T extends BaseObservable>
extends MutableLiveData<T> {
@Override
public void setValue(T value) {
super.setValue(value);
//listen to property changes
value.addOnPropertyChangedCallback(callback);
}
Observable.OnPropertyChangedCallback callback = new Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
//Trigger LiveData observer on change of any property in object
setValue(getValue());
}
};
}
Then all you need to do is use this CustomMutableLiveData instead of MutableLiveData in your View Model
public class InfoViewModel extends AndroidViewModel {
CustomMutableLiveData<User> user = new CustomMutableLiveData<>();
-----
-----
So by doing this you can notify both view & LiveData observer with little change to existing code. Hope it helps
Solution 2
When using MVVM and LiveData, you can re-bind the object to the layout so it will trigger all changes on the UI.
Given "user" is a MutableLiveData<User>
in the ViewModel
ViewModel
class SampleViewModel : ViewModel() {
val user = MutableLiveData<User>()
fun onChange() {
user.value.firstname = "New name"
user.value = user.value // force postValue to notify Observers
// can also use user.postValue()
}
}
Activity/Fragment file:
viewModel = ViewModelProviders
.of(this)
.get(SampleViewModel::class.java)
// when viewModel.user changes, this observer get notified and re-bind
// the user model with the layout.
viewModel.user.observe(this, Observer {
binding.user = it //<- re-binding user
})
Your layout file shouldn't change:
<data>
<variable
name="user"
type="com.project.model.User" />
</data>
...
<TextView
android:id="@+id/firstname"
android:text="@{user.firstname}"
/>
Solution 3
If you are using Kotlin and LiveData, I can offer you 2 ways - with and without extension fucntion:
Without extension function
liveData.value = liveData.value?.also { it ->
// Modify your object here. Data will be auto-updated
it.name = "Ed Khalturin"
it.happyNumber = 42
}
Same, but with extension
// Extension. CopyPaste it anywhere in your project
fun <T> MutableLiveData<T>.mutation(actions: (MutableLiveData<T>) -> Unit) {
actions(this)
this.value = this.value
}
// Usage
liveData.mutation {
it.value?.name = "Ed Khalturin"
it.value?.innerClass?.city= "Moscow" // it works with inner class too
}
Solution 4
From reddit - @cedrickc's answer :
add an extension function to MutableLiveData:
fun <T> MutableLiveData<T>.modifyValue(transform: T.() -> T) {
this.value = this.value?.run(transform)
}
Solution 5
How can I make sure when some filed in user object changes observers get notified? BTW it is important to me to keep this data in the separate object and not use primary values like Strings in my ViewModel.
You can use androidx.lifecyle.Transformation class to monitor for individual fields.
val user = MutableLiveData<User>();
//to monitor for User.Name
val firstName: LiveData<String> = Transformations.map(user) {it.firstName}
val lastName: LiveData<String> = Transformations.map(user) {it.lastName}
you update user as per normal, and listen for firstname/lastname to monitor for changes in those fields.
Alireza Ahmadi
Checkout my LinkedIn profile for more info https://www.linkedin.com/in/alzahm/
Updated on July 05, 2022Comments
-
Alireza Ahmadi almost 2 years
I'm using Android MVVM architecture with LiveData. I have an object like this
public class User { private String firstName; private String lastName; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } }
And my view model looks like this
public class InfoViewModel extends AndroidViewModel { MutableLiveData<User> user = new MutableLiveData<>(); public InfoViewModel(@NonNull Application application) { super(application); User user = new User(); user.setFirstName("Alireza"); user.setLastName("Ahmadi"); this.user.setValue(user); } public LiveData<User> getUser(){ return user; } public void change(){ user.getValue().setFirstName(user.getValue().getFirstName() + " A "); } }
How can I make sure when some field in user object changes observers get notified? BTW it is important to me to keep this data in the separate object and not use primary values like Strings in my ViewModel.
-
Sup.Ia over 5 yearsI do not know that why . in my class could not find BR.
-
Abhishek V over 5 years@Sup.Ia Re-build the project to generate BR file
-
MiguelHincapieC over 5 yearsis there a way to do this without data binding? I mean, an approach like this one in where you can choose when the liveData's value has changed.
-
AouledIssa over 5 yearsThis is working! but does re-binding has any performance drawbacks ?
-
Marat over 5 yearsI was reading this article medium.com/@tylerwalker/… that explains same solution but in Kotlin. However I couldn't get it to work. I think it is outdated because it is using lifecycle version = 1.1.1. How to set the value inside of
onPropertyChanged()
in Kotlin?value = value
does not compile. -
Kwnstantinos Nikoloutsos about 5 yearsBR stands for? Anyone knows that?
-
PhilBlais about 5 yearsThe BR is also giving me issues
-
muetzenflo about 5 yearsBR stands for BindingResources. It is created when compiling your project. For every
<variable>
-tag in your data-bound layout xml there will be a reference in BR. Similar to normal resources that are also defined in xml (e.g. R.string.something) -
A.K. almost 5 yearsHi, it works without the CustomMutableLiveData class. Why do i need the class?
-
ejdrian313 almost 5 yearsIt's simpler and more clear approch than marked as best.
-
Crearo Rotar almost 5 yearsThis just seems wrong to me. Isn't the whole point of using LiveData to not rebind?
-
morpheus05 over 4 yearsWho unregisters the listener if we change to a completely different value object? And don't we register a new listener object every time we change one of the User object fields?
-
Sean over 4 yearsInstead of binding user, you can bind viewModel, then you won't need to re-bind.
-
Aleksandar Stefanović over 4 yearsGreat idea. I would enhance it further by using
actions: MutableLivedata<T>.() -> Unit
, to that the lambda could be written withoutit
-
Alireza about 4 yearsbinding viewModel does not work . for me re-binding the object works very well.
-
fullmoon about 4 yearsThis is not rebinding, it just refreshes the variable, which will reflect the change on the UI. Don't see a problem here.
-
Afilu over 3 yearsElegant. The point I missed was that setting the exact same value object triggers the notification to observers. Hopefully it won't be 'optimized' in future releases.
-
Carteră Veaceslav over 3 yearshow it suppose to work? This will be fired only once (during the initialization), not every time the value changes
-
Nick M over 3 yearshow does it behave on additions to an .innerArray ?
-
act262 about 3 yearsfun <T> MutableLiveData<T>.setField(transform: T.() -> Unit) { this.value = this.value?.apply(transform) }
-
Bilawal muzaffar over 2 yearsi think @morpheus05 is right.
-
Alex Bin Zhao over 2 yearsIt is probably better to create new user instead of using existing one
-
Lifes almost 2 yearsThis no longer works.