You won’t believe this one weird trick to handle Android Intent extras with Kotlin
CAUTION: may contain useful knowledge 💡
Android is well known for being plagued by some… unfriendly API.
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
Quite a few things changed this time! Let’s see:
- It’s now possible to get/set the extra directly on the
Intentinstance, 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
Activityand 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
SampleActivitycan access them directly. If you want to access a scope from outside, you need to force
thisto 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
withmethod from the standard library: it takes an object, and a lambda inside of which
thisis the object we passed (and so its scope is accessible). Note that the
applyfunctions 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
- 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:
- Changed the delegate signature slightly to match the documentation.
- Used the
bykeyword to bind the delegate.
- Dropped the
Delegatesuffix 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
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
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
IntentOptionsis split into a different object.
- You can now easily create the
Activityfrom outside, and even “configure it”, thanks to a lambda that has the
IntentOptionsobject as the receiver, inheriting the scope with all its extensions, and is given the
Intentinstance as its parameter (named
it, unless you give it a name inside the lambda)!
- Because the companion object doesn’t contain the
IntentOptionsanymore, the new extension
Intent.optionshas 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
As a useful little reminder, here are all the major Kotlin features we’ve used: