Sealed classes and interfaces represent restricted class hierarchies that provide more control over inheritance. All direct subclasses of a sealed class are known at compile time. No other subclasses may appear after a module with the sealed class is compiled.
As we can see that all subclasses of a sealed class is known at compile time it gives us the ability to use it inside when block and the compiler is able to warn us if any branch is not handled, in exactly the same way that it does for enumerations.
Lets look at an example
Consider this class as an example.
sealed class ResourceState<out T> {
object Loading: ResourceState<Nothing>()
data class Error(val message: String): ResourceState<Nothing>()
data class Success<T>(val data: T): ResourceState<T>()
}
In this example class there are three subclasses of parent ResourceState
class.
The first one is Loading
which doesn't contain any additional property. This will be responsible for handling the loading state of the UI.
The second one is Error
which contains a property named message
which is of String
type. This will be responsible for handling the error state of the UI. We can use the message property and display it not the UI.
And the last and most important one is Success
which will hold the data when API request or any other type of data fetching is succeed.
Now lets see how we can use this Sealed class for handling UI states in Android applications.
Consider this operation using coroutines inside a ViewModel class which which fetches some data from a repository.
private val _someData = MutableLiveData<ResourceState<SomeResponse>>()
val someData: LiveData<SomeResponse> get() = _someData
fun fetchSomething() = viewModelScope.launch {
try {
_someData.value = ResourceState.Loading
_someData.value = repository.fetchSomeDataFromServer()
} catch (e: Exception) {
_someData.value = ResourceState.Error(e.message)
}
}
As we can see, initially we have set the value of _someData
as ResourceState.Loading
in the next line it fetches some data from the server and if it success it sets the value of _someData
to ResourceState.Success
with the received value from server. If somehow the request fails an exception will be thrown and the catch block will be called. In this case the value for _someData
will be set to ResourceState.Error
with an error message included.
Now lets see how can we observe this inside UI related classes live Activity
or Fragment
.
viewModel.someData.observe(this) {
when (it) {
is ResourceState.Loading -> {
/*
* We can display some loading indicators
* when this block is executed
* */
}
is ResourceState.Success -> {
/*
* This will be called when the data fetching succeed
* we can then hide the loading or error indicator
* and display the data into the UI
* */
populateViewWithData(it.data)
}
is ResourceState.Error -> {
/*
* This will be called when the data fetching failed
* and an exception is thrown
* we can show the error state with some message in this scenario
* */
}
}
}
Hope this was useful!