architecture-components-samples icon indicating copy to clipboard operation
architecture-components-samples copied to clipboard

How much state to put in ViewModel?

Open finneapps opened this issue 8 years ago • 0 comments

Hi. Before android architecture components were released I started to work on a project where I had my own ViewModels that had the same life cycle as a Fragment and the ViewModels state was saved in a StateObject inside a Loader so that the state could survive orientation change. The ViewModel talked to the Fragment through an Interface. This worked fine because the ViewModel and Fragment had the same life cycle. My ViewModel contained all kinds of state. It had a isLoading boolean, isEmptyStateVisible boolean etc. And every time a state changed, I called something like view.notifyIsLoadingChanged(true/false) and in this case the Fragment would show or hide a spinner.

Now I am about to change my implementation to use the new ViewModels together with LiveData. The fastest way to implement LiveData is to change my implementation of the UI interface that the ViewModel is using. So I could keep my current implementation and just add this UI interface implementation:

public class LiveDataProductReviewSheetUI extends LiveDataUI implements ProductReviewSheetUI {
    public final MutableLiveData<ReviewViewModelState> ratingDescChanged = new MutableLiveData<>();
    public final MutableLiveData<ReviewViewModelState> ratingChanged = new MutableLiveData<>();
    public final MutableLiveData<ReviewViewModelState> reviewChanged = new MutableLiveData<>();
    public final MutableLiveData<ReviewViewModelState> reviewValid = new MutableLiveData<>();
    public final MutableLiveData<ReviewViewModelState> expandReview = new MutableLiveData<>();
    public final MutableLiveData<ReviewViewModelState> reviewQuestion = new MutableLiveData<>();
    public final MutableLiveData<ReviewViewModelState> reviewCreated = new MutableLiveData<>();
    public final MutableLiveData<ReviewViewModelState> showMsg = new MutableLiveData<>();
    public final MutableLiveData<ReviewViewModelState> dismiss = new MutableLiveData<>();

    public void observe(LifecycleOwner owner, final ProductReviewSheetUI observer) {
        ratingDescChanged.observe(owner, state -> observer.onRatingDescriptionChanged(state));
        ratingChanged.observe(owner, state -> observer.onRatingChanged(state));
        reviewChanged.observe(owner, state -> observer.onReviewChanged(state));
        reviewValid.observe(owner, state -> observer.onHasValidReviewDataChanged(state));
        expandReview.observe(owner, state -> observer.onExpandReviewFieldHasChanged(state));
        reviewQuestion.observe(owner, state -> observer.onProductReviewQuestionChanged(state));
        reviewCreated.observe(owner, state -> observer.onReviewCreated(state));
        showMsg.observe(owner, state -> observer.onShowMessage(state));
        dismiss.observe(owner, state -> observer.onCloseView());
    }

    @Override
    public void onRatingDescriptionChanged(ReviewViewModelState state) {
        ratingDescChanged.setValue(state);
    }

    @Override
    public void onRatingChanged(ReviewViewModelState state) {
        ratingChanged.setValue(state);
    }

    @Override
    public void onReviewChanged(ReviewViewModelState state) {
        reviewChanged.setValue(state);
    }

    @Override
    public void onHasValidReviewDataChanged(ReviewViewModelState state) {
        reviewValid.setValue(state);
    }

    @Override
    public void onExpandReviewFieldHasChanged(ReviewViewModelState state) {
        expandReview.setValue(state);
    }

    @Override
    public void onProductReviewQuestionChanged(ReviewViewModelState state) {
        reviewQuestion.setValue(state);
    }

    @Override
    public void onReviewCreated(ReviewViewModelState state) {
        reviewCreated.setValue(state);
    }

    @Override
    public void onShowMessage(ReviewViewModelState state) {
        showMsg.setValue(state);
    }

    @Override
    public void onCloseView() {
        dismiss.setValue(dismiss.getValue());
    }
}

The LiveDataUI class that this class extends has even more methods like:

public final MutableLiveData<Boolean> showLoading = new MutableLiveData<>();
public final MutableLiveData<Boolean> showEmptyState = new MutableLiveData<>();

With this implementation I will end up with a lot of MutableLiveData objects and it does not feel right. Am I putting to much state into my ViewModel? My idea was to have all logic inside the ViewModel so that I could write tests where I could verify that isLoading is true when data is being loaded, and if there is no data returned from Api, the isEmptyState is true etc.

I have also noticed that if i call setValue(state); multiple times within a very short time on the same MutableLiveData object, the onChanged method is only invoked once. Is that correct?

finneapps avatar Aug 17 '17 07:08 finneapps