Calling Web Service with Android Architecture Component

Published Dec 18, 2017Last updated Apr 22, 2018

Introduction

Since Google IO 2017, Android Architecture Component has been one of the topics that has really caught my interest (not only me, but other Android developers as well).

Right now, there are a lot of coding architectures out there, such as MVP, MVVM, etc. However, this time Google has provided the official guideline that helps you with the architecture of an Android application.

Therefore, it really is worth trying it out. In this article, I will focus on calling web service with this new architecture. The source code of this project is on my GitHub. Moreover, It’s pure Kotlin 😃

Before starting

This article will focus on calling web services with Android Architecture Component. The basics of Kotlin and other libraries, such as Dagger and Retrofit will not be included in this article.

What does the architecture look like ?


Inside the red oval is where we are focusing on

The picture above is taken from the official Android site. I would like to classify the above picture into two layers.

Data Layer

  1. Repository: Talking to local datasources (databases) and remote datasources (web services)
  2. Remote datasource: Connecting to web service

Presentation Layer

  1. ViewModel: Communicating to Repository then updating the data to UI (fragment/activity) respectively
  2. Fragment/Activity: All about the UI (set up and update)

Demo Application

The demo application fetches the data from https://swapi.co. The application will display a list of species in Star Wars.


screenshot of the app

In this article, I will also cover common actions dealing with web services, including getting results, handling errors, and showing loading UI. You could clone the code from here and look at it along with this article.

Data Layer

Remote datasource

Retrofit is used as a remote datasource to connect to web services. The below code is the interface for the API. If you use Retrofit , you should already be familiar with this.

Source code package : boonya.ben.callingwebservice.api.Swapi.kt

interface Swapi {
    @GET("species/")
    fun getSpecies(): Call<SpeciesList>
}

Repository

The calling to web service happens here, and also includes receiving data and errors from web services. Let’s take a look at the code below.

Source code package: boonya.ben.callingwebservice.species.SpeciesRepository.kt

class SpeciesRepositoryImpl(val apiService: Swapi) : SpeciesRepository {

    override fun getSpecies(successHandler: (List<Species>?) -> Unit, failureHandler: (Throwable?) -> Unit) {
        apiService.getSpecies().enqueue(object : Callback<SpeciesList> {
            override fun onResponse(call: Call<SpeciesList>?, response: Response<SpeciesList>?) {
                response?.body()?.let {
                    successHandler(it.speciesList)
                }
            }

            override fun onFailure(call: Call<SpeciesList>?, t: Throwable?) {
                failureHandler(t)
            }
        })
    }
}

As you can see, the function getSpecies is a higher-order function.

A higher-order function is a function that takes functions as parameters or returns a function.

The getSpecies takes two functions as arguments.

  1. (successHandler: (List<Species>?) -> Unit ) This function is called when the call to the web service is successful. It takes List<Species>, which is the response data from web service, as argument. You will see later how ViewModel can access this data.
  2. (failureHandler: (Throwable?) -> Unit) This function is invoked when the call to web service fails. It also takes Throwable as argument, which will be manipulated in ViewModel.

By having Repository, the presentation layer (Activity/Fragment, ViewModel) will not have direct access to remote source. Therefore, if we want to use another remote source instead of Retrofit, we could do this without changing the code in the presentation layer.

Presentation Layer

Before going any further, let me introduce you to another new component called LiveData.

Here is a short explanation.

LiveData makes normal model class observable.

MutableLiveData is implementation of LiveData. MutableLiveData can use a method called setValue that can be used to assign new values to the LiveData.

ViewModel

ViewModel is a new component introduced in Android Architecture Component. Instead of putting everything in Activity/Fragment, we could separate the code that is not related to UI into ViewModel.

Moreover, ViewModel survives when configuration changes (rotate screen) occur. Normally, Activity/Fragment and everything inside will be recreated if there is configuration change. However, by using ViewModel, the existing one can be reused without creating a new one.

Let’s take a look at example.

Source code package: boonya.ben.callingwebservice.species.SpeciesListViewModel.kt

  class SpeciesListViewModel : ViewModel() {

      @Inject
      lateinit var repository: SpeciesRepository

      var isLoading = MutableLiveData<Boolean>()

      var apiError = MutableLiveData<Throwable>()

      var speciesResponse = MutableLiveData<List<Species>>()

      fun getSpecies() {
          isLoading.value = true
          repository.getSpecies(
                  {
                      speciesResponse.value = it
                      isLoading.value = false
                  },

                  {
                      apiError.value = it
                      isLoading.value = false
                  })
      }
      //ignore other code
  }

There are three MutableLiveData set ups to be observed from activity/fragment.

  1. isLoading: set to true when start calling web services, and change to false when the call is successful or a failure.
  2. apiError : if there is error in calling web services, the Throwable data will be set to this variable.
  3. speciesResponse: when the data is successfully obtained from web service, the response data will be set to this variable.

Calling to Repository from ViewModel

To make Repository calls to web service, we should trigger the method called getSpecies. This method will call another method in Repository, which I wrote about in the Repository section.

As you can see, repository.getSpecies(…. , ….) takes two functions as arguments. The explanation for each argument is down below.

  1. The first argument is the function that will be triggered when calling to the web service is successful. There will be response data returning from web service, which can be referred to as it
  2. The second argument function is triggered when calling to the web service fails and the Throwable data can be referred as it.

Activity

The activity will only contain the code that is related to UI. Here are the codes for activity.

class SpeciesActivity : AppCompatActivity() {

    private val registry = LifecycleRegistry(this)
    private lateinit var viewModel: SpeciesListViewModel
    private lateinit var adapter: SpeciesListAdapter

    override fun getLifecycle(): LifecycleRegistry = registry

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_species)
        
        //set up ui
        viewModel = createViewModel()
        attachObserver()
        viewModel.getSpecies()
    }

    private fun attachObserver() {
        viewModel.isLoading.observe(this, Observer<Boolean> {
            it?.let { showLoadingDialog(it) }
        })
        viewModel.apiError.observe(this, Observer<Throwable> {
            it?.let { Snackbar.make(rvSpecies, it.localizedMessage, Snackbar.LENGTH_LONG).show() }
        })
        viewModel.speciesResponse.observe(this, Observer<List<Species>> {
            it?.let { adapter.notifyDataSetChanged() }
        })
    }

    private fun createViewModel(): SpeciesListViewModel =
            ViewModelProviders.of(this).get(SpeciesListViewModel::class.java).also {
                ComponentInjector.component.inject(it)
            }

    private fun showLoadingDialog(show: Boolean) {
        if (show) progressBar.visibility = View.VISIBLE else progressBar.visibility = View.GONE
    }
}

To make the activity work with Android Architecture Component, the activity should implement LifecycleRegistryOwner and also override method getLifeCycle() like above.

Let’s take a close look at attachObserver() method. This activity will observe to three MutableLiveData in ViewModel.

  1. viewModel.isLoading: when changes occur to the variable showLoadingDialog, method will be triggered to show or hide progressBar.
  2. viewModel.apiError: Snackbar with error message will show if the changes occur to this variable.
  3. viewModel.speciesResponse: The recycler adapter will be notified when the data is set to this value.

Conclusion

I personally like Android Architecture Component a lot. Not only does it allow you to write more testable and maintainable code, it also solves the pain that most Android developers face, like handling configuration change.

If you are about to start a new project, it is really worth checking out this architecture. As of publication time, the library is in alpha stage. However, after the first stable release, everything should be ready to go.

Reference

If you'd like some more reading about this, in Thai, click here!

Discover and read more posts from Boonya Kitpitak
get started