Kotlin Coroutines MVVM Retrofit UnitTestCases — Part3
This story explains about Kotlin Coroutines with MVVM Architecture and Retrofit with Sample examples.
App Flow :
The Coroutine sample that will be explained in the story contains 3 screens
(1) Login Screen
(2) List Screen
(3) List Detail Screen
(4)Corresponding Unit test cases with Mockito.
Used Libraries:
The below libraries are used as part of this implementation
Mockito : For unit testcases
Retrofit : For Making API calls
CoRoutines : Used in combination with Retrofit to make API calls in the separate thread.
Dagger : Dagger2 is used for Dependency Injection
MVVM : MVVM Architecture is followed to build the screens.
The below URL is used to make API calls in the above screens:
Detailed Explanation of Each of the screens:
(1) Login Screen : Contains user interface that display 2 fields Email and password which are pre-populated for the purpose of the tutorial.
When login button is clicked , post API call is performed to validate the entered credentials.
NetworkAPIService: The below interface contains list of suspend functions to make API calls .
@Post : For Login to validate login validate credentials by posting to Server
@GET : fun fetchUsers() Will fetch list of all users from API server
@GET: fun fetchSelectedUsers() : Will fetch information about selected user in List to display user details .
interface NetworkAPIService {
@POST("/api/login")
suspend fun validateLogin(@Body loginModel: LoginModel) : Response<TokenModel>
@GET("/api/users")
suspend fun fetchUsers(@Query("page") page :Int): Response<RetroResult>
@GET("/api/users/{id}")
suspend fun fetchSelectedUsers(@Path("id") id : Int): Response<RetroResultUser>
}
NetworkURL: This is the companion object class that contains information about the URL.
class NetworkURL {
companion object {
const val NETWORK_BASE_URL = "https://reqres.in"
const val REQUEST_TIMEOUT = 60L
}
}
ViewModels : These are the classes where business logic to handle API calls .
LoginViewModelFactory: This class injects Retrofit instance and it initialized corresponding view model with a Dispatcher.Main and networkAPIService as arguments .
Note: Dispatcher.Main to be passed to ViewModel , in order to unit test the view model.
class LoginViewModelFactory : ViewModelProvider.Factory {
@Inject
lateinit var retrofit: Retrofit
lateinit var networkAPIService: NetworkAPIService
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
DaggerNetworkComponent.create().inject(this)
networkAPIService = retrofit.create(NetworkAPIService::class.java)
if (modelClass.isAssignableFrom(LoginViewModel::class.java)) {
return LoginViewModel(Dispatchers.Main,networkAPIService) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
ViewModel: (LoginViewModel)
This file contains all the logic related to posting login credential data to the API service for validation. This class uses Coroutines to make API calls .
View Model takes Dispatcher and NetworkService API as arguments:
class LoginViewModel(
private val dispatcher: CoroutineDispatcher,
private val apiService: NetworkAPIService
) : ViewModel(), LifecycleObserver
Sample Code for the function that makes API call:
fun validateLogin(loginModel: LoginModel){
viewModelScope.launch(dispatcher) {
try{
val response = apiService.validateLogin(loginModel)
if(response.isSuccessful) {
responseToken.postValue(response.body())
loading.postValue(false)
}else{
loading.postValue(false)
// println("Response Error body during failure is ${JSONObject(response.errorBody()?.string()).get("error")} ")
val errorMessage = JSONObject(response.errorBody()?.string()).get("error").toString()
println("Response Error Message $errorMessage")
errorOnAPI.postValue("$errorMessage")
}
}catch (e : Exception){
loading.postValue(false)
errorOnAPI.postValue("Something went wrong::${e.localizedMessage}")
}
}
}
UserList Screen:
This screen contains a Recycler View to fetch data from the API.
The above screen uses @Get service call to fetch data from the API.
UserDetail Screen : Launched when clicked on any of the list item.