Efficient Error Handling in Ktor Android Client: A Custom Approach

Efficient Error Handling in Ktor Android Client: A Custom Approach

Asim Latif's photo
May 19, 2024·

3 min read

We'll explore a streamlined method for error handling in Ktor Android client by leveraging a custom network result wrapper class. By utilizing a sealed interface with Success and Error data classes, we'll encapsulate response data and exceptions. Additionally, we'll demonstrate the creation of an extension function that simplifies error handling by associating HTTP response statuses with corresponding error messages. This approach enhances code readability and maintainability, ensuring a smoother user experience.

We start by defining a Network Result Wrapper class, designed to encapsulate both data and exceptions. This sealed interface comprises two nested data classes: Success, which holds successful data, and Error, which captures any encountered exceptions.

sealed interface NetworkResult<out T : Any> {
    data class Success<out T : Any>(val data: T) : NetworkResult<T>
    data class Error<out T : Any>(val error: Exception) : NetworkResult<T>
}

Next, we'll define an extension function for processing HTTP response statuses in various Ktor requests, such as GET, POST, and PUT.

suspend inline fun <reified T : Any> HttpResponse.toResult(): NetworkResult<T> {
    return when (status.value) {
        200 -> NetworkResult.Success(body())
        400 -> NetworkResult.Error(NetworkException("Check your credentials and try again!"))
        401 -> NetworkResult.Error(NetworkException("Authorization Failed! Try Logging In again."))
        500, 503 -> NetworkResult.Error(NetworkException("Server Disruption! We are on fixing it."))
        504 -> NetworkResult.Error(NetworkException("Too much load at this time, try again later!"))
        else -> NetworkResult.Error(NetworkException("Something went wrong! Please try again or contact support."))
    }
}

class NetworkException(message: String) : Exception(message)

With this setup, we've effectively mapped each status code to our customized Network Exception along with personalized error messages.

Usage

Suppose we have an API that fetches posts. We start by defining a response class to handle the retrieved posts:

data class PostsResponse(val posts: List<String>)

Then, we construct our API interface, utilizing the previously created client provider:

interface PostsApi {
    suspend fun get(): NetworkResult<PostsResponse>
}

class PostsApiImpl(private val clientProvider: ClientProvider) : PostsApi {

    private val client get() = clientProvider.instance()

    override suspend fun get(): NetworkResult<PostsResponse> =
        client.get(urlString = "base-url.com/posts/").toResult()
}

Now, accessing the response is straightforward. We create a class to handle the retrieval process:

class GetPosts(
    private val api: PostsApi
) {
    suspend fun get(): Unit = when (val result = api.get()) {
        is NetworkResult.Error -> {
            Logger.error(result.error.message)
        }

        is NetworkResult.Success -> {
            Logger.log(result.data)
        }
    }
}

Here, we elegantly handle both success and error scenarios using Kotlin's when expression, making the process of fetching and handling posts concise and readable.

You can deep dive further by exploring this.

Summary

In this blog, we delve into efficient error handling in Ktor Android client by introducing a custom approach using a Network Result Wrapper class. By encapsulating data and exceptions, and leveraging an extension function to map HTTP response statuses to tailored error messages, we streamline error management. Through practical implementation examples, we demonstrate how this method enhances code clarity and simplifies the process of handling network requests in Kotlin.