Swift Apprentice (New Edition)

Go from novice to expert in Swift with hands-on activities in Xcode playgrounds with our freshly-updated book.

Home Android & Kotlin Tutorials

Kotlin Enums Tutorial for Android: Getting Started

In this tutorial, you’ll build an Android app, using the full potential of Kotlin Enums to handle a list of cartoon avatars and help your users easily create their profiles.

4.9/5 8 Ratings

Version

  • Kotlin 1.4, Android 9.0, Android Studio 4.1

Kotlin enums are a crucial part of the language. They are, in their most basic form, collections of predefined constant elements. Now that’s a mouthful!

In this tutorial, you’ll learn what that means, plus what enums are capable of in Kotlin. You’ll build an the app that uses enums to create a cartoon avatar selection feature, and learn about:

  • What enums are — and aren’t — useful for
  • How to use when() for flow control with enums
  • A way to add extra functionality to enums
  • How to reference an enum constant using its name
  • An overview of best practices and gotchas to look out for

Ready to learn more? Then read on!

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.

Open your project on Android Studio 4.1 or higher. Select Open an Existing Project and point it to the starter project you just extracted:

Instruction for opening a project on Android Studio

The starter project has a few files you should get acquainted with:

  • MainActivity.kt: Your app’s main user interface, which is what you see when you run the project
  • CartoonAvatarAdapter.kt: This file contains a ListAdapter. MainActivity uses this adapter to populate the RecyclerView containing all the avatar options below the “Pick an avatar” TextView (which is empty for now).
  • AvatarSelectedActivity.kt: You’ll use this activity to display the user’s selection. For now, there’s nothing starting it.
  • Utils.kt: This contains a small extension method that returns the first letter of a string and capitalizes it. You’ll use this method in AvatarSelectedActivity to display the user’s initial.
Note: For more details about RecyclerView, read our Android RecyclerView Tutorial.

Additionally, some strings you’ll use in this tutorial are already conveniently included for you. Find them in res/values/strings.xml.

Build and run the project. You’ll see a screen that says “Enter your details to continue”:

Starter project with text that says "Enter your details to continue"

This app doesn’t do anything yet. Next, you’ll make it come to life — and learn about Kotlin enums in the process! But first, an introduction to enums.

Kotlin Enums

In short, Kotlin enums are a way to describe a set of predefined constants in your code. But what does this mean, exactly?

When you’re modeling your app’s data, some concepts will have a limited set of possibilities. An example is an app that supports light and dark modes. In a case like this, you define an enum with two predefined cases: LIGHT and DARK. No other cases are possible — those are the only possible modes.

When you have a well-defined realm of possibilities, enums are a great candidate. By using them, your code has compile-time checks that prevent the developer from using an undefined case. You can’t reference an app mode called COLORFUL when the only options are LIGHT and DARK. That same characteristic makes enums unsuitable for variable or dynamic sets of data. You can’t define enum cases at runtime, and there’s no reason to: It would defeat their purpose of holding a predefined set of values. It’s also better for performance, as you won’t have to compare String values, for instance.

Are Enums Bad for Memory Management?

The question of whether enums are bad for memory management emerged a long time ago, way before Google announced that Kotlin was its preferred language for Android app developers. The short answer is: not anymore. Enums are fine now. There are two improvements that contributed to this:

  • Dalvik vs ART: Before Lollipop, Android used a runtime called Dalvik. Engineers designed this runtime for a different reality than today: Devices had minimal resources back then. Since enums are constant values, they’re always using their necessary memory, and memory allocations then were very expensive. Fast-forward to Lollipop, and the default runtime changed to Android Runtime, or ART. This is a vast improvement for modern devices, and the concerns around enums are no longer relevant. You can find more information about this in the Android Runtime (ART) and Dalvik article from Android’s documentation.
  • R8: Another concern about enums was the app size overhead, as they use significantly more space than integer constants. Without diving too deep into this, R8 is a compiler that cleans and optimizes your code when building your app. It replaces ProGuard. Among its many other features, R8 helps remove the overhead from enums whenever possible by understanding how your code uses them. The Android documentation has more details about this in the Shrink, obfuscate, and optimize your app guide.

Now that you know enums are good, it’s time to define them.

Defining a Basic Enum

In Android Studio, open CartoonAvatarAdapter.kt. Inside the adapter class, you’ll see a data class called Item. Notice how it contains imageResource, which is an Android resource ID for the avatar image. You’ll change that now to instead use an enum.

In the project navigation:

  1. Right-click your main package, com.raywenderlich.android.cartoonsocialclub, and select New > Kotlin File/Class
  2. .

  3. In the pop-up, name your file CartoonAvatar, select Enum class and press enter.

Instructions for creating a CartoonAvatar enum on Android Studio

Inside the newly created CartoonAvatar.kt add the following code:

enum class CartoonAvatar {
  DOG,
  MONSTER,
  OCTOPUS,
  NONE
}

Now, you’ve got your enum for the different types of avatars that the app will support. Congratulations on creating your first enum!

Using The Enum

Now that you have an enum with all possible avatar selections, it’s time to use it with your code. Go back to CartoonAvatarAdapter.kt, find // TODO 1 and replace Item with the following:

data class Item(
  // 1
  val avatar: CartoonAvatar,
  var isSelected: Boolean
) {
  // 2
  @DrawableRes
  var imageResource = when (avatar) {
    CartoonAvatar.DOG -> R.mipmap.avatar_dog
    CartoonAvatar.MONSTER -> R.mipmap.avatar_monster
    CartoonAvatar.OCTOPUS -> R.mipmap.avatar_octopus
    CartoonAvatar.NONE -> R.mipmap.avatar_none
  }
}

Here’s what the code above does:

  1. Instead of receiving the Android drawable resource directly, Item now receives an instance of your new enum, CartoonAvatar. In doing so, you’ll ensure that an item can only be one of the possible cartoon avatars.
  2. Now, imageResource is a computed property. Using when(), your logic defines which image resource to use based on the CartoonAvatar passed. Now the user of Item doesn’t need to know about Android image resources.

Benefits of Enums

When you use enums to define a set of constants, you get extra help from the IDE. Auto-complete can easily understand all the possible cases of CartoonAvatar and suggest code for you.

Additionally, it’s possible to validate that code is exhaustive. What that means is, since you predefine all enum cases, it’s easy to automatically detect if your when() expression doesn’t handle them all.

Give it a try: Remove one of the lines in when(). Android Studio will instantly complain that your expression isn’t exhaustive. Very handy! Add that line back now, of course.

Build and run. You’ll see the same screen as before; nothing has changed yet:

Starter project with text that says "Enter your details to continue"

But now it’s time to start mixing things up!

Displaying an Avatar for Each Enum Case

So far, all those changes you did had no effect on how the app looks. This is about to change.

Open MainActivity.kt and populate avatarChoices by replacing emptyList<Item>() below // TODO 2 with the following:

CartoonAvatar.values().map {
  Item(avatar = it, isSelected = false)
}

The code above uses values() to get an array of all CartoonAvatar cases. This method is present in all Kotlin enums. Then, you call map() in this array to convert it to a list of Item, which is what the adapter takes. [TODO: Is Item correct or should it be Items?] Since the adapter is set up using this property further down in MainActivity, it now has a list of all possible cartoon avatars a user can select. You can see that isSelected is set to false by default.

Build and run. You’ll now see a screen with a selection of avatar options:

Project showing avatars for selection

Next, you’ll start adding data to your enums.

Add Extra Data to Enums

Enum constants can also have data attached to them. This is helpful for cases where you need to translate an enum case to a different type. Each cartoon character has a respective drawable resource, so you’ll use this enum feature instead of when() to simplify your code even more.

Open CartoonAvatar.kt and replace the class with the code below:

import androidx.annotation.DrawableRes

enum class CartoonAvatar(@DrawableRes val drawableRes: Int) {
  DOG(R.mipmap.avatar_dog),
  MONSTER(R.mipmap.avatar_monster),
  OCTOPUS(R.mipmap.avatar_octopus),
  NONE(R.mipmap.avatar_none)
}

