使用Android ViewModel

3

当我正在使用 ViewModel 测试一小段代码时,我注意到一个小的逻辑问题,并想知道您如何解决这个问题。让我们看看这个小类:

public class MyDataViewModel extends ViewModel {
    MutableLiveData<List<MyData>> mData = new MutableLiveData<>();

    public ContactsViewModel() {}

    public void setData(List<MyData> data) {
        mData.postValue(data);
    }

    public LiveData<List<MyData>> getData() {
        return mData;
    }
}

问题是,如果你在“LiveData observer”被注册之前使用“setData()”更改“LiveData”,那么你的观察者在注册后不会触发。尽管这似乎很合理,但在编写异步代码时可能会导致问题,而且您不知道“setData()”是否会在注册观察者之前被调用。我想知道在注册观察者时如何检查数据是否已经设置。只需检查getData().getValue() != null吗?
另一个问题是数据同步。我需要牢记LiveData同步(与所有其他普通数据一样),还是LiveData在内部处理?例如,setData()getData().getValue()可以同时调用吗?
最后一个问题,似乎每当设置值时,“LiveData”观察者都会触发,即使它是相同的(例如,如果您在LoaderonLoadFinished()中使用setData(),则每次活动重新创建时都会调用setData())。这将导致观察者用相同的数据被调用两次。我想知道防止这种情况的最佳方法是什么。检查ViewModel中的数据是否与我们拥有的数据类似,并且不要再次设置值?

1
LiveData + ViewModel 的存在是为了让你不需要使用 Loader,我认为。 - EpicPandaForce
@EpicPandaForce 不,它们的存在是为了解决片段和活动之间的数据共享问题。使用ViewModel进行片段间数据共享非常好,但我不认为它们会影响Loader的使用。 - Afshin
为什么要使用 MutableLiveData<List<MyData>> 而不是 MutableLiveData<ArrayList<MyData>> - Afshin
不允许MutableLiveData内部数据可变并不是一个好的实践,因此通常将数组列表包装在Collections.unmodifiableList()中更好。除非您使用set/postValue,否则它不应该是可修改的,而ArrayList并不能防止这种情况。因此,MutableLiveData<List<T>>可以实现这一点:D - EpicPandaForce
2个回答

2
问题在于,如果您在LiveData观察者注册之前使用setData()更改LiveData,则在注册观察者后,您的观察者将不会触发。
根据LiveData中observe()方法的文档:
/**
 * Adds the given observer to the observers list within the lifespan of the given
 * owner. The events are dispatched on the main thread. 
 * If LiveData already has data set, it will be delivered to the observer.

如果LiveData已有数据集,它将传递给观察者。如果没有,则可能是个bug。


另一个问题是数据同步。我需要注意LiveData的同步吗(就像所有其他普通数据一样),还是LiveData在内部处理?

LiveData通过setValue()(仅主线程)或postValue()方法修改LiveData中的数据时会通知所有订阅者。

为了使LiveData保持最新状态,必须有某些内容使用最新数据更新其值,以便向所有订阅者“广播”此值。

例如,当您从Room的DAO提供LiveData<T> getData();时,Room会检查T表是否被修改。如果已修改,则它会更新使用T表的所有LiveData

您基本上永远不需要调用getValue()


这将导致观察者两次使用相同的数据。我想知道防止这种情况的最佳方法是什么。

如果您正确设置了差异或观察逻辑,则这应该无关紧要。

public class TasksViewModel
        extends BaseObservable
        implements Observer<List<Task>> {    
    @Inject
    TasksViewModel(...) {
       //...
    }

    private LiveData<Task> liveResults;

    public void start() {
        liveResults = tasksRepository.getTasks();
        liveResults.observeForever(this);
    }

    public void stop() {
        liveResults.removeObserver(this);
    }

    @Override
    public void onChanged(@Nullable List<Task> tasks) {
        if(tasks == null) {
            return; // loading...
        }
        items.clear();
        items.addAll(tasks); // <-- whether it is same data or not is irrelevant.
        notifyPropertyChanged(BR.empty);
    }
}

使用Android的ViewModel,您很可能会将`start()`移动到构造函数中(使用`ViewModelProvider.Factory`),并将`stop()`移动到`onCleared()`中。

如果LiveData已经有数据集,则将其传递给观察者。如果没有,则可能是一个错误。我也见过这种情况。但我猜它的意思是如果你已经注册了观察者。例如,如果您的应用程序重新创建,您以前已经注册了观察者,因此您的观察者将被调用一次,因为其数据先前已更改。但是当应用程序第一次运行时,您不会收到通知,因为观察者是在设置数据后注册的。或者至少,这就是我的经验。 - Afshin

0

如果有人需要一个只在setData()调用时调用观察者的LiveData,我写了这个小类。我自己使用它来防止在你决定在onLoaderFinished()setData()并且活动重新创建时多次调用观察者。

public class MutableSemiLiveData<T> extends MutableLiveData<T> {
    private volatile boolean mManualSetting = false;

    @Override
    protected void onInactive() {
        super.onInactive();
        mManualSetting = false;
    }

    @Override
    public void setValue(T value) {
        mManualSetting = true;
        super.setValue(value);
    }

    @Override
    public void postValue(T value) {
        mManualSetting = true;
        super.postValue(value);
    }

    @Override
    public void observe(LifecycleOwner owner, final Observer<T> observer) {

        super.observe(owner, new Observer<T>() {
            @Override
            public void onChanged(@Nullable T t) {
                if (mManualSetting) {
                    observer.onChanged(t);
                }
            }
        });
    }
}

请记住,只有在您想通过设置值来触发观察者时才使用此类,它不会在活动重新创建时触发。

最好的问候


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接