3

When I was testing a small code using ViewModel, I noticed a small logically correct problem and I wonder how you counter this problem. Let's see this small class:

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

Problem is that if you use setData() to change LiveData before LiveData observer is registered, your observer will not trigger after registering observer. Even though it seems logical, but it can cause problems when you write asynchronous codes, and you don't know if setData() will be called before registering observer or not. I wonder how you check if data is already set when you want to register observer. Just check if getData().getValue() != null?

Another question is data synchronization. Do I need to keep LiveData synchronization in mind (like all other normal data), or LiveData internally handles it? For example, can setData() and getData().getValue() get called at same time?

Last problem, it seems LiveData observers will trigger anytime you set a value, even if it is the same one(For example, if you use setData() in onLoadFinished() of a Loader, setData() will be called every time activity recreates). This will cause observer called twice with same data. I wonder what is best way to prevent this. Check if data in ViewModel is similar to what we have and don't set value again?

4
  • 1
    LiveData + ViewModel exists so that you don't need to use Loader, I think. Commented Jul 30, 2017 at 17:58
  • @EpicPandaForce No, they exist to solve data sharing problem, between fragments and activity. Inter-fragment data sharing is great with ViewModels, but I don't think they affect Loader usage. Commented Jul 30, 2017 at 18:02
  • any special reason for MutableLiveData<List<MyData>> rather than MutableLiveData<ArrayList<MyData>>? Commented Jul 30, 2017 at 18:40
  • It's not really good practice to allow the data inside MutableLiveData to be mutable, so generally wrapping the array list in Collections.unmodifiableList() is better. It shouldn't be modifiable unless you set/postValue, and ArrayList does not prevent that. So MutableLiveData<List<T>> makes that possible :D Commented Jul 30, 2017 at 18:47

2 Answers 2

2

Problem is that if you use setData() to change LiveData before LiveData observer is registered, your observer will not trigger after registering observer

According to documentation of observe() method in LiveData:

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

So If LiveData already has data set, it will be delivered to the observer.. If it doesn't, then that would be a bug.


Another question is data synchronization. Do I need to keep LiveData synchronization in mind (like all other normal data), or LiveData internally handles it?

LiveData notifies all of its subscribers when the data inside LiveData is modified via setValue() (main-thread-only) or postValue() methods.

In order to keep LiveData up to date, something must update its value with the latest data so that it would "broadcast" this to all its subscribers.

For example, when you provide LiveData<T> getData(); from Room's DAO, then Room checks if table for T is modified. If it is modified, then it updates all LiveData that uses table for T.

You shouldn't need to call getValue(), pretty much ever.


This will cause observer called twice with same data. I wonder what is best way to prevent this.

If you set up diffing or your observation logic properly, this should not matter.

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

With Android's ViewModel, you'd most likely move start() to the constructor (using ViewModelProvider.Factory), and stop() to onCleared().

Sign up to request clarification or add additional context in comments.

1 Comment

So If LiveData already has data set, it will be delivered to the observer.. If it doesn't, then that would be a bug., I have seen this too. But I guess it means that if you have already registered observer. For example, if your app recreates, you have previously had registered an observer, so your observer will be called once because its data have previously changed. But when app run for the first time, you won't get notified because observer is registered after setting data. or at least, that's what I have experienced.
0

I case someone need a LiveData which only calls observer on setData(), I wrote this small class. I used it myself to prevent multiple observer calls when you decide to setData() on onLoaderFinished() and activity recreates.

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

Just remember, use this class only when you want to trigger observers by setting value and it doe snot trigger on activity recreation.

Best Regards

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.