Open In App

How to Handle API Responses (Success/Error) in Android?

Improve
Improve
Like Article
Like
Save
Share
Report

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
) {
 
    // We'll wrap our data in this 'Success'
    // class in case of success response from api
    class Success<T>(data: T) : Resource<T>(data = data)
 
    // We'll pass error message wrapped in this 'Error'
    // class to the UI in case of failure response
    class Error<T>(errorMessage: String) : Resource<T>(message = errorMessage)
 
    // We'll just pass object of this Loading
    // class, just before making an api call
    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>
    // We have wrapped our api response in
    // Retrofit's Response class. So that we can have
    // some extra information about api call response
    // like whether it succeed or not, etc
   
    @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() {
 
    // we'll use this function in all
    // repos to handle api errors.
    suspend fun <T> safeApiCall(apiToBeCalled: suspend () -> Response<T>): Resource<T> {
 
        // Returning api response
        // wrapped in Resource class
        return withContext(Dispatchers.IO) {
            try {
               
                // Here we are calling api lambda
                // function that will return response
                // wrapped in Retrofit's Response class
                val response: Response<T> = apiToBeCalled()
                 
                if (response.isSuccessful) {
                    // In case of success response we
                    // are returning Resource.Success object
                    // by passing our data in it.
                    Resource.Success(data = response.body()!!)
                } else {
                    // parsing api's own custom json error
                    // response in ExampleErrorResponse pojo
                    val errorResponse: ExampleErrorResponse? = convertErrorBody(response.errorBody())
                    // Simply returning api's own failure message
                    Resource.Error(errorMessage = errorResponse?.failureMessage ?: "Something went wrong")
                }
                 
            } catch (e: HttpException) {
                // Returning HttpException's message
                // wrapped in Resource.Error
                Resource.Error(errorMessage = e.message ?: "Something went wrong")
            } catch (e: IOException) {
                // Returning no internet message
                // wrapped in Resource.Error
                Resource.Error("Please check your network connection")
            } catch (e: Exception) {
                // Returning 'Something went wrong' in case
                // of unknown error wrapped in Resource.Error
                Resource.Error(errorMessage = "Something went wrong")
            
        }
    }
  
    // If you don't wanna handle api's own
    // custom error response then ignore this function
    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> {
        // Passing 'api.fetchPopularArticles()' function
        // as an argument in safeApiCall function
        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 {
        // Calling this function in init{}
        // block so that it'll automatically
        // get called on initialization of viewmodel
        getPopularArticles()
    }
 
    private fun getPopularArticles() = viewModelScope.launch {
         // Firstly we are posting
         // Loading state in mutableLiveData
        _popularArticles.postValue(Resource.Loading())
         
         // Posting success response
         // as it becomes ready
        _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.



Last Updated : 10 Jan, 2023
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads