Kotlin: Sealed Classes for better handling of API Response

Anubhav
CodeX
Published in
4 min readJun 17, 2021
Photo by Steve Johnson on Unsplash

While working in Java to represent a fixed set of constants, a classic choice was to use an enum. An enum type is a special data type that enables a variable to be a set of predefined constants. The variable must be equal to one of the values that have been predefined for it.

For instance, suits in the deck of a card (CLUB, DIAMOND, HEART, SPADE), the response of an API (SUCCESS, ERROR, LOADING), etc.

public enum Response {
SUCCESS,
ERROR,
LOADING
}

Similarly, we can have enums in Kotlin as well,

enum class Response {
SUCCESS,
ERROR,
LOADING
}

So enums are pretty convenient when dealing with a fixed number of constants, but there are some limitations to them. In the enum, we only have one object per type, because of which enums can only be constants and have no state, which brings us to Sealed classes in Kotlin.

Sealed Classes

The concept of Sealed classes is pretty similar to that of enums, sealed classes represent restricted class hierarchies, i.e, our class can have a specific number of subclasses, that provide more control over the inheritance. All subclasses of a sealed class are known at compile time. Sealed classes ensure type safety by restricting the types to be matched at compile-time rather than at runtime.

Unlike the Enum class, the sealed class can have states, as we can have several objects of the same class.

Sealed classes have another distinct feature, their constructors are private by default. A sealed class is implicitly abstract and hence it cannot be instantiated. Like abstract classes, sealed classes allow us to represent hierarchies, child classes can be of any type, data class, object class, any regular class, or even another sealed class. But unlike abstract classes, we need to define these hierarchies in the same file or as nested classes.

A sealed class is "an extension of enum class”

Declaration of Sealed Class

A sealed class precedes the class modifier with the sealed keyword,

sealed class NetworkResult

I will now be showing how to use sealed class for managing an API response,

sealed class NetworkResult<T>(
val data: T? = null,
val message: String? = null
) {

class Success<T>(data: T) : NetworkResult<T>(data)

class Error<T>(message: String?, data: T? = null) : NetworkResult<T>(data, message)

class Loading<T> : NetworkResult<T>()

}

Here, I have created a sealed class with Success, Error, and Loading as the subclasses. Each of the subclasses can correspond to different possible API responses and are parameterized accordingly. For instance, if the API response is successful, we will obtain the data and there will be no error message, in case of an error, there will be no data, hence I have used nullable type for the data. Similarly for the loading state, we do not need any parameters.

I am using generics for the class, so that it can be reused for different response types.

Consider our model class to be Result,

var response: MutableLiveData<NetworkResult<Result>> = MutableLiveData()

Our response variable will store the response wrapped by the NetworkResult class,

private fun fetchResult(...) {
response.value = NetworkResult.Loading()
if (hasInternetConnection()) {
try {
val response = repository.getResult()
if (response.code() == 200) {
response.value = NetworkResult.Success(response)
} else {
response.value = NetworkResult.Error(response.message())
}
} catch (e: Exception) {
response.value = NetworkResult.Error(e.message())
}
} else {
response.value = NetworkResult.Error("No Internet connection !")
}
}

When we call fetchResult(…), I am setting the response value to be Loading, if I receive a successful response, I set its value to the obtained response body and for the error case, I am setting different error messages as per the error. This is an example where a possible result type can have different states.
Now in the calling class, we can observe our response live data and render the UI elements, based on the response,

private fun requestApiData() {    viewModel.response.observe(viewLifecycleOwner) { response ->
when (response) {
is NetworkResult.Success -> {
response.data?.let {
//bind the data to the ui
}
}
is NetworkResult.Error -> {
//show error message
Toast.makeText(
requireContext(),
response.message.toString(),
Toast.LENGTH_SHORT
).show()
}

is NetworkResult.Loading -> {
//show loader, shimmer effect etc
}
}
}
}

This is how we can use our sealed class with the API response.

Another thing to notice here is,

‘when’ statement on sealed class is exhaustive

If we leave any subclasses out, when will complain? If we implement all of them, we do not need the else statement. And in general, it will not be recommended because that way we are sure that we are doing the right thing for all of them.

Hope you learned something today, Cheers!

--

--

CodeX
CodeX

Published in CodeX

Everything connected with Tech & Code. Follow to join our 1M+ monthly readers

Anubhav
Anubhav

Responses (1)