How to Handle API Responses (Success/Error) in Android?
Last Updated :
10 Jan, 2023
There could be many ways of handling API responses coming from servers in android but do you use a good way of handling it? In this article, we’ll see API responses handled with the help of Kotlin Sealed Class while using the Retrofit library for API calls. This sealed class approach fits perfectly in this case: If the user is expecting data in UI, then we need to send the errors all the way to our UI to notify the user & to make sure the user won’t just see a blank screen or experience unexpected UI.
Make Sealed Class
Note: We are following MVVM Architecture and using Retrofit with Kotlin Coroutines for api calls in background.
Just create a generic sealed class named Resource in a separate file in your data package or utils/others package. We have named it Resource & created it generic because we’ll use this class to wrap our different types of API responses. Basically, we need to update our UI on these three events i.e. Success, Error, Loading. So, we have created these as child classes of Resources to represent different states of UI. Now, in case of success, we’ll get data so we’ll wrap it with Resource. Success and in case of error, we’ll wrap the error message with Resource. Error & in case of loading we’ll just return Resource.Loading object (you can also alter it to wrap your loading data according to your needs).
Kotlin
sealed class Resource<T>(
val data: T? = null ,
val message: String? = null
) {
class Success<T>(data: T) : Resource<T>(data = data)
class Error<T>(errorMessage: String) : Resource<T>(message = errorMessage)
class Loading<T> : Resource<T>()
}
|
Let’s also have a look at our ApiService Interface (see just below), here in every suspended API call function we are wrapping our API call response with Retrofit’s Response class because it provides us some extra info about API calls. For example: whether calls getting successful or not, error code, etc. We’ll call these suspended functions from our repositories… we’ll see that in while. Don’t get confused in our custom Resource class and Retrofit’s Response class. Resource class just represents different states of UI while Retrofit’s Response class gives us extra info about API calls.
Kotlin
interface ExampleApiService {
@GET ( "example/popular_articles" )
suspend fun fetchPopularArticles(): Response<PopularArticlesResponse>
@GET ( "example/new_articles" )
suspend fun fetchNewArticles(): Response<NewArticlesResponse>
}
|
Now let’s jump on to the main part of API success/error handling
You must be having different repositories in your project according to your needs and all your api calls must be happening through these repositories we need to do error handling of each & every API call. So, we need to wrap each API call inside the try-catch block. But wait… writing a try-catch block for every api call isn’t a good idea. So, let’s create a BaseRepository & write a common suspend function there named safeApiCall (any name which you like) which will be responsible to handle api responses of each & every api call.
Kotlin
abstract class BaseRepo() {
suspend fun <T> safeApiCall(apiToBeCalled: suspend () -> Response<T>): Resource<T> {
return withContext(Dispatchers.IO) {
try {
val response: Response<T> = apiToBeCalled()
if (response.isSuccessful) {
Resource.Success(data = response.body()!!)
} else {
val errorResponse: ExampleErrorResponse? = convertErrorBody(response.errorBody())
Resource.Error(errorMessage = errorResponse?.failureMessage ?: "Something went wrong" )
}
} catch (e: HttpException) {
Resource.Error(errorMessage = e.message ?: "Something went wrong" )
} catch (e: IOException) {
Resource.Error( "Please check your network connection" )
} catch (e: Exception) {
Resource.Error(errorMessage = "Something went wrong" )
}
}
}
private fun convertErrorBody(errorBody: ResponseBody?): ExampleErrorResponse? {
return try {
errorBody?.source()?.let {
val moshiAdapter = Moshi.Builder().build().adapter(ExampleErrorResponse:: class .java)
moshiAdapter.fromJson(it)
}
} catch (exception: Exception) {
null
}
}
}
|
safeApiCall is having suspended lambda function named ‘api’ that returns data wrapped in Resource so that safeApiCall could be able to receive our every api call function. As safeApiCall is a suspend function so we are using withContext coroutine scope with Dispatchers.IO, this way all network calls will run on the Input/Output background thread. Whenever a network call fails it throws an exception so we need to call our lambda function ‘apiToBeCalled’ inside the try-catch block and here if api call gets successful then we’ll wrap our success response data in Resource. Success object & we’ll return it through safeApiCall and if api call gets failed then we’ll catch that particular exception in one of the following catch blocks:
- When something will went wrong on the server when processing an HTTP request then it’ll throw an HTTP Exception.
- When the user won’t be having valid internet/wifi connection then it’ll throw a Java IO Exception.
- If any other exception will occur then it’ll simply come in general Exception block (put that block in last). You can also add other exceptions catch block according to your specific case before that last one general exception catch block.
Now in catch blocks, we can simply send our own customize error messages or error messages getting from exceptions in Resource.Error object.
Note: In case if network call doesn’t fail & doesn’t throw an exception instead if it is sending it’s own custom error json response (sometimes happens in case of Authentication failure/Token expires/Invalid api key/etc) then we won’t get an exception instead Retrofit’s Response class will help us determine by returning true/false on response.is Successful. In the above code also we are handling that inside try block, if it returns false then inside else case we’ll parse that api’s own custom error json response (see example below) into a pojo class created by us i.e. ExampleErrorResponse. For parsing this json response to our ExampleErrorResponse pojo, we are using Moshi library in our convertErrorBody function that’ll return ExampleErrorResponse object & then we can get API’s custom error response.
{
“status”: ”UnSuccessful”,
“failure_message” : ”Invalid api key or api key expires”
}
So, ultimately our safeApiCall function will return our response wrapped in Resource class. it could be either Success or Error. If you have understood up till this much then shoutout to you. You have won most of the battle.
Now let’s change the way of calling Apis in your current repositories
We’ll extend our all repositories from BaseRepo so that we can access safeApiCall function there. And inside our repo’s functions like getPopularArticles we’ll pass our ApiService’s function ( for ex: apiService.fetchPopularArticles() ) as a lambda argument in safeApiCall and then safeApiCall will simply handle its success/error part then ultimately it’ll return Resource<T> that’ll go all the way top to UI through ViewModel.
Note: Remember when we’ll call getPopularArticles function in viewmodel (see example below) then first we’ll post Resource.Loading object in mutable live data such that our fragment/activity can observe this loading state and can update the UI to show loading state and then as we’ll get our api response ready, it’ll be posted in mutable live data again so that UI can get this response (success/error) update again and can show data in UI or update itself accordingly.
Kotlin
class ExampleRepo( private val apiService: ExampleApiService) : BaseRepo() {
suspend fun getPopularArticles() : Resource<PopularArticlesResponse> {
return safeApiCall { apiService.fetchPopularArticles() }
}
suspend fun getPublishedArticles() : Resource<NewlyPublishedArticlesResponse> = safeApiCall { apiService.fetchPublishedArticles() }
}
|
ViewModel
Kotlin
class ExampleViewModel ( private val exampleRepo: ExampleRepo) {
private val _popularArticles = MutableLiveData<Resource<PopularArticlesResponse>>()
val popularArticles: LiveData<Resource<PopularArticlesResponse>> = _popularArticles
init {
getPopularArticles()
}
private fun getPopularArticles() = viewModelScope.launch {
_popularArticles.postValue(Resource.Loading())
_popularArticles.postValue(exampleRepo.getPopularArticles())
}
}
|
That’s it, now we can observe all the states i.e. Success, Error, Loading in our UI easily. That was about api error handling or api response handling in a better way.
Share your thoughts in the comments
Please Login to comment...