对象字段更改的 LiveData 更新

Posted

技术标签:

【中文标题】对象字段更改的 LiveData 更新【英文标题】:LiveData update on object field change 【发布时间】:2018-06-09 18:18:51 【问题描述】:

我正在使用带有 LiveData 的 android MVVM 架构。我有一个这样的对象

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;
    

我的视图模型看起来像这样

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 ");
    

如何确保用户对象中的某些字段发生更改时,观察者会收到通知?顺便说一句,将这些数据保存在单独的对象中并且不要在我的 ViewModel 中使用诸如字符串之类的主要值对我来说很重要。

【问题讨论】:

我徘徊在这篇文章的第一个答案与第二个答案的区别,第二个看起来更容易。 【参考方案1】:

如果您这样做,您应该使用setValue 通知您的观察者 user.getValue().setFirstName(user.getValue().getFirstName() + " A ");您的观察员不会收到通知!

查看模型

public MutableLiveData<User> getUser() 
    return user;

活动/片段

mModel = ViewModelProviders.of(this).get(InfoViewModel.class);
mModel.getUser().observe(this, s -> 
    // User has been modified
);

在你的活动/片段中的某个地方

这将触发观察者:

mModel.getUser().setValue(user);

如果你想只更新一个对象的一个​​字段而不是更新整个对象,你应该有多个MutableLiveData&lt;String&gt;

// View Model
private MutableLiveData<String>         firstName;
private MutableLiveData<String>         lastName;

//Somewhere in your code
mModel.getFirstName().setValue(user.getValue().getFirstName() + " A ");
mModel.getFirstName().observe(this, s -> 
    // Firstname has been modified
);

【讨论】:

【参考方案2】:

我认为 android 没有为此推荐任何最佳实践。我建议您使用使用更清洁和更少样板代码的方法。

如果您使用 android 数据绑定和LiveData,您可以使用以下方法:

你的 POJO 对象看起来像这样

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);
    

因此,您将已经拥有一个类,该类会在其属性更改时发出通知。因此,您可以在 MutableLiveData 中使用此属性更改回调来通知其观察者。您可以为此创建一个自定义的 MutableLiveData

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());

        
    ;



那么你需要做的就是在你的视图模型中使用这个 CustomMutableLiveData 而不是 MutableLiveData

public class InfoViewModel extends AndroidViewModel 

    CustomMutableLiveData<User> user = new CustomMutableLiveData<>();
-----
-----

因此,通过这样做,您可以通知视图和 LiveData 观察者,而无需对现有代码进行任何更改。希望对你有帮助

【讨论】:

我不知道为什么。在我的课堂上找不到 BR。 @Sup.Ia 重新编译项目生成BR文件 有没有办法在没有数据绑定的情况下做到这一点?我的意思是,像这样的一种方法,您可以在其中选择 liveData 的值何时发生变化。 我正在阅读这篇文章medium.com/@tylerwalker/…,它解释了相同的解决方案,但在 Kotlin 中。但是我无法让它工作。我认为它已经过时了,因为它使用的是生命周期版本 = 1.1.1。如何在 Kotlin 中设置 onPropertyChanged() 内部的值? value = value 无法编译。 如果我们更改为完全不同的值对象,谁取消注册监听器?而且我们不是在每次更改用户对象字段之一时都注册一个新的侦听器对象吗?【参考方案3】:

使用 MVVM 和 LiveData 时,您可以将对象重新绑定到布局,以便触发 UI 上的所有更改。

鉴于“用户”是 ViewModel 中的 MutableLiveData&lt;User&gt;

视图模型

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()
    

活动/片段文件:

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
)

您的布局文件不应更改:

<data>
    <variable
        name="user"
        type="com.project.model.User" />
</data>

...

<TextView
        android:id="@+id/firstname"
        android:text="@user.firstname"
        />

【讨论】:

这行得通!但是重新绑定是否有任何性能缺陷? 它比标记为最佳更简单、更清晰。 这对我来说似乎是错误的。使用 LiveData 不重新绑定的重点不是吗? 可以绑定viewModel,而不是绑定用户,就不需要重新绑定了。 这不是重新绑定,它只是刷新变量,这将反映在 UI 上的变化。在这里看不到问题。【参考方案4】:

如果您使用 Kotlin 和 LiveData,我可以为您提供 2 种方法 - 有和没有扩展功能:

无扩展功能

liveData.value = liveData.value?.also  it ->
    // Modify your object here. Data will be auto-updated
    it.name = "Ed Khalturin"
    it.happyNumber = 42

相同,但有扩展名

// 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

【讨论】:

好主意。我会通过使用actions: MutableLivedata&lt;T&gt;.() -&gt; Unit 来进一步增强它,这样可以在没有it 的情况下编写 lambda 优雅。我错过的一点是,设置完全相同的值对象会触发对观察者的通知。希望它不会在未来的版本中“优化”。 它应该如何工作?这只会触发一次(在初始化期间),而不是每次值更改时 它在添加到 .innerArray 时表现如何?【参考方案5】:

如何确定用户对象中的某些文件何时更改观察者 得到通知?顺便说一句,将这些数据保存在 单独的对象,而不是在我的 视图模型。

您可以使用 androidx.lifecyle.Transformation 类来监控各个字段。

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

您照常更新用户,并监听名字/姓氏以监控这些字段的变化。

【讨论】:

【参考方案6】:

来自reddit - @cedrickc's answer :

为 MutableLiveData 添加一个扩展函数:

fun <T> MutableLiveData<T>.modifyValue(transform: T.() -> T) 
   this.value = this.value?.run(transform)

【讨论】:

fun MutableLiveData.setField(transform: T.() -> Unit) this.value = this.value?.apply(transform) 【参考方案7】:

2021 年简单易行的解决方案(也适用于StateFlow):

这里的重点是,.value必须改成Different Content (hash code),这样绑定的ui才会更新。

//Your model
data class Student(val name: String)

//Your ViewModel class
fun onStudentClick() 
    val newName = "Kate"    //change field/property

    _student.value = _student.value.copy(name = newName)    //must be different content (hash code)!!

就是这样。

PS:是的,我知道copy() 会返回一个不同的对象(引用),但内容相同,在这里不起作用。您必须更改内容。

【讨论】:

以上是关于对象字段更改的 LiveData 更新的主要内容,如果未能解决你的问题,请参考以下文章

Jetpack之LiveData扩展MediatorLiveData

如何创建可以保存状态的不可为空的 LiveData

带有TextWatcher和LiveData的EditText中的光标位置?

如何使用 Room 和 LiveData 保持 RecyclerView 顺序的更改?

更改 T 的属性时如何使 LiveData<MutableList<T>> 更新?

为啥 LiveData 没有从协程更新?