Retrofit 2.0 — A new way to work with Retrofit 2.0 in Android — Android Architecture
As Developer, we love to write less code, because we are lazy :). Retrofit 2.0 is a popular library. Many developers use it normally, I mean the traditional way — create an ApiService Interface, a model, and a callback using RxJava or Call<T> interface or any other way.
Today we are going to learn a new way to work with Retrofit Library. Let’s dive into it.
Our model class
data class User(
val id: Int,
val name: String,
val phone: String
)
This is the universal ApiService class. This class will be used to get a response from our server.
interface ApiService { @FormUrlEncoded
@POST("{url}")
suspend fun <T> postRequest(
@Path(value = "url", encoded = true) path: String,
@FieldMap hashMap: Map<String, String>
): Response<T> @GET("{url}")
suspend fun <T> getRequest(
@Path(value = "url", encoded = true) path: String,
@QueryMap hashMap: Map<String, String>
): Response<T> @Multipart
@POST("{url}")
suspend fun <T> sendDocuments(
@Path(value = "url", encoded = true) path: String,
@PartMap partMap: Map<String, @JvmSuppressWildcards RequestBody>,
@Part image: List<@JvmSuppressWildcards MultipartBody.Part>
): Response<T>}
In your ApiHelper class
suspend fun createUser(mobileNo: String) = getResult(type = User::class.java) {
val hashMap = HashMap<String, String>()
hashMap["phone"] = mobileNogetApiCall(CALL_TYPE_POST, "users", hashMap)
}
Here we created a function named createUser. This function will create a user. However, we call the getApiCall function inside the createUser function. There is also another function that returns the result.
getApiCall function determines which function we should hit from our ApiService interface. Hence we used a predefined variable called CALL_TYPE_POST. So, we are going to hit the post function from our ApiService interface.
In your ApiHelper class
//call type
private val CALL_TYPE_GET = "get"
private val CALL_TYPE_POST = "post"private suspend fun <T> getApiCall(
type: String,
path: String,
hashMap: Map<String, Any>,
multiMartHashMap: List<MultipartBody.Part>? = null
): Response<T> {
return when (type) {
CALL_TYPE_GET -> {
apiService.getRequest(path, hashMap as Map<String, String>)
}
CALL_TYPE_POST -> {
apiService.postRequest(path, hashMap as Map<String, String>)
}
else -> {
apiService.sendDocuments(
path,
hashMap as Map<String, RequestBody>,
multiMartHashMap!!
)
}
}
}
All the code you need in your ApiHelper class
class ApiHelper(private val apiService: ApiService) : RetrofitBaseDataSource() { //call type
private val CALL_TYPE_GET = "get"
private val CALL_TYPE_POST = "post" private suspend fun <T> getApiCall(
type: String,
path: String,
hashMap: Map<String, Any>,
multiMartHashMap: List<MultipartBody.Part>? = null
): Response<T> {
return when (type) {
CALL_TYPE_GET -> {
apiService.getRequest(path, hashMap as Map<String, String>)
}
CALL_TYPE_POST -> {
apiService.postRequest(path, hashMap as Map<String, String>)
}
else -> {
apiService.sendDocuments(
path,
hashMap as Map<String, RequestBody>,
multiMartHashMap!!
)
}
}
} suspend fun createUser(mobileNo: String) = getResult(type = User::class.java) {
val hashMap = HashMap<String, String>()
hashMap["phone"] = mobileNo
getApiCall(CALL_TYPE_POST, "users", hashMap) }
}
The main RetrofitBaseDataSource that we extend in our ApiHelper class. Let’s see what we have done in this class.
This is the main player. It ensures a safe and clean response to our UI.
getResult function is an inline function. It receives a class type that will help us later to determine which model class should be parsed and it receives a higher-order function.
After getting a response from the server, it handles the errors and passes the result to the UI using the Result sealed class.
abstract class RetrofitBaseDataSource { protected suspend inline fun <T : Any> getResult(
type: Type,
crossinline call: suspend () -> Response<T>
): Flow<Result<T>> {
return flow {
emit(Result.InProgress(true)) val response = call() emit(
if (response.isSuccessful) {
val body = response.body()
if (body != null) {
Result.Success(body, type)
} else {
Result.UnknownError(null)
}
} else {
Result.ApiError(response.errorBody()!!, response.code())
}
) emit(Result.InProgress(false))
}.catch { e ->
emit(Result.InProgress(false))
emit(
if (e is IOException) {
Result.NetworkError(e)
} else {
Result.UnknownError(e)
}
)
}.flowOn(Dispatchers.IO) } }
Result class
sealed class Result<out R> {
data class Success<T>(val data: T, val type: Type) : Result<T>()
data class ApiError(val errorBody: ResponseBody, val code: Int) : Result<Nothing>()
data class NetworkError(val error: IOException) : Result<Nothing>()
data class UnknownError(val exception: Throwable?) : Result<Nothing>()
data class InProgress(val isLoading: Boolean) : Result<Nothing>()
}
Let’s hit the createUser function from our ViewModel to get a response.
In your ViewModel
fun createUser(mobileNo: String) {
if (mobileNo.isMobileValid()) {
executeSuspendedFunction { dataManager.apiHelper.createUser(mobileNo) }
} else
_snackBarMsg.value = context.getString(R.string.mobile_no_valid_msg)
}
Success response in your activity or fragment
So we get a response now we need to handle it on the UI side. so we created a function called handleSuccessfulResponse.
vm.result.observe(this, {
it?.let {
when (it) {
is Result.InProgress -> {
if (it.isLoading)
showProgressing()
else
hideProgressing()
}
is Result.Success -> {
handleSuccessfulResponse(it)
}
}
}
})private fun handleSuccessfulResponse(success: Result.Success<Any>) {
when (success.type) {
User::class.java -> {
val data = Gson().fromJson(success.data.toString(), User::class.java)
} }
}
See the above function, We are using class type to determine which response we received at the UI side.
In your BaseViewModel
Here we are handling the response that we receive from the getResult function.
private val _result = MutableLiveData<Result<Any>>()
val result: LiveData<Result<Any>>
get() = _result
fun executeSuspendedFunction(codeBlock: suspend () -> Flow<Result<Any>>) {
viewModelScope.launch {
codeBlock().collect { result ->
_result.value = result when (result) {
is Result.ApiError -> {
Log.d("RetrofitResult", result.errorBody.toString())
}
is Result.NetworkError -> {
Log.d("RetrofitResult", result.error.toString())
result.error.printStackTrace()
}
is Result.UnknownError -> {
result.exception?.printStackTrace()
Log.d("RetrofitResult", result.exception.toString())
}
}
}
}
}
Here we attach the response to our livedata variable and handling the errors.
See the whole architecture here
https://github.com/shaon2016/Android-CleanArchitecture-MVVM-HILT