The year is 2019 and with every passing month, technology and the stack underneath are upgrading. For eg: The Android permissions which were open for all, became dangerous, then restricted, and then permissible only when required. Things are changing fast for the better and more solid Android development with core principles getting intact.
Change is the only constant — Mahatma Gandhi
Two years back when TvFlix repository was created, the aim was to follow the latest tech stack and architecture. So the app was first designed in MVP pattern and then last year migrated to MVVM by Android Architecture Components powered by RxJava and Dagger 2. Now, the complete TvMaze repository has been rewritten in Kotlin with the latest tech stack and language goodness. So let’s dive in.
Upgrade to Moshi
TvMaze earlier relied upon Gson for converting JSON to POJO and back. To promote immutability, AutoValue was being used with Parcelable and Gson extensions by Ryan Harter. Gson TypeAdapters helped in avoiding reflection while making the conversion. AutoValue also helped in creating builders while still maintaining immutability. A typical AutoValue class will look like this:
Too much of a boilerplate isn’t it?! Also, since the libraries are moving to Kotlin first then why not upgrade for a better future with the goodness of the language? Moshi solves all of the above to stand out as a better JSON serialization library. It provides first-hand Kotlin support with moshi-kotlin
and codegen
. Kotlin data classes are immutable and copy
the method helps in creating the builder. Kotlin's @Parcelize
helps to replace the AutoValue parcel extension thus removing the complete AutoValue library. So the above data class becomes pretty neat and small.
@Parcelize
@JsonClass(generateAdapter = true)
data class Episode(
val id: Long,
val url: String?) : Parcelable
Migration to Coroutine from RxJava — A Paradigm Shift
Coroutines are new ways to encourage asynchronous programming. They help in synchronously writing asynchronous operations. There are tons of articles that already explain the Whys and Hows of Coroutines. So let’s jump directly into implementation.
With Retrofit latest release, Coroutine is officially supported. That means you can remove retrofit’s RxJava2CallAdapterFactory
. Just make all the retrofit calls to suspend
functions and return the values directly.
interface TvMazeApi {
@GET("/schedule")
suspend fun getCurrentSchedule(
@Query("country") country: String,
@Query("date") date: String
): List<Episode>
@GET("/shows")
suspend fun getShows(@Query("page") pageNumber: Int): List<Show>
}
The home screen of TvMaze shows the list of shows fetched from the TVDB API clubbing them with the user’s favorites. A user can mark any show as a favorite which gets stored via Room. Room library now comes with Coroutine support which means all the Dao operations can be suspended thus removing RxJava completely.
Now we are ready to load data for Home screen. HomeViewModel
previously relied on RxJava heavily. When the onScreenCreated
was called, two streams: one from API and the other from DB were zipped using Single.zip
to give the combined result.
public void onScreenCreated() {
isLoading.setValue(true);
String currentDate = getCurrentDate();
Single<List<Show>> favoriteShows = favoriteShowsRepository
.getAllFavoriteShows()
.map(this::convertToShows);
Single<List<Episode>> episodes = tvMazeApi.getCurrentSchedule(COUNTRY_US, currentDate);
Single<List<Episode>> zippedSingleSource =
Single.zip(episodes, favoriteShows, this::favorites);
Disposable homeDisposable = zippedSingleSource.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(this::onSuccess, this::onError);
compositeDisposable.add(homeDisposable);
}
Let's convert the above function to coroutines. Since Retrofit and Room are already on Coroutine support, they respect the suspend
calls. That means, viewModelScope
will launch a new coroutine, which then calls retrofit’s suspend function tvMazeApi.getCurrentSchedule
. The retrofit will move the network operation to the IO-bound coroutine via Dispatchers.IO
and once the suspending function is complete, it will return the result on Dispatchers.Main
. A similar case will happen with Room suspend
calls.
fun onScreenCreated() {
homeViewStateLiveData.value = Loading
val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception ->
onError(exception)
}
// viewModelScope launches the new coroutine on Main Dispatcher internally
viewModelScope.launch(coroutineExceptionHandler) {
// Get favorite shows from db, suspend function in room will launch a new coroutine with IO dispatcher
val favoriteShowIds = favoriteShowsRepository.allFavoriteShowIds()
// Get shows from network, suspend function in retrofit will launch a new coroutine with IO dispatcher
val episodes = tvMazeApi.getCurrentSchedule(COUNTRY_US, currentDate)
// Return the result on main thread via Dispatchers.Main
homeViewStateLiveData.value = Success(HomeViewData(getShowsWithFavorites(episodes, favoriteShowIds)))
}
}
viewModelScope
is a lifecycle Kotlin extension to support coroutines. This handles the cancellation of the coroutine when ViewModel onCleared
is called so that you don’t have to worry about holding the Job
instance and explicitly canceling it. CoroutineExceptionHandler
handles the exceptions being thrown at any Coroutine so that the errors can be propagated and displayed to the user.
Unit Testing Coroutine
We have introduced Coroutine to replace RxJava both on the network and DB layer. Time to put them to the test. We will use Mockito with mockito-kotlin
for better mocking APIs. The test will run on RobolectricTestRunner
to incorporate Android resources into testing.
Testing Room With Coroutine
TvFlix stores the user-marked favorite shows in Db. All the CRUD operations happen via FavoriteShowsRepository
To test FavoriteShowsRepository
, we will create an in-memory database via Room.inMemoryDatabaseBuilder
. The in-memory database provides faster CRUD operations on the database and also ensures that the database disappears when the process is killed. To effectively test coroutines, we will rely on Coroutine test library(still in development). Let’s check the FavoriteShowsRepositoryTest
class and understand the flow:
We mock the context and provide it to Room to create the database. Mocking of context is possible by mockito-inline
which helps in mocking final classes. With the database, we get the mock showDao
and create our repository. runBlocking
runs the new coroutine on the current thread until its completion. We launch the new Coroutine or run the suspending function in this block. We have used Truth
library which provides better and more expressive assertions.
Testing ViewModel
InstantTaskExecutorRule
is an Android architecture testing component that executes the task in the same thread. MainCoroutineRule
sets the main Coroutines Dispatcher to TestCoroutineScope
for better control over coroutine execution in unit testing. HomeViewModel
is dependent upon TvMazeApi
and FavoriteShowsRepository
. So, we mock the required dependencies and then use @InjectMocks
to mock HomeViewModel
. To test, if the home screen is loaded with shows and without favorites, we first stub network and DB calls. Then, we verify whether the loader is shown or not. pauseDispatcher
pauses the execution of a coroutine to verify the initial Loading
state. After verification, we resumeDispatcher
to proceed with execution of pending coroutine actions.
Initially, when HomeViewModelTest
was run, it failed because of the way the tests were written. Read more here. Then after further readings and listening to an awesome Podcast, it was found that we don’t explicitly have to worry about context-switching with Retrofit and Room. The libraries that come with Coroutine support, own the suspend
functions and move them off the UI thread on their own. Thus, they reduce the hassles of handling thread callbacks by the developers in a big way. The result is pretty neat asynchronous calls written synchronously with no callbacks and better readability.
UI Testing — Robot Pattern
Robot Pattern was introduced by Jake Wharton to provide a better separation of concern for UI testing. The pattern aims at properly managing the UI test by separating the What and How of it. Meaning that what is to be tested should not concern How it is to be tested. Both should have clear separation so that maintenance of UI tests becomes easy.
Say, we want to test the Home Screen, whether it shows the list of Popular Shows, and whether Add to Favorites is working fine or not. This is the What of Robot Pattern. The How of this will be taken care of by HomeRobot
which handles all the verification/assertions/actions via Espresso
.
LoadingIdlingResource
tells Espresso to wait until the list ofshows
is fetched and displayed on the screen. This is achieved via the visibility of the progress bar. As an alternative, some suggest using Espresso in production code, but having a test code in production does not look good(Debatable). The final piece is our Kotlin Robot HomeRobot
This Robot is loaded with Kotlin goodness to remove the builder pattern and use language advanced primitives. Know more here.
Future is Bright
RxJava is a powerful library for asynchronous programming packed with tons of operators to get along. Coroutines are still new, and the Android community is slowly accepting them. Heavy development around Flow and Channels, are bridging this gap. Kotlin surely bags a punch with superb language features. With an awesome community, the road to the future of Android Application development looks even brighter. So why wait, go ahead and give a boost to your Android development with Kotlin now. 🚀