You won’t believe this one weird trick to handle Android Intent extras with Kotlin

CAUTION: may contain useful knowledge 💡

It seems that I really cannot refrain myself from using click-bait titles… Oh well.

Android is well known for being plagued by some… unfriendly API.

How an Intent is created is a good example, because it’s a very indirect way to pass some parameters to another component (the Activity) over which we have no control.

On top of that, Java doesn’t make it easy (nor pretty) to maintain high-level abstractions that declare and document the “real” intentions of developers.

Thankfully, now we have Kotlin: a “practical” language from JetBrains (the creators of Intellij IDEA, which is the base of Android Studio) that is just delightful to use and happens to be a match made in heaven for Android.

In this article I’ll illustrate the thought process that occurred a few months ago while trying to improve this very issue. You don’t have to necessarily like or use the approach that will be described, but the journey is sure to show you how to put some of the most powerful Kotlin features to good use.

As a side note, some of these features will be barely explained, but I will always put a link to the (excellent) official documentation the first time they’re mentioned, and group them together at the end of the article, for reference.

If you want a TL;DR: go directly to the end of the article, you’ll find a link to a new library that introduces some Kotlin ✨ to handle Intent and Bundle extras in a nicer way.

One way that is sometimes necessary to manage Intent extras in Java is through static getters/setters on the Activity class. It’s not a great solution: it fragments what should be a single “thing” (getting/setting the extra) and pollutes the rest of the class while doing so.

You also need a constant for the “extra name”, which should generally be unique if you don’t want any accidental interference, as the Intent can contain any number of extras coming even from outside of the app. It also can’t be generated purely at runtime, as the app process might die and be restarted. Because of these limitations, it’s hard to automatically generate it.

In the gist below you can see how it’s normally declared and used, both inside and outside of the Activity, including an example usage from Kotlin.

Throughout the article I will use an example String extra called “message”.

Let’s start by porting the above code directly to Kotlin:

What changed? Essentially nothing, except that if you want to use the getters/setters from Java now you have to reference the companion object of the Activity (unless you put the @JvmStatic annotation on the functions).

Note that the companion object is named by default Companion, and that like any other object declaration it’s both a class and a singleton instance of that class.

Let’s give a descriptive name to the companion object and transform the getter/setter in extension functions over Intent:

Quite a few things changed this time! Let’s see:

  • It’s now possible to get/set the extra directly on the Intent instance, instead of passing it as a parameter.
  • Naming the companion object has made the usage from Java much more readable, although the signature of the getter/setter is untouched.
  • There’s now a slight difference between handling the getter/setter inside the Activity and outside, and that’s due to one essential property of extension functions: scope. These extensions are scoped to where they are declared, which in this case is the companion object of the Activity. Because companion objects are special, they “extend their scope” to the class they’re declared in, so SampleActivity can access them directly. If you want to access a scope from outside, you need to force this to become something else, and you do that through special lambdas that have a receiver. Notice how we did it in the gist by using the with method from the standard library: it takes an object, and a lambda inside of which this is the object we passed (and so its scope is accessible). Note that the run and apply functions would work as well, I just find them less readable in this case.

Now that we have two extension functions on Intent, getting and setting a value on it, it’s natural to group them into an extension property:

Let’s recap what happened here:

  • You can now read and write the extra as if it was a field of the Intent.
  • The getter/setter logic is now encapsulated in a single declaration.
  • The Java signature is untouched.

This is looking good, but there’s still the boilerplate of having to specify how to read/write from the Intent every single time we declare any “extra property”.

Let’s extract a delegate:

Nothing that you can’t do in Java.

Now, it would be amazing if Kotlin had a clever syntax to bind this delegate without having to explicitly declare the getter/setter…

Oh, wait, it totally has, it’s called delegated properties:

We just:

  • Changed the delegate signature slightly to match the documentation.
  • Used the by keyword to bind the delegate.
  • Dropped the Delegate suffix to make it more readable.

Note that we are now given a KProperty instance, which is a representation of the bound property: it contains a ton of useful information, including the name of the property, and it’s also possible to extract the “owner” of it, which in this case is the IntentOptions companion object.

By the way: since Kotlin 1.1-M04 there’s an useful operator for intercepting the delegate creation/binding called provideDelegate.

Can we use it to remove the last piece of boilerplate, that annoying constant we have to write by hand every time? Oh yes:

In the above gist we’re using the name provided by the user, if there is one, otherwise we fall back to the “owner canonical name” (in this case me.eugeniomarletti.sample.SampleActivity.IntentOptions) followed by :: and the name of the property (in this case message).

As a last, optional step, I can show you how to evolve this approach.

Right now the companion object of our Activity is named IntentOptions because it doesn’t contain anything else.

What if we wanted to do more with it?

Here is what changed:

  • The companion object extends the abstract class ActivityCompanion, and IntentOptions is split into a different object.
  • You can now easily create the Intent for the Activity from outside, and even “configure it”, thanks to a lambda that has the IntentOptions object as the receiver, inheriting the scope with all its extensions, and is given the Intent instance as its parameter (named it, unless you give it a name inside the lambda)!
  • Because the companion object doesn’t contain the IntentOptions anymore, the new extension Intent.options has been provided to make it easy to access the options inside the Activity; it uses the same trick as above to access the scope. The Java signature also changed slightly.

Phew! The definition of our “Intent extra property” really can’t get slimmer than that, but there’s still one open question: what about extras of different types, do they all need a different delegate?

Yes, but luckily that job is already done in this library I just open sourced!

The basic approach is identical to what is outlined in this article, just optimised and taken a bit further to allow painless customisation. Oh and it works with Bundle too.

As a useful little reminder, here are all the major Kotlin features we’ve used: