Kotlin and Android: Beyond the Basics with Sealed Classes
In this tutorial, you’ll learn about Kotlin sealed classes and how to use them to manage states when developing Android apps. By Harun Wangereka.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Kotlin and Android: Beyond the Basics with Sealed Classes
25 mins
- Getting Started
- Advantages of Sealed Classes
- Multiple Instances
- Inheritance
- Architecture Compatibility
- “When” Expressions
- Managing State in Android
- Classical State Management
- Requesting the Characters
- Addressing Errors
- Using a Try-Catch Statement to Catch Errors
- Handling HTTP Errors
- Indicating Download Progress
- Handling Null Responses
- Problems With Classical State Management
- Simplifying States With Sealed Classes
- Modeling States With Sealed Classes
- Applying the States to Your App
- Displaying the Character Details
- Where to Go From Here?
Managing UI state is one of the most important things you can do as an app developer. Done wrong, state management causes problems like poor error handling, hard-to-read code and a lack of separation between UI and business logic.
With Kotlin, sealed classes make it simpler to manage state.
In this tutorial, you’ll learn the advantages of Kotlin sealed classes and how to leverage them to manage states in your Android apps.
You’ll do this by building the RickyCharacters app, which displays a list of characters from the television show. You’ll download the character data from The Rick And Morty API and manage states in the app by using the Retrofit library to make network calls.
If you’re unfamiliar with Kotlin, take a look at our Kotlin For Android: An Introduction tutorial.
To brush up on your Android skills, check out our Android and Kotlin tutorials.
You can also learn more about sealed classes in our Kotlin Sealed Classes tutorial.
If you’re unfamiliar with Kotlin, take a look at our Kotlin For Android: An Introduction tutorial.
To brush up on your Android skills, check out our Android and Kotlin tutorials.
You can also learn more about sealed classes in our Kotlin Sealed Classes tutorial.
Getting Started
Download the begin project by clicking the Download Materials button at the top or bottom of the tutorial.
Extract the zip file and open the begin project in Android Studio by selecting Open an existing Android Studio project from the welcome screen. Navigate to the begin directory and click Open.
Once the Gradle sync finishes, build and run the project using the emulator or a device. You’ll see this screen appear:
You probably expected some Rick and Morty Characters on the home screen, but that feature isn’t ready yet. You’ll add the logic to fetch the character images later in this tutorial.
Advantages of Sealed Classes
Sealed classes are a more powerful extension of enums. Here are some of their most powerful features.
Multiple Instances
While an enum constant exists only as a single instance, a subclass of a sealed class can have multiple instances. That allows objects from sealed classes to contain state.
Look at the following example:
sealed class Months {
class January(var shortHand: String) : Months()
class February(var number: Int) : Months()
class March(var shortHand: String, var number: Int) : Months()
}
Now you can create two different instances of February
. For example, you can pass the 2019
as an argument to first instance, and 2020
to second instance, and compare them.
Inheritance
You can’t inherit from enum classes, but you can from sealed classes.
Here’s an example:
sealed class Result {
data class Success(val data : List<String>) : Result()
data class Failure(val exception : String) : Result()
}
Both Success
and Failure
inherit from the Result
sealed class in the code above.
Kotlin 1.1 added the possibility for data classes to extend other classes, including sealed classes.
Architecture Compatibility
Sealed classes are compatible with commonly-used app architectures, including:
- MVI
- MVVM
- Redux
- Repository pattern
This ensures that you don’t have to change your existing app architecture to leverage the advantages of sealed classes.
“When” Expressions
Kotlin lets you use when
expressions with your sealed classes. When you use these with the Result
sealed class, you can parse a result based on whether it was a success or failure.
Here’s how this might look:
when (result) {
is Result.Success -> showItems(result.data)
is Result.Failure -> showError(result.exception)
}
This has a few advantages. First, you can check the type of result
using Kotlin’s is
operator. By checking the type, Kotlin can smart-cast the value of result
for you for each case.
If the result is a success, you can access the value as if it’s Result.Success
. Now, you can pass items without any typecasting to showItems(data: List
.
If the result was a failure, you display an error to the user.
Another way you can use the when
expression is to exhaust all the possibilities for the Result
sealed class type.
Typically, a when
expression must have an else
clause. In the example above, however, there are no other possible types for Result
. The compiler and IDE know you don’t need an else
clause.
Look what happens when you add an InvalidData
object to the sealed class:
sealed class Result {
data class Success(val data : List<String>) : Result()
data class Failure(val exception : String) : Result()
object InvalidData : Result()
}
The IDE now shows an error on the when statement because you haven’t handled the InvalidData
branch of Result
.
The IDE knows you didn’t cover all your cases here. It even knows which possible types result
could be, depending on the Result
sealed class. The IDE offers you a quick fix to add the missing branches.
when
expression exhaustive if you use it as an expression, you use its return type or you call a function on it.
Managing State in Android
Fasten your seat belt, as you’re about to travel back in time to a multiverse where sealed classes did not exist. Get ready to see how Rick and Morty survived back then.
Classical State Management
Your goal is to fetch a list of characters for the “Rick and Morty” television show from The Rick And Morty API and display them in a Recycler view.
Open CharactersFragment.kt in com.raywenderlich.android.rickycharacters.ui.views.fragments.classicalway and you’ll see the following code:
class CharactersFragment : Fragment(R.layout.fragment_characters) {
// 1
private val apiService = ApiClient().getClient().create(ApiService::class.java)
private lateinit var charactersAdapter: CharactersAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 2
charactersAdapter = CharactersAdapter {character ->
displayCharacterDetails(character)
}
recyclerViewMovies.adapter = charactersAdapter
fetchCharacters()
swipeContainer.setOnRefreshListener {
fetchCharacters()
}
}
// 3
private fun displayCharacterDetails(character: Character) {
val characterFragmentAction =
CharactersFragmentDirections.actionCharactersFragmentToCharacterDetailsFragment(
character)
findNavController().navigate(characterFragmentAction)
}
To explain the code above:
- You have two variables at the very top. One represents the
retrofit
service class and the other represents therecyclerview
adapter class. - Inside
onViewCreated
, you initialize the adapter class and set the adapter for the Recycler view, which displays the list of characters. There’s a call tofetchCharacters
and you also set a refresh listener to theSwipeRefeshLayout
that calls thefetchCharacters
on refresh. -
displayCharacterDetails(character: Character)
is responsible for navigating to the CharacterDetailsFragment and showing character details once you tap on each character.
Below displayCharactersDetails(character: Characters)
, there’s more code responsible for requesting the characters and displaying the appropriate UI.
// 1
private fun fetchCharacters() {
//TODO 1 Make a get characters Request
//TODO 2 Catch errors with else statement
//TODO 3 Catch errors with try-catch statement
//TODO 4 Catch HTTP error codes
//TODO 5 Add refresh dialog
//TODO 6 Handle null response body
}
// 2
private fun showCharacters(charactersResponseModel: CharactersResponseModel?) {
charactersAdapter.updateList(charactersResponseModel!!.results)
}
// 3
private fun handleError(message : String) {
errorMessageText.text = message
}
// 4
private fun showEmptyView() {
emptyViewLinear.show()
recyclerViewMovies.hide()
hideRefreshDialog()
}
// 5
private fun hideEmptyView() {
emptyViewLinear.hide()
recyclerViewMovies.show()
hideRefreshDialog()
}
// 6
private fun showRefreshDialog() {
swipeContainer.isRefreshing = true
}
// 7
private fun hideRefreshDialog() {
swipeContainer.isRefreshing = false
}
Here’s an explanation for the code above:
-
fetchCharacters()
is responsible for fetching the characters from the api and handling the response. There’s a couple of //TODOs here, which you’ll address one by one in this tutorial. -
showCharacters(charactersResponseModel: CharactersResponseModel?)
takesCharacterResponseModel
as an argument. This is a data class representing the response from the Rick and Morty API. The function sends the list of characters to theCharactersAdapter
. -
handleError(message : String)
takes aString
as an argument and sets the message to aTextView
. -
showEmptyView()
hides the Recycler view and shows the empty view. This is a linear layout with an image and an error text view for displaying the error message. NoticerecyclerViewMovies.hide()
uses an extension function from com.raywenderlich.android.rickycharacters.utils.extensions.kt. This is also where you find theshow()
extension function. -
hideEmptyView()
hides the empty view and shows the Recycler view. -
showRefreshDialog()
sets the refresh property of SwipeRefreshLayout totrue
. -
hideRefreshDialog
sets the refresh property of SwipeRefreshLayout tofalse
.
With the code in this class explained, we’re ready to begin coding. In the next section, you’ll add begin to request characters from the Rick and Morty API. Time to get schwifty!