Why LiveData setValue or PostValue triggers onChange just once in the view?

sunflower20 picture sunflower20 · Jun 21, 2018 · Viewed 9k times · Source

LiveData setValue should have triggered the onChanged method in the Activity, however it calls only at the first time, after when I try to make paging, it breaks and doesn't call onChanged anymore, though my response is does successful and I see it in the log. What's wrong with setValue/postValue? Is it a bug? Should I implement observer pattern on my own? Whats the point of using LiveData then? My paging doesn't work merely for this already 2-3 days.....

  1. MainActivity class

     public class MainActivity extends AppCompatActivity 
     private MutableLiveData<List<Photo>> mLivePhotos;
     // some code...
    
     @Override
     protected void onCreate(Bundle savedInstanceState) {
        mLivePhotos = loadData();
        mLivePhotos.observe(this, photos -> {
            Log.d(TAG, "onChanged!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
            mProgressBar.setVisibility(View.GONE);
            mPhotos = photos;
            if (mIsInitialCall) {
                initiateAdapter();
                mIsInitialCall = false;
            } else {
                mAdapter.updateList(mPhotos.subList(mPageNumber, mPageNumber + 10));
            }
        });
    
        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                int lastPosition = 
    mLayoutManager.findLastCompletelyVisibleItemPosition();
                Log.d(TAG, "onScrolled - lastPosition: " + lastPosition);
                if (lastPosition == mLayoutManager.getItemCount() - 1) {
                    Log.d(TAG, "onScrolled - End of list?");
                    loadData();
                }
            }
        });
    }
    
    private MutableLiveData<List<Photo>> loadData() {
        Log.d(TAG, "loadData");
        if (mArticleViewModel == null) return null;
        mPageNumber += 10;
        mProgressBar.setVisibility(View.VISIBLE);
        return mArticleViewModel.loadPhotos();
    }
    
  2. ViewModel

    public class ArticleViewModel extends ViewModel {
        private MutableLiveData<List<Photo>> photos;
        private ArticleRepository articleRepository;
    
        public MutableLiveData<List<Photo>> loadPhotos() {
            Log.d(TAG, "getArticleList");
            //TODO; add Dagger 2
            articleRepository = new ArticleRepository();
            photos = articleRepository.getPhotos();
            return photos;
        }
    
  3. Repository

    public class ArticleRepository {
    
        public MutableLiveData<List<Photo>> getPhotos() {
            final MutableLiveData<List<Photo>> result = new MutableLiveData<>();
            Log.d(TAG, "getResults");
            ApiService.getService().getPhotos().enqueue(new Callback<List<Photo>>() {
            @Override
            public void onResponse(Call<List<Photo>> call, Response<List<Photo>> response) {
                Log.d(TAG, "onResponse");
                if (response.isSuccessful()) {
                    Log.d(TAG, "isSuccessful");
                    result.postValue(response.body());
                }
            }
    
            @Override
            public void onFailure(Call<List<Photo>> call, Throwable t) {
                Log.d(TAG, "onFailure: " + t.getMessage() + "\n" +  t.getStackTrace());
            }
        });
        return result;
    }
    

Answer

Tiago Redaelli picture Tiago Redaelli · Jun 21, 2018

The Activity shouldn't have any MutablieLiveData member variables, that should be inside the ViewModel.

The reason for why it only works the first time, is because the first time you observe something it notifes as changed, however because your arrangement is incorrect it never updates again. That is, because ArticleRepository is recreated again inside your ViewModel with a new set of MutableLiveData, the previous one you subscribed to is no longer relevant - and you only subscribe once onCreate().

You should separate bind from async-tasks such as loadData() they are not the same thing. Binding is what you do at the beginning to gather the MutableLiveData (what you are doing in loadData), but after you've done that once you shouldn't do it again.

I also noted that you are actually having LiveData inside the model, it's not recommended to do it this way as it breaks the pattern and can bring other issues. It's the ViewModel that is supposed to prepare the presentation, not the Repository. As you currently have configured things your repository might as well be called the ViewModel. Instead what you should do is use observables to notify the ViewModel of a new batch to post or handle possible errors that occurred.

Study this example: https://developer.android.com/topic/libraries/architecture/viewmodel

Note that loadUsers() is done once when getUsers() is called. This is what binds the Activity to the ViewModel. But loadUsers() can be done again later and should post the changes to the LiveData inside the ViewModel.