Kotlin trick: writing shared Enum utility code

@p-y.wtf

What's this?

👋 Hi, I'm P-Y, I work as an Android Eng at Block. I was just looking for an opportunity to try out whtwnd.com, a blog service built on atproto (Bluesky's protocol), to try out the integration between blog, comments & Bluesky feed. 5 minutes ago I wrote a cute piece of Kotlin code, shared it in our Slack, then realized that'd work out just fine as a quick public blog post.

❤️ Enums ❤️

I love enums. I really do! If you've been doing Android for a while, you might have seen my troll project back in 2015: Fragnums ("An enum based library to replace fragments").

So, naturally, I end up creating a lot of enums, and I want to share utility code that operates on those enums.

Let's say we have a Screen enum:

enum class Screen {
  CartScreen,
  HomeScreen,
}

See that trailing comma? I also love trailing commas. I love them especially because they make Romain Guy grumpy.

Sorted entries

Because I like neat things, I want to make sure entries are sorted alphabetically. This could be a compiler check, but instead we'll do it with a runtime check from a static initializer, using Enum.entries (previously Enum.values()):

enum class Screen {
  CartScreen,
  HomeScreen,
  ;

  companion object {
    init {
      val names = entries.map { it.name }
      check(names == names.sorted()) {
        "Screen enum entries should be sorted alphabetically"
      }
    }
  }
}

Shared order check

I want to sort more enums! Let's move the order check to a shared utility function. We can replace Screen.entries with enumEntries<T>() (previously enumValues<T>()) combined with a reitied type paramater:

object Enums {
  inline fun <reified T : Enum<T>> checkEntriesSorted() {
    val names = enumEntries<T>().map { it.name }
    check(names == names.sorted()) {
      "${T::class.java.simpleName} enum entries should be sorted alphabetically"
    }
  }
}

enum class Screen {
  CartScreen,
  HomeScreen,
  ;

  companion object {
    init {
      Enums.checkEntriesSorted<Screen>()
    }
  }
}

Unique analytics names

Let's add an analytics name to our screens, for logging purposes. We want that name to be unique, so we need a new shared utility: Enums.checkUniqueAnalyticsName<T>().

interface HasAnalyticsName {
  val analyticsName: String
}

enum class Screen(override val analyticsName: String) :
  HasAnalyticsName {

  CartScreen("Cart"),
  HomeScreen("Home"),
  ;

  companion object {
    init {
      Enums.checkEntriesSorted<Screen>()
      Enums.checkUniqueAnalyticsName<Screen>()
    }
  }
}

Where it's at

How can we implement Enums.checkUniqueAnalyticsName<T>()? We need a function that operates on enums (so that we can call enumEntries<T>()) but we also need to pull out analyticsName from each individual entry.

So we want to constrain T to extend Enum<T> and also implement HasAnalyticsName. In Kotlin we can do that with the where keyword:

  inline fun <reified T> checkUniqueAnalyticsName()
    where T : Enum<T>,
          T : HasAnalyticsName {
    val names = enumValues<T>().map { it.analyticsName }
    check(names == names.distinct()) {
      "${T::class.java.simpleName} enum entries should have a unique analytics name"
    }
  }

Isn't this neat?

Fin

Comments welcome! I want to see if the integration with Bluesky makes this more fun than with other blogging platforms.

p-y.wtf
P-Y

@p-y.wtf

https://p-y.wtf | Android Eng @Square | hashnode.com/@py | instagram.com/py.ricau | LeakCanary, Radiography, square/logcat, square/PAPA. He/Him

Post reaction in Bluesky

*To be shown as a reaction, include article link in the post or add link card

Reactions from everyone (0)