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

85,417

Solution 1

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

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

... 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

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

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

Share:
85,417

Related videos on Youtube

S Haque
Author by

S Haque

I am an Android Developer with more than 5 years of experience in this field; passionate about developing better applications through writing performance-optimized, reusable, and testable code. My core strengths lie within my profound knowledge on Algorithm, Data-Structure, Software Architecture and Software Design Patterns which helps me to write robust and scalable applications. I also love to learn about the latest practices and library components of the continuously evolving Android world. Furthermore, I am a user-centric developer. I love to travel and explore new places and I also love sports. Recently, I am emphasizing a lot on self-improvement, building good habits like reading, meditation and exercising to improve my productivity.

Updated on February 10, 2020

Comments

  • S Haque
    S Haque over 4 years

    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?

    • Marko Gajić
      Marko Gajić about 7 years
      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.htm‌​l
    • S Haque
      S Haque about 7 years
      thank you for the suggestion :)
    • Jono
      Jono over 6 years
      Repositor class is not mentioned in the offocial docs describing ROOM or the android architecture components
    • glisu
      glisu almost 6 years
      Repository is a suggested best practice for code separation and architecture, look at this example: codelabs.developers.google.com/codelabs/…
    • S Haque
      S Haque almost 6 years
      The function loadUsers() basically will call the repo to get the user information
    • Delark
      Delark over 2 years
      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.
    • Delark
      Delark over 2 years
      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.
  • S Haque
    S Haque about 7 years
    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?
  • albert c braun
    albert c braun about 7 years
    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.
  • Prashant
    Prashant almost 7 years
    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 ?
  • Long Ranger
    Long Ranger over 6 years
    To remind, postValue() is protected in LiveData but public in MutableLiveData
  • Akshay Chordiya
    Akshay Chordiya over 6 years
    I'm using evernote-job which is updating the database in the background while I am on the UI. But LiveData is not updating
  • Lumii
    Lumii about 6 years
    users.postValue(mUsers); -> But, does MutableLiveData's postValue method able to accept LiveData???
  • Hesam
    Hesam almost 6 years
    My mistake was using value instead of postValue. Thanks for your answer.
  • kilokahn
    kilokahn almost 6 years
    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>?
  • albert c braun
    albert c braun almost 6 years
    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: stackoverflow.com/questions/50943919/…
  • kilokahn
    kilokahn almost 6 years
    @pcj you need to either do the room operation in a thread or enable operations on the main thread - google for more answers.
  • davejoem
    davejoem almost 4 years
    this work even from a background task and in situations where .setValue() wouldn't be allowed
  • davejoem
    davejoem almost 4 years
    good to note that .postValue() work from background tasks and .setValue() doesn't
  • Rohit Singh
    Rohit Singh over 2 years
    How will you do that from Room Dao. For example, @Query("SELECT * FROM product_table") ?
  • androidcodehunter
    androidcodehunter over 2 years
    Room database support livedata or coroutines flow. You can just return livedata from room query. It will work automatically.