对象字段更改时的 LiveData 更新

LiveData update on object field change

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

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 中使用像字符串这样的主要值对我来说很重要。

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

查看模型

public MutableLiveData<User> getUser() {
    return user;
}

Activity / 片段

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

你的 activity / 片段中的某处

这将触发观察者:

mModel.getUser().setValue(user);

如果您只想更新一个对象的一个​​字段而不是更新整个对象,您应该有多个 MutableLiveData<String>

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

我认为 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);
    }
}

所以您已经有了一个 class,它会在 属性 发生变化时发出通知。因此,您可以在 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 观察者,而无需对现有代码进行少量更改。希望对你有帮助

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

给定 "user" 是 ViewModel

中的一个 MutableLiveData<User>

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 文件:

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

如果您正在使用 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
}

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.

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

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}

您按正常方式更新用户,并监听 firstname/lastname 以监控这些字段中的变化。

来自reddit - @cedrickc's answer

向 MutableLiveData 添加扩展函数:

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

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

这里的关键点是,.value 必须更改为 不同的内容(哈希码),以便绑定 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() 将 return 一个不同的对象(参考),但内容仍然是同样,它不会在这里工作。您必须更改内容。