‘Extension classes’
When extension functions alone aren’t enough.

Note: If you’re viewing on mobile, the code snippets are better in landscape.
While reading Danny Preussler’s article Keep your interfaces simple, I had some thoughts on how to use some other Kotlin features to take it a step further. I’d suggest reading that first for context.
Lets say we have our feature flags defined like this:
sealed class Feature
object SomeFeature: Feature()
object SomeOtherFeature : Feature()
And using the same approach to determine feature availability as Danny’s article:
interface AppFeatures {
fun isEnabled(feature: Feature): Boolean
}fun AppFeatures.isDisabled(feature: Feature): Boolean =
isEnabled(feature).not()
Let’s compare the readability when using the isDisabled
extension:
appFeatures.isDisabled(SomeFeature)
As stated in Danny’s article, this certainly reads a lot better than if(!isEnabled(SomeFeature))
. Translating both of these to an English sentence would give us something like:
if not enabled some feature
vs
if disabled some feature
While it’s still perfectly readable, what we really want is for it to read as:
if some feature is disabled
But how can we do that?
Extension functions are great as they allow us to ‘add methods’ to a class without placing the noun (the class name) after the verb, adjective or phrase (the extension function). However, the problem we face is that we can’t extend Feature
with either isEnabled
or isDisabled
directly as only AppFeatures
can determine this!
What we ultimately need is a way to extend over multiple classes, in our case AppFeatures
and Feature
, but how on Earth do we do this?
Inline classes to the rescue!
Well actually, inline classes aren’t required specifically to do this, but it’s an example of where you might want to use them as we only have one outer class to extend: AppFeatures
. Inline classes are intended to wrap a single instance of an object without the memory overhead of creating an instance of the inline class at runtime. Optionally, they can extend the functionality of that object’s type.
inline class AppFeaturesExtension(val appFeatures: AppFeatures) {
val Feature.enabled: Boolean
get() = appFeatures.isEnabled(this)
val Feature.disabled: Boolean
get() = enabled.not()
}
AppFeaturesExtension
takes AppFeatures
as its single value parameter. It defines local extensions for Feature
: enabled
and disabled
. As these extensions no longer need to accept a parameter, they can be defined as extension properties instead. Using it directly would look something like this:
AppFeaturesExtension(appFeatures).apply {
if (SomeFeature.disabled) {
TODO("do something when SomeFeature is disabled")
}
}
We’ve now placed the adjective after the noun, which is exactly what we set out to address. We’ve created a new problem though, AppFeaturesExtension(appFeatures).apply {
looks pretty bad. Thankfully we can fix this with a simple extension to AppFeatures
:
inline operator fun <T> AppFeatures.invoke(
appFeaturesExtensionContext: AppFeaturesExtension.() -> T
): T = appFeaturesExtensionContext(AppFeaturesExtension(this))
The appFeaturesExtensionContext
that is passed in is an extension function of AppFeaturesExtension
that can optionally return a type T
. As the outer extension is defined as an invoke operator function, we can call it simply by doing this:
// calling appFeatures as if it were a function
// same as appFeatures.invoke, the invoke is implicit
appFeatures {
if (SomeFeature.disabled) {
TODO("do something when SomeFeature is disabled")
}
}
Takeaway
Kotlin is a powerful language that allows developers to express their intent in ways that are not possible with older languages like Java. Extension classes aren’t a real thing in Kotlin, but inline classes can be used to extend the functionality of a class with minimal overhead at runtime. Kotlin has many powerful features that can be used together to create APIs that are expressive and concise. This example showcases how you can combine inline classes, extension functions/properties, and operator overloads to improve the readability of the code that consumes your API.
You can find a full working example of this technique on Gist and Kotlin playground: