LiveData update on object field change
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
LiveData update values when field of object change
Try this:
You need to make your model class extend BaseObservable.
public class Object extends BaseObservable {
private String name;
private float internalvalue;
private float in1;
private float out1;
private float out2;
public Object(String name, float internalvalue) {
this.name = name;
this.internalvalue = internalvalue;
}
public float getOut1() {
return this.out1;
}
public void setOut1(float out1) {
this.out1 = out1;
notifyChange();
}
public float getOut2() {
return this.out2;
}
public void setOut2(float out2) {
this.out2 = out2;
notifyChange();
}
public void setIn1(float in1) {
this.in1 = in1;
}
private void performSomething(float internalvalue, float in1) {
SubClassSingleton.performSomething(internalvalue, in1, new SubClassSingletonListener() {
@Override
public void onSuccess(float out1, float out2) {
setOut1(out1);
setOut2(out2);
}
});
}
}
And in View, you cannot bind float directly to view. It has to be string, so bind like this:
<layout ...
<data>
<variable
name="viewModel"
type="app.example.MainViewModel"/>
</data>
<LinearLayout
...>
<EditText
android:text='@={""+viewModel.obj.in1}'
.../>
<TextView
android:text='@{""+viewModel.obj.out1}'
.../>
<TextView
android:text='@{""+viewModel.obj.out2}'
.../>
</LinearLayout>
</layout>
Changes to LiveData of composite objects
The fact is that the onChanged
method of the LiveData
's Observer
is only called when the containing value of the LiveData
is being set using setValue()
or postValue()
method. There is no mechanism to observe fields inside an object that is held by a LiveData
. As a result, by changing the value of fields inside object-A
, the observer shouldn't be notified.
On the other hand, as you know, the methods that are provided by Room to query on a db are able to return LiveData<SomeType>
instead of SomeType
. Under the hood, Room creates and registers a ContentObserver
on your query's table(s) to get aware of changes in it. So, every time a change occurs on the data, Room gets notified using the mentioned ContentObserver
, then fetches the query result again and post it on the LiveData
.
How to update LiveData value?
Finally I found the solution after the struggle of 3 days,
For updating the value of LiveData<PagedList>
we can use MediatorLiveData
, as follows, inside your ViewModel
class:
public LiveData<PagedList<RecipeListPojo>> liveData;
private MediatorLiveData<PagedList<RecipeListPojo>> mediatorLiveData;
public RecipeListViewModel(@NonNull Application application) {
super(application);
mediatorLiveData = new MediatorLiveData<>();
}
public MediatorLiveData<PagedList<RecipeListPojo>> init(RecipeDao recipeDao, RecipeFrom recipeFrom, String orderBy) {
liveData = new LivePagedListBuilder(recipeDao.getAllRecipesList(simpleSQLiteQuery), 6).build();
mediatorLiveData.addSource(liveData, new Observer<PagedList<RecipeListPojo>>() {
@Override
public void onChanged(@Nullable PagedList<RecipeListPojo> recipeListPojos) {
mediatorLiveData.setValue(recipeListPojos);
}
});
return mediatorLiveData;
}
Further details can be found here about MediatorLiveData
How can i update a field of a ListItem (LiveData)
LiveData
is just a container holding a reference to your data and notifying possible observer of changes. So as far as LiveData
is concerned this is the best you can do.
However LiveData
does't support molecular changes so every change may results in the entire list invalidating and redrawn. It is strongly suggested that you use a different method if you have lot of changes in data (like real-time applications).
LiveData - How to observe changes to List inside object?
MutableLiveData
will only notify view when it value changes, e.g. when you place other value which is different from an old one. That's why user.value = user.value!!.copy(pets = newList)
works.
MutableLiveData
cannot know when one of the fields was changed, when they're simple basic types/classes.
But you can make pets
a mutable state, in this case live data will be able to notify about changes. Define it like val pets = mutableStateListOf<String>()
.
I personally not a big fan of live data, and code with value!!
looks not what I'd like to see in my project. So I'll tell you about compose way of doing it, in case your project will allow you to use it. You need to define both pets
as a mutable state list of strings, and user
as a mutable state of user.
I suggest you read about compose states in the documentation carefully.
Also note that in my code I'm defining user with delegation, and pets without delegation. You can use delegation only in view model, and inside state holders you cannot, othervise it'll become plain objects at the end.
@Composable
fun TestView() {
val viewModel = viewModel<TestViewModel>()
Column {
// TextField and Button that calls viewModel.addPet(petName)
var i by remember { mutableStateOf(0) }
Button(onClick = { viewModel.addPet("pet ${i++}") }) {
Text("add new pet")
}
LazyColumn {
items(viewModel.user.pets) { pet ->
Text(text = pet)
}
}
}
}
class User {
val pets = mutableStateListOf<String>()
}
class TestViewModel: ViewModel() {
val user by mutableStateOf(User())
fun addPet(petName: String) {
user.pets.add(petName)
}
}
Related Topics
Android: Remove All the Previous Activities from the Back Stack
A Failure Occurred While Executing Com.Android.Build.Gradle.Internal.Tasks
Duplicate Files Copied (Android Studio 0.4.0)
Error Message:This Android Sdk Requires Android Developer Toolkit Version 22.6.1 or Above
How to Include a Username When Storing Email and Password Using Firebase (Baas) in an Android App
Android - Key Dispatching Timed Out
How to Evenly Distribute Buttons Across the Width of a Linearlayout
Disabling User Dragging on Bottomsheet
Passing an Object from an Activity to a Fragment
How to Start Android Application Info Screen Programmatically
Start an Activity from a Fragment
Version Conflict Updating to 8.4.0
How to Resolve "Waiting for Debugger" Message
Pathpattern to Match File Extension Does Not Work If a Period Exists Elsewhere in the File Name
How to Sign an APK with More Than One Certificate
How to Open a PDF Stored Either in Res/Raw or Assets Folder