Skip to content

Instantly share code, notes, and snippets.

@alifhasnain
Last active March 6, 2022 04:12
Show Gist options
  • Save alifhasnain/61b4cfbd2b9cad038c174730b906e07a to your computer and use it in GitHub Desktop.
Save alifhasnain/61b4cfbd2b9cad038c174730b906e07a to your computer and use it in GitHub Desktop.

What are Sealed classes?

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.

So, what gives it more control on handling UI state?

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment