Xcoding with Alfian

Mobile development articles and tutorials

Using Android Architecture Components Live Data for Asynchronous API

Alt text

For many years Android developers have been using Loaders to request data from server asynchronously on background thread safely. Loaders manage the Activity or Fragment lifecycle events and configuration changes automatically. The Loader API provides callback methods when an event occurs and can also add observers when change occurs in underlying datasource so the UI can be updated with new data and handling duplicate data query.

While Loaders gets the job done pretty well, the API is dependant on Activity or Fragment that provides LoaderManager to start and attach listener callback to the Loaders. This coupling makes it harder to test the data loading separately in a scenario when you don’t have Activity or Fragment. Writing all the listener callback event also leads to poor organization of code inside your Activity or Fragment.

Here comes the Android Lifecycle Aware Components!

Luckily at IO 2017, Google released Android Architecture Components suites of library for the developers so we can build more scalable, maintainable, crash free, and testable application easier. There are two separate components that we can use to load data asynchronously:

  1. LiveData is a lifecycle aware data holder class that also act as an observable object. This means that LiveData automatically handle subscription when the lifecycle owner is created and unsubscribe when the lifecycle owner is destroyed to avoid memory leaks.
  2. ViewModel is a lifecycle aware class that is designed to store and manage data that is associated with UI. ViewModel keep data when configuration changes such as when screen rotation occurs. This makes data ownership belongs to the ViewModel independently and separated from UI controller so Unit Testing become much more easier because of the modularity.

LiveData & ViewModel in action

So how do we use LiveData and ViewModel to load asynchronous data from a server?. Here is the example code of loading Github API using ViewModel class with LiveData that is observed by the Activity or Fragment so the UI can be updated when the response data is received.

class GithubReposViewModel: ViewModel() {

    val reposResult = MutableLiveData<Pair<List<GithubRepo>?, Error?>>()
    private val githubRepo = GithubRepository.instance()

    init {
        loadReposFromRepository()
    }

    fun loadReposFromRepository() {
        githubRepo.getRepositories { repos, error -> 
            reposResult.value = Pair(repos, error)
        }
    }
    
}

The GithubViewModel Class exposes reposResult property which is a MutableLiveData object containing Kotlin Pair object for optional list of repos loaded from the web or optional error object in case of server error or network error. MutableLiveData object is a mutable data, which means the value of the data can be changed.

A Singleton GithubRepository class is used to encapsulate the datasource repository layer, this repository can use data from cache such as SQLite or fetch the latest data from network directly. The getRepositories function calls the GitHub API and pass the results through the callback argument which the GithubViewModel assign to update the value of the reposResult.

class GithubRepository {

  fun getRepositories(completion: (List<GithubRepo>?, Error?) -> Unit) {
    val client = OkHttpClient()
    val request = Request.Builder()
            .url("https://api.github.com/repositories")
            .build()
    client.newCall(request).enqueue(object: Callback {
        override fun onFailure(call: Call?, e: IOException?) {
            completion(null, Error(e?.message))
        }

        override fun onResponse(call: Call?, response: Response?) {
            val jsonString = response?.body()?.string()
            val repos = GithubRepo.mapJSONStringToList(jsonString)
            completion(repos, null)
        }
    })
}
    companion object {
        private var sInstance: GithubRepository? = null
        fun instance(): GithubRepository {
            if (sInstance == null) {
                synchronized(GithubRepository) {
                    sInstance = GithubRepository()
                }
            }
            return sInstance!!
        }
    }

}

Observing ViewModel LiveData and update UI

At last, we can use the GitHubViewModel to get the repository data from Activity or Fragment. To get the ViewModel we can use the ViewModelProviders factory method call and pass the GithubReposViewModel class to get the ViewModel. The Activity or Fragment then observe the reposResult property and get the latest value whenever the underlying MutableLiveData changes. The Observer callback pass the value which can be used to display the list of repositories inside a RecyclerView or ListView and display error message if an error occurs. We don’t have to worry about managing the lifecycle of the observer because Activity and Fragment is already implements the LifeCycleOwner interface.

class RepositoryListActivity : AppCompatActivity() {

    val githubReposViewModel: GithubReposViewModel
        get() = ViewModelProviders.of(this).get(GithubReposViewModel::class.java)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_repository_list)
        setSupportActionBar(toolbar)
   
        githubReposViewModel.reposResult.observe(this, Observer {
            // Update UI (Recycler View, List View if repos is not null or show error if error is not null)
        })

    }

}

Conclusion

Loading asynchronous data using Android Architecture Components really makes the app becomes more easier to write, test and maintain because of the MVVM pattern it exposes. As a mobile developer, i really hope someday Apple will also officially release something similar on iOS platform, although for now we can use RxSwift or similar library to build MVVM architecture based app on iOS.

There are still much more to learn from Android Architecture Components such as Room Data Persistence Library and Paging Library. You can check more about these on the Official Documentation Website.