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

5

114

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?

Merriweather answered 26/5, 2017 at 15:15 Comment(6)
First of all, you're missing a Repository. Your ViewModel should not be doing any data loading tasks. Other than that, since your using Room, your Service doesn't have to be updating the LiveData in the ViewModel directly. Service can be only inserting data into Room, while your ViewModelData should be attached only to Room, and get updates from Room (after Service inserts data). But for the absolute best architecture, look at the NetworkBoundResource class implementation from the bottom of this page: developer.android.com/topic/libraries/architecture/guide.htmlOphthalmologist
Repositor class is not mentioned in the offocial docs describing ROOM or the android architecture componentsSlew
Repository is a suggested best practice for code separation and architecture, look at this example: codelabs.developers.google.com/codelabs/…Drogheda
The function loadUsers() basically will call the repo to get the user informationMerriweather
Repositories are overrated they do nothing different from a JVM perspective, references are still "stored/cached" inside the ViewModel, only with a different mediating referent, repositories add a new layer of bloatedness, add space in memory each new instanced reference to Repository because this reference is not a singleton and/or dependency (Dagger+Retrofit acknowledges this and reworks the pattern) Repository could effectively be just a Utility class (dont even need Dagger) and it would serve the same purpose with better performance and same degree of organization & separation of concerns.Arabele
The problem of repositories is not a problem with repositories, Repositories solve a problem that lies within the Android ViewModel architecture.Viewmodel's design lacks cohesion with other VM's. ViewModels with their dependency quality were thought out with the principle of serving as repositories themselves, the issue is the lack of communication between them, meaning you cannot reuse information from one VM to another without using an external event-cycle. If VM's could b interlinked in a tree with a reactive pattern,repositories would be a story of the past. But this is past already.Arabele
F
103

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.

Foregoing answered 31/5, 2017 at 19:54 Comment(8)
I am getting "java.lang.IllegalStateException: Cannot access database on the main thread since it" on calling my db methods like above, Can you tell what might be wrong ?Calvin
I'm using evernote-job which is updating the database in the background while I am on the UI. But LiveData is not updatingTabaret
users.postValue(mUsers); -> But, does MutableLiveData's postValue method able to accept LiveData???Thereinto
My mistake was using value instead of postValue. Thanks for your answer.Corpulence
@pcj you need to either do the room operation in a thread or enable operations on the main thread - google for more answers.Mantling
good to note that .postValue() work from background tasks and .setValue() doesn'tLaughton
How will you do that from Room Dao. For example, @Query("SELECT * FROM product_table") ?Samella
Room database support livedata or coroutines flow. You can just return livedata from room query. It will work automatically.Foregoing
C
49

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)
}
Correlate answered 30/7, 2017 at 12:46 Comment(2)
To remind, postValue() is protected in LiveData but public in MutableLiveDataGlossy
this work even from a background task and in situations where .setValue() wouldn't be allowedLaughton
B
20

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

}
Basicity answered 8/6, 2017 at 20:21 Comment(4)
Repository's getUsers() method might call an api for the data which is async (might start a service or asynctask for this) in that case how can it return the List from it's return statement?Merriweather
Perhaps it could take the LiveData object as an argument. (Something like repository.getUsers(users)). Then the repository method would call users.postValue itself. And, in that case, the loadUsers method would not even need a background thread.Basicity
Thank you for the answer .... however, I'm using a Room DB for storage and the DAO returns a LiveData<Object> ... how do I convert the LiveData to a MutableLiveData<Object>?Mantling
I don't think Room's DAO is really intended to deal in MutableLiveData objects. The DAO is notifying you of a change to the underlying db, but, if you want to change the value in the DB, you would call of the DAO's methods. Also maybe the discussion here is useful: #50944419Basicity
C
4

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.

Club answered 5/6, 2017 at 18:22 Comment(0)
M
3

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

Mcwherter answered 13/5, 2019 at 10:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.