How to make retrofit API call using ViewModel and LiveData

25,979

Solution 1

There are 3 problems in your code.

  1. You must create a MutableLiveData object because you have an empty response before API call then your LiveData object will be filled somehow through the IGDB response.
private MutableLiveData<List<Game>> mGameList = new MutableLiveData();
//...
public LiveData<List<Game>> getGameList() {
    return mGameList;
}
  1. Another mistake is changing the reference of mGameList instead of setting its value, So try to change:
Timber.d("Call response body not null");
mGameList = response.body();

to

mGameList.setValue(response.body());
  1. Calling retrofit inside your ViewModel class is avoiding separation of concerns. It's better to create a repository module and get your response through an interface. Read this article for details.

Repository modules are responsible for handling data operations. They provide a clean API to the rest of the app. They know where to get the data from and what API calls to make when data is updated. You can consider them as mediators between different data sources (persistent model, web service, cache, etc.).

Solution 2

I just inspired by Google's Demo and created a library, which can add LiveData support for Retrofit. The usage is simple:

Retrofit.Builder()
            .baseUrl("https://api.github.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(LiveDataCallAdapterFactory())
            .build()
            .create(GithubService::class.java)
            .getUser("shawnlinboy").observe(this,
                Observer { response ->
                    when (response) {
                        is ApiSuccessResponse -> {
                          //success response
                        }
                        else -> {
                            //failed response
                        }
                    }
                })

The library site: https://github.com/shawnlinboy/retrofit-livedata-adapter

Share:
25,979

Related videos on Youtube

Chao Li
Author by

Chao Li

Updated on January 07, 2022

Comments

  • Chao Li
    Chao Li over 2 years

    this is the first time I'm trying to implement MVVM architecture, and I'm a bit confused about the correct way to make an API call.

    Currently, I'm just trying to make a simple query from the IGDB API, and output the name of the first item in a log.

    My activity is setup as follow:

    public class PopularGamesActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_popular_games);
    
    
            PopularGamesViewModel popViewModel = ViewModelProviders.of(this).get(PopularGamesViewModel.class);
            popViewModel.getGameList().observe(this, new Observer<List<Game>>() {
                @Override
                public void onChanged(@Nullable List<Game> gameList) {
                    String firstName = gameList.get(0).getName();
                    Timber.d(firstName);
                }
            });
        }
    }
    

    My View Model is set up as follow:

    public class PopularGamesViewModel extends AndroidViewModel {
    
        private static final String igdbBaseUrl = "https://api-endpoint.igdb.com/";
        private static final String FIELDS = "id,name,genres,cover,popularity";
        private static final String ORDER = "popularity:desc";
        private static final int LIMIT = 30;
    
        private LiveData<List<Game>> mGameList;
    
        public PopularGamesViewModel(@NonNull Application application) {
            super(application);
    
    
            // Create the retrofit builder
            Retrofit.Builder builder = new Retrofit.Builder()
                    .baseUrl(igdbBaseUrl)
                    .addConverterFactory(GsonConverterFactory.create());
    
            // Build retrofit
            Retrofit retrofit = builder.build();
    
            // Create the retrofit client
            RetrofitClient client = retrofit.create(RetrofitClient.class);
            Call<LiveData<List<Game>>> call = client.getGame(FIELDS,
                    ORDER,
                    LIMIT);
    
            call.enqueue(new Callback<LiveData<List<Game>>>() {
                @Override
                public void onResponse(Call<LiveData<List<Game>>> call, Response<LiveData<List<Game>>> response) {
                    if (response.body() != null) {
                        Timber.d("Call response body not null");
                        mGameList = response.body();
    
                    } else {
                        Timber.d("Call response body is null");
                    }
                }
    
                @Override
                public void onFailure(Call<LiveData<List<Game>>> call, Throwable t) {
                    Timber.d("Retrofit call failed");
                }
            });
    
        }
    
        public LiveData<List<Game>> getGameList() {
            return mGameList;
        }
    

    Now the problem is because this is an API call, the initial value of mGameList will be null, until call.enqueue returns with a value. This will cause a null pointer exception with

    popViewModel.getGameList().observe(this, new Observer<List<Game>>() {
    
    1. So what is the correct way to handle the observation of a LiveData, while API call is being made?
    2. Did I perform the Retrofit API call in the right place?
  • Chao Li
    Chao Li almost 6 years
    Thank you for your response. Ive actually noticed my error 1 and 2 before realising your response, so I got that fixed. Thank you for pointing out my error 3, I will go add a repository.
  • Richard Hsieh
    Richard Hsieh over 5 years
    I think many people have the same problem, you save my life, thanks.
  • Alston
    Alston over 4 years
    How can I know from the View when the repository data request is finished.