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?

Peter
  • 437
  • 1
  • 4
  • 20
S Haque
  • 6,881
  • 5
  • 29
  • 35
  • 11
    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: https://developer.android.com/topic/libraries/architecture/guide.html – Marko Gajić May 28 '17 at 16:35
  • thank you for the suggestion :) – S Haque May 29 '17 at 15:24
  • 1
    Repositor class is not mentioned in the offocial docs describing ROOM or the android architecture components – Jono Feb 22 '18 at 10:56
  • 2
    Repository is a suggested best practice for code separation and architecture, look at this example: https://codelabs.developers.google.com/codelabs/android-room-with-a-view/#7 – glisu Sep 22 '18 at 14:24
  • 1
    The function `loadUsers()` basically will call the repo to get the user information – S Haque Sep 23 '18 at 05:23
  • 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 Oct 24 '21 at 17:27
  • 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. – Delark Oct 24 '21 at 17:33

5 Answers5

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.

xarlymg89
  • 2,552
  • 2
  • 27
  • 41
androidcodehunter
  • 21,567
  • 19
  • 47
  • 70
  • 3
    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 ? – Prashant Aug 01 '17 at 09:33
  • I'm using evernote-job which is updating the database in the background while I am on the UI. But `LiveData` is not updating – Akshay Chordiya Feb 12 '18 at 11:58
  • `users.postValue(mUsers);` -> But, does MutableLiveData's postValue method able to accept LiveData??? – Cheok Yan Cheng Apr 01 '18 at 21:42
  • 2
    My mistake was using `value` instead of `postValue`. Thanks for your answer. – Hesam Jul 20 '18 at 03:53
  • 1
    @pcj you need to either do the room operation in a thread or enable operations on the main thread - google for more answers. – kilokahn Jul 23 '18 at 18:56
  • good to note that `.postValue()` work from background tasks and `.setValue()` doesn't – davejoem Aug 11 '20 at 22:48
  • How will you do that from Room Dao. For example, @Query("SELECT * FROM product_table") ? – Rohit Singh Oct 17 '21 at 03:17
  • Room database support livedata or coroutines flow. You can just return livedata from room query. It will work automatically. – androidcodehunter Nov 15 '21 at 03:49
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)
}
Allan Veloso
  • 5,823
  • 1
  • 38
  • 36
minhazur
  • 4,928
  • 3
  • 25
  • 27
19

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

}
albert c braun
  • 2,650
  • 1
  • 22
  • 32
  • 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? – S Haque Jun 09 '17 at 06:15
  • 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. – albert c braun Jun 09 '17 at 17:35
  • 1
    Thank you for the answer .... however, I'm using a Room DB for storage and the DAO returns a LiveData ... how do I convert the LiveData to a MutableLiveData? – kilokahn Jul 21 '18 at 23:33
  • 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: https://stackoverflow.com/questions/50943919/convert-livedata-to-mutablelivedata – albert c braun Jul 22 '18 at 03:25
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.

user1978019
  • 3,008
  • 1
  • 29
  • 38
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

Shubham Agrawal
  • 1,252
  • 12
  • 29