In the code above, you added drawableRes to CartoonAvatar. All those values are constant, so each case has a predefined drawableRes defined. With this, you’re now able to map each enum case to its respective drawable resource.

Replace when() With Enum Property

Go back to CartoonAvatarAdapter. Inside Item, replace the entire imageResource property with the following:

@DrawableRes 
var imageResource = avatar.drawableRes

The code above is using the drawableRes you just defined in the enum to replace the when() expression. Your adapter code is now a little simpler.

Build and run. It’ll look the same as before:

Project showing avatars for selection

But you’ll change that.

Enums Automatically Implement Equality

Your app isn’t reacting to changes yet. When the user types their name and selects an avatar, a button should show up to let them move to the next step. You’ll implement that logic now.

Open MainActivity.kt and add the following property below // TODO 3:

private var selectedAvatar: CartoonAvatar? = null

Here, you created a property that will hold the selected avatar when the user taps one.

Next, find selectAvatar() and add the following lines below // TODO 4:

// 1
selectedAvatar = avatar.avatar

// 2
avatarChoices.forEach { it.isSelected = it.avatar == selectedAvatar }
cartoonAvatarAdapter.notifyDataSetChanged()

The adapter calls the method above when the user selects an avatar option. This logic has been set up for you in setupRecyclerView() in the starter project.

Here’s what the code above is doing:

  1. Updates the value of selectedAvatar with the new selection.
  2. Updates avatarChoices in the activity to set isSelected to true in the newly selected item. Here, you’re using another useful feature of Kotlin enums: They implement equality automatically. That’s why you can use == to compare them inside forEach. After you update the list to set isSelected, cartoonAvatarAdapter.notifyDataSetChanged() takes care of updating the adapter. The adpter will then reflect the changes in the view.

Build and run. Now select one of the avatar options and the view will reflect it:

Cartoon social club app with an avatar selected

See the selected avatar?

Using the Name of an Enum Case

When your user selects an avatar and types their name, they’ll be ready to move to the next step. To do that, open AvatarSelectedActivity.kt and paste the following code below // TODO 5:

companion object {
  // 1
  private const val EXTRA_FULL_NAME = "com.raywenderlich.android.cartoonsocialclub.FULL_NAME"
  private const val EXTRA_CARTOON = "com.raywenderlich.android.cartoonsocialclub.CARTOON"

  // 2
  fun newIntent(fullName: String, selectedAvatar: CartoonAvatar, context: Context): Intent {
    return Intent(context, AvatarSelectedActivity::class.java).apply {
      putExtra(EXTRA_FULL_NAME, fullName)
      // 3
      putExtra(EXTRA_CARTOON, selectedAvatar.name)
    }
  }
}

You’ve created a companion object with a method called newIntent(). Here’s what the code does in detail:

  1. Defines two constants that you’ll use to reference Intent extras later
  2. newIntent() is an Android convention: It takes the data an activity needs and returns an Intent to start it.
  3. Another enum goodie: Kotlin provides name for all enum cases. It’s a string, and using it, you’ll be able to get a reference to that same case later with CartoonAvatar.valueOf(name). This property’s value will be something like "MONSTER".

Next, go back to MainActivity and add the following method below // TODO 6:

private fun setupContinueButton() {
  // 1
  fullNameEditText.doAfterTextChanged { fullName ->
    if (fullName.toString().isNotBlank() && selectedAvatar != null) {
      continueButton.visibility = View.VISIBLE
    } else {
      continueButton.visibility = View.GONE
    }
  }
  
  // 2
  continueButton.setOnClickListener {
    val selectedAvatar = selectedAvatar ?: return@setOnClickListener
    val firstName = fullNameEditText.text.toString()
    val intent = AvatarSelectedActivity.newIntent(firstName, selectedAvatar, this)
    startActivity(intent)
  }
}

Here’s what the code above does:

  1. Tells fullNameEditText to execute the following logic when its text changes. If the user has typed their full name and selected an avatar, the Continue button is visible. Otherwise, it’s hidden.
  2. Adds an onClickListener to the Continue button that starts the AvatarSelectedActivity when the user taps it. The Intent holds the user selections.

Now, add a call to setupContinueButton() at the end of onCreate(), below // TODO 7.

And finally, paste the following lines at the end of selectAvatar():

if (fullNameEditText.text.isNotBlank()) {
  continueButton.visibility = View.VISIBLE
}

The code above makes the Continue button visible when the user selects an avatar if they already typed their name.

Build and run. Type your name, select your avatar and press Continue. The app will take you to the next step:

Screen saying "Your profile has been created" before it can display data

But before continuing, you’ll learn a bit about name.

Caveats of the name Property

You shouldn’t use name for user-facing text. These strings are not localized, which means there’s no easy way to translate them to different languages if your app supports that. Instead, you can use name for:

  • Programmatically referencing enum cases: A great example is the above. When your app needs to pass enum cases around via Intent, you can use its name to reference it.
  • Debugging: It can be useful to log a given enum case for debugging purposes. For example: You could log the selected avatar name to help identify a bug in your code.

With that said, you should not hardcode those strings to reference them later. Renaming an enum case will change its name, and the hardcoded string will no longer work. A special case to look out for is that of using the name to persist data that references enums. This can lead to trouble! Imagine if you rename DOG to SUNGLASS_DOG. The new version is no longer backwards compatible, and the persisted string now references a non-existent case! Make sure to account for the possibility of changing names when designing your code to avoid trouble later.

The Enum ordinal Property

In addition to name, enums also have an ordinal property. This references the zero-based position at which the enum case appears in code. Using CartoonAvatar as an example, see what each case’s ordinal is:

enum class CartoonAvatar(@DrawableRes val drawableRes: Int) {
  DOG(R.mipmap.avatar_dog), // ordinal = 0
  MONSTER(R.mipmap.avatar_monster), // ordinal = 1
  OCTOPUS(R.mipmap.avatar_octopus), // ordinal = 2
  NONE(R.mipmap.avatar_none) // ordinal = 3
}
Note: The ordinal can change as you move cases around, add more, remove some, etc. For that reason, be mindful when using it. Avoid hardcoding ordinal values to reference enum cases. Use name to reference them instead of ordinal.

Now, you’ll continue with the app.

Obtaining Data From the Intent

Open AvatarSelectedActivity.kt and add the following method below // TODO 8:

private fun populateView(fullName: String, cartoonAvatar: CartoonAvatar) {
  // 1
  fullNameTextView.text = fullName

  // 2
  when (cartoonAvatar) {
    CartoonAvatar.NONE -> {
      initialTextView.text = fullName.uppercasedInitial()
      selectedAvatarImageView.visibility = View.GONE
      initialTextView.visibility = View.VISIBLE
    }
    else -> {
      selectedAvatarImageView.setImageResource(cartoonAvatar.drawableRes)
      selectedAvatarImageView.visibility = View.VISIBLE
      initialTextView.visibility = View.GONE
    }
  }
}

Here’s what the code above does:

  1. Populates the fullNameTextView with the full name passed via Intent
  2. Using when() with an else clause, you’re able to omit having to reference all cases individually. If the selected CartoonAvatar is NONE, populating the view means placing the user’s name’s initial in the initialTextView and hiding the selected avatar image. For all other cases, it means hiding the initialTextView and displaying the selected avatar’s image.

Now, paste the following below // TODO 9:

private fun loadDataFromIntent() {
  // 1
  val extras = intent.extras
  val fullName = extras?.getString(EXTRA_FULL_NAME)
  val cartoonEnumCaseName = extras?.getString(EXTRA_CARTOON)
  if (cartoonEnumCaseName == null) {
    // 2
    finish()
    return
  }

  // 3
  val cartoonAvatar = CartoonAvatar.valueOf(cartoonEnumCaseName)
  populateView(fullName ?: "", cartoonAvatar)
}

Here’s what you’re doing in the code above:

  1. Read the data passed via Intent, using the constants defined in the companion object.
  2. If you started the activity incorrectly — without using the newIntent() method — this finishes it because there’s nothing it can do.
  3. Enums have a static method called valueOf() that returns the enum case from its name. Using this method, you recover the case that the user selected in the previous screen. With all the necessary data, you call populateView().

To combine everything, find // TODO 10 at the end of onCreate() and add the following below it:

loadDataFromIntent()

Build and run. After typing your name and selecting an avatar, press Continue.

Profile Created screen showing profile picture and name

As you can see above, the view will now show the name and avatar.

Enums Can Have Methods

The last part your app is missing is a description of the selected avatar in AvatarSelectedActivity.

Here, you’ll use another functionality enums provide: They can have methods. So, open CartoonAvatar.kt and add ; right after the last case of the enum so that it looks like the following:

NONE(R.mipmap.avatar_none); // <- Added the semicolon

This is necessary to separate the cases from the rest of the enum body.

Between NONE and }, paste the following code:

abstract fun selectionDescription(context: Context): String

The line above defines a method that every enum case must implement. Since this is an abstract method and none of your cases implement that yet, Android Studio will start complaining. To fix it, right-click the case highlighted in red and select Show Context Actions ▸ Implement Members like below:

Pop-up with "Implement members" selected

Select Implement members in the pop-up above. A window will show up. Press OK. See the image below for reference:

Implement Members with missing methods

Make sure to select the missing method and then press OK.

Repeat the steps above for all enum cases. Android Studio will add TODO() marks to those methods. Replace them with the following code:

DOG:

val name = context.getString(R.string.avatar_description_dog)
return context.getString(R.string.your_selected_avatar_description, name)

MONSTER:

val name = context.getString(R.string.avatar_description_monster)
return context.getString(R.string.your_selected_avatar_description, name)

OCTOPUS:

val name = context.getString(R.string.avatar_description_octopus)
return context.getString(R.string.your_selected_avatar_description, name)

NONE:

return "" // No avatar selected, show no text

The code above returns a string like "Your selected avatar: Dog With Sunglasses", depending on which character received the call to selectionDescription(). For the special case of NONE, it returns an empty string.

Finally, plug all that in by opening AvatarSelectedActivity.kt and adding the following line inside populateView(), below fullNameTextView.text = fullName:

selectedAvatarDescriptionTextView.text = cartoonAvatar.selectionDescription(this)

The code above sets the description you just defined in the enum as the text for selectedAvatarDescriptionTextView.

Build and run. After making all selections, you'll see the description below the profile picture and name card:

Final project: avatar selected and description showing

Great work! But what if you want to know more about enums?

Enum Extras: Interfaces and Synthetic Methods

What else can you do with enums? See below:

  • Kotlin enums are classes. As such, they can implement interfaces. Then, each enum case can implement methods and properties separately, or the enum definition can provide a general implementation. Note that enums cannot extend from other classes.
  • There are global functions to allow accessing enums in a generic way: enumValues() and enumValueOf().
Note: While the features above are available for specific cases, it's generally bad practice to add a lot of extra functionality to enums.

An example of a bad practice is adding too much data to enum constants. Unlike instances of classes, which the garbage collector helps deallocate when they are no longer needed, enum constants may stay in memory for the lifetime of your application. So, be mindful of your app's memory consumption.

For examples and a quick overview of the main features of enums, check out Kotlin's official documentation on enum classes.

Where to Go From Here?

Download the final project using the Download Materials button at the top or bottom of this tutorial.

In this tutorial, you learned:

  • What enums are and how to use them when you need a predefined set of constants
  • How ART and R8 helped reduce the performance hit that used to be a problem with enums in the past
  • How to use when() for flow control using enums
  • Why exhaustive code matters and how enums help with this
  • How to add extra data to enum constants
  • How every enum in Kotlin implements equality automatically
  • The use of name to pass enum cases via Intent
  • How to add methods to enums

To learn even more about enums and their functionality, check out the Kotlin and Android: Beyond the Basics with Sealed Classes tutorial.

We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!

Average Rating

4.9/5

Add a rating for this content

8 ratings

More like this

Contributors

Comments