How to update LiveData of a ViewModel from background service and Update UI

AndroidViewmodelBackground ServiceAndroid RoomAndroid Architecture-Components

Android Problem Overview


Recently I am exploring Android Architecture, that has been introduced recently by google. From the Documentation I have found this:

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<Users>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // do async operation to fetch users
    }
}

the activity can access this list as follows:

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}

My Question is, I am going to do this:

  1. in the loadUsers() function I am fetching the data asynchronously where I will first check the database(Room) for that data

  2. If I do not get the data there I will make an API call to fetch the data from the web server.

  3. I will insert the fetched data into the database(Room) and update the UI according the data.

What is the recommended approach to do this?

If I start a Service to call the API from the loadUsers() method, how can I update the MutableLiveData<List<User>> users variable from that Service?

Android Solutions


Solution 1 - Android

I am assuming that you are using android architecture components. Actually it doesn't matter wherever you are calling service, asynctask or handler to update the data. You can insert the data from the service or from the asynctask using postValue(..) method. Your class would look like this:

private void loadUsers() {
    // do async operation to fetch users and use postValue() method
    users.postValue(listOfData)
}

As the users is LiveData, Room database is responsible for providing users data wherever it is inserted.

Note: In MVVM like architecture, the repository is mostly responsible for checking and pulling local data and remote data.

Solution 2 - Android

You can use MutableLiveData<T>.postValue(T value) method from background thread.

private void loadUsers() {
    // do async operation to fetch users and use postValue() method
   users.postValue(listOfData)
}

Solution 3 - Android

> ... in the loadUsers() function I am fetching the data asynchronously ... > If I start a Service to call the API from the loadUsers() method, how > can I update the MutableLiveData> users variable from that > Service?

If the app is fetching user data on a background thread, postValue (rather than setValue) will be useful.

In the loadData method there is a reference to the MutableLiveData "users" object. The loadData method also fetches some fresh user data from somewhere (for example, a repository).

Now, if execution is on a background thread, MutableLiveData.postValue() updates outside observers of the MutableLiveData object.

Maybe something like this:

private MutableLiveData<List<User>> users;

.
.
.

private void loadUsers() {
    // do async operation to fetch users
    ExecutorService service =  Executors.newSingleThreadExecutor();
    service.submit(new Runnable() {
        @Override
        public void run() {
            // on background thread, obtain a fresh list of users
            List<String> freshUserList = aRepositorySomewhere.getUsers();

            // now that you have the fresh user data in freshUserList, 
            // make it available to outside observers of the "users" 
            // MutableLiveData object
            users.postValue(freshUserList);        
        }
    });

}

Solution 4 - Android

Take a look at the Android architecture guide that accompanies the new architecture modules like LiveData and ViewModel. They discuss this exact issue in depth.

In their examples they don't put it in a service. Take a look at how they solve it using a "repository" module and Retrofit. The addendums at the bottom include more complete examples including communicating network state, reporting errors, etc.

Solution 5 - Android

If you are calling your api in Repository then,

In Repository:

public MutableLiveData<LoginResponseModel> checkLogin(LoginRequestModel loginRequestModel) {
    final MutableLiveData<LoginResponseModel> data = new MutableLiveData<>();
    apiService.checkLogin(loginRequestModel)
            .enqueue(new Callback<LoginResponseModel>() {
                @Override
                public void onResponse(@NonNull Call<LoginResponseModel> call, @Nullable Response<LoginResponseModel> response) {
                    if (response != null && response.isSuccessful()) {
                        data.postValue(response.body());
                        Log.i("Response ", response.body().getMessage());
                    }
                }

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

In ViewModel

public LiveData<LoginResponseModel> getUser() {
    loginResponseModelMutableLiveData = repository.checkLogin(loginRequestModel);
    return loginResponseModelMutableLiveData;
}

In Activity/Fragment

loginViewModel.getUser().observe(LoginActivity.this, loginResponseModel -> {
        if (loginResponseModel != null) {
            Toast.makeText(LoginActivity.this, loginResponseModel.getUser().getType(), Toast.LENGTH_SHORT).show();
        }
    });

Note : Using JAVA_1.8 lambda here, you can use without it

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionS HaqueView Question on Stackoverflow
Solution 1 - AndroidandroidcodehunterView Answer on Stackoverflow
Solution 2 - AndroidminhazurView Answer on Stackoverflow
Solution 3 - Androidalbert c braunView Answer on Stackoverflow
Solution 4 - Androiduser1978019View Answer on Stackoverflow
Solution 5 - AndroidShubham AgrawalView Answer on Stackoverflow