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. By Caio Fukelmann Landau.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

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().

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.

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.