Search code examples
javaandroidandroid-architecture-componentsandroid-livedataandroid-viewmodel

LiveData not updating when data changes


I'm using LiveData to fetch data from a server and observe it. My onChanged() method just gets called the first time, and does not get called when data in the server gets updated.

UserFragment:

UserViewModel userViewModel = ViewModelProviders.of(this).get(UserViewModel.class);
userViewModel.getUser().observe(this, new Observer<User>() {
    @Override
    public void onChanged(User user) {
        //Set UI
    }
});

UserViewModel:

public class UserViewModel extends AndroidViewModel {
    private LiveData<User> user;

    public UserViewModel(Application application) {
        super(application);
        user = UserRepository.getInstance().fetchUser();
    }

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

UserRepository:

public class UserRepository {    
    private ApiService apiService;
    private static UserRepository userRepository;

    private UserRepository() {
        apiService = RestClient.getClient().create(ApiService.class);
    }

    public synchronized static UserRepository getInstance() {
        if (userRepository == null) userRepository = new UserRepository();
        return userRepository;
    }

    public LiveData<User> fetchUser() {
        final MutableLiveData<User> data = new MutableLiveData<>();
        Call<User> call = apiService.getUser();
        call.enqueue(new Callback<User>() {
            @Override
            public void onResponse(@NonNull Call<User> call, @NonNull Response<User> response) {
                if (response.body() != null) {
                    data.postValue(response.body());
                }
            }

            @Override
            public void onFailure(@NonNull Call<User> call, @NonNull Throwable t) {
                data.postValue(null);
                t.printStackTrace();
            }
        });
        return data;
    }
}

Solution

  • The issue is that fetchUser creates a new LiveData<> every time you call it.

    This means that your first one will never receive an update.

    Please take a look at these...

    Repository

    public class UserRepository {    
        private ApiService apiService;
        private static UserRepository userRepository;
    
        private UserRepository() {
            apiService = RestClient.getClient().create(ApiService.class);
        }
    
        public synchronized static UserRepository getInstance() {
            if (userRepository == null) userRepository = new UserRepository();
            return userRepository;
        }
    
        // Your example code
        public LiveData<User> fetchUser() {
            // Your problem lies here. Every time you fetch user data, you create a new LiveData.
            // Instead, fetch user should update the data on a pre-existing LiveData.
            final MutableLiveData<User> data = new MutableLiveData<>();
            Call<User> call = apiService.getUser();
            call.enqueue(new Callback<User>() {
                @Override
                public void onResponse(@NonNull Call<User> call, @NonNull Response<User> response) {
                    if (response.body() != null) {
                        data.postValue(response.body());
                    }
                }
    
                @Override
                public void onFailure(@NonNull Call<User> call, @NonNull Throwable t) {
                    data.postValue(null);
                    t.printStackTrace();
                }
            });
            return data;
        }
    
        // My alterations below:
        private MutableLiveData<User> userLiveData = new MutableLiveData<>();
    
        public LiveData<User> getUser() {
            return userLiveData;
        }
    
        public LiveData<User> fetchUser2() {
            Call<User> call = apiService.getUser();
            call.enqueue(new Callback<User>() {
                @Override
                public void onResponse(@NonNull Call<User> call, @NonNull Response<User> response) {
                    if (response.body() != null) {
                        userLiveData.postValue(response.body());
                    }
                    // TODO: Consider a fallback response to the LiveData here, in the case that bad data is returned. Perhaps null?
                }
    
                @Override
                public void onFailure(@NonNull Call<User> call, @NonNull Throwable t) {
                    userLiveData.postValue(null);
                    t.printStackTrace();
                }
            });
            return userLiveData;
        }
    }
    

    ViewModel

    I would also change this slightly. Instead of observing fetch, I would observe the LiveData directly.

    user = UserRepository.getInstance().getUser();
    

    Later, you can request updated data from the server at any point.

    UserRepository.getInstance().fetchUser2();
    

    You could also call fetchUser2() on the first construction of UserRepository. Then only updates would call fetchUser2() directly.

    private UserRepository() {
        apiService = RestClient.getClient().create(ApiService.class);
        fetchUser2();
    }
    

    Fragment

    Also, in your Fragment, do not observe on this. Instead use getViewLifecycleOwner()

    userViewModel.getUser().observe(getViewLifecycleOwner(), new Observer<User>() {
        @Override
        public void onChanged(User user) {
            //Set UI
        }
    });