How and When to Use Kotlin Extension Functions

Ryan Michael Kay
6 min readDec 15, 2018

In order to ground this article in with some practical examples, I’ll be referring to some snippets taken from my open source application SpaceNotes.

Now that I’ve been coding in Kotlin for over a year, I’ve had the chance to test out many of its features in production code. When I’m asked for reasons which might make Kotlin worth switching to in favour of other languages, my favourite practical examples happen to be Coroutines and Extensions.

While demonstrating direct style asynchronous programming with coroutines often intrigues more advanced developers used to callback hell or epic RxJava chained functions, it can be a glorified magic show for beginner, junior, and even some intermediate developers who haven’t yet begun to build reactive, scalable, and concurrent applications.

Extensions on the other hand provide many use cases which can help developers of all levels improve their code by:

  • Reducing the number of classes necessary to build a modular, object oriented application (helps to manage class explosions which are a natural result of applying separation of concerns in an information system such as a computer program)
  • Extending the functionality of existing types, classes, and objects (source files), without requiring that you change their source code itself (for more information, look in to the Open-Closed Principle from SOLID Principles)
  • Making application code less repetitive (therefore more concise) and more legible (assuming you don’t write ugly and illegible names for your extensions)

In this short article, I’ll discuss a few use cases of extensions which I’ve come to employ frequently.

Glossary:

Implementation: Lines of codes; don’t overthink this one.

Abstraction: This one is trickier, but if you figure it out, you’ll understand the difference between phenomenological reality (of which is comprised of abstractions, or ideas, or mental models of things real and imagined), and physical reality. Since you’re here to learn code and not philosophy, here’s an explanation specifically for programmers.

Sources: Typically I’m referring to class files of one kind or another (Kotlin has quite a few; in case you haven’t noticed). This also applies to object declarations.

Domain Model: A class which models data which may not reference external APIs or modules. POJO or POKO applies (Plain Old Java/Kotlin Object). Be warned, this word doesn’t necessarily mean the same thing to every developer.

Data Model: Since certain APIs require you to do things like include annotations or restrict you to primitive data types which might not be convenient or feasible for your Domain Models, I’d advise creating a separate Data Model to be mapped to and from your respective IO devices.

Reducing Repetitive Code Without Inheritance

Suppose you have a situation where some implementation must be used between more than one source (or you suspect that may be the case). To take a simple example, if you’ve written any Android Applications at all, you’ll very likely have called…:


Toast.makeText(activity, value, Toast.LENGTH_SHORT).show()

…at some point. It’s also possible that you forgot to type .show() and angrily paced around for a few hours before realizing this mistake (back in my day, we didn’t have so many nice Lint warnings).

In my Android projects, I’ll typically have an “Extensions” file for each module (as in Gradle Module) which contains extension functions and extensions properties which I think may end up being used in multiple different places within the module. For example, in my Android Front End Module (app), I have a file called AndroidExt.kt which contains such extensions. Even if they only end up used in a single place after all in the module, they’ll still typically reduce lines and prettify the class which uses them:

//This could absolutely be made in to single expression syntax
internal fun Fragment.makeToast(value: String) { Toast.makeText(activity, value, Toast.LENGTH_SHORT).show()
}

Naturally you’ll need to change the Receiver Type (the thing which goes before the “.” and after “fun”) if you don’t happen to be using Fragments as Views. If you’re wondering where activity comes from, you’ll find that it’s a property of the Receiver. Try changing the Receiver to “Activity”, and “activity” to “this” and you may start to see how this works (you have access to the instance of the class with which the extension is called):

//still works!
internal fun Activity.makeToast(value: String) {
Toast.makeText(this, value, Toast.LENGTH_SHORT).show()
}

Even though these extensions are defined in a file separate to the classes which will be using them (although you can include them in the same file if it fits your use case), they’ll be visible to the classes by the same means that visibility between classes is dictated (this example is internal access between classes in the same module; as intended for internal). When it’s time to use this extension in something like NoteDetailView, which happens to be a Fragment, it’s as easy as:

//override is here simply because this class implements an interface
override fun showMessage(message: String) = makeToast(message)

If you’re wondering where the brackets went, and where the “=” sign came from, watch this video on single expression syntax.

As for how this works under the hood, it’s worth exploring Kotlin’s compilation process in another article. The short version is that AndroidExt.kt will be turned in to a static class called AndroidExtKt.class at compile time, which will be created and used at run time.

Extending Functionality Without Modifying Existing Sources

One of the biggest annoyances of any beginner programmer, is the frustration that arises from breaking things that used to work fine, just because you added something new (or made any change whatsoever).

While there are plenty of things you can do to mitigate this problem (it’s called Software Architecture, and it doesn’t seem to be explained well by many online “gurus”), Kotlin’s extensions provide a completely different solution to this problem than inheritence and polymorphism. Back in my day (good ol’ Java), if you wanted to create something which could change without breaking other things, you needed to use abstractions. This typically meant a Java interface or an abstract class.

Although that’s a rabbit hole worth going down, I won’t discuss that process here.

In any case, one of the side effects of using a variety of different data sources of a variety of APIs in SpaceNotes (in this case Room and Firestore), is that I needed to write a lot of mapping functions to get the domain models in and out of their respective repositories. After messing around with a few options, I opted to create another module level extensions file (DataExt.kt), and implement each mapping operation as either an extension function or extension property:

internal val Note.toRegisteredRoomNote: RegisteredRoomNote    
get() = RegisteredRoomNote(
this.creationDate,
this.contents,
this.upVotes,
this.imageUrl,
this.safeGetUid
)
internal val RegisteredRoomNote.toNote: Note
get() = Note(
this.creationDate,
this.contents,
this.upVotes,
this.imageUrl,
User(this.creatorId)
)

These extensions properties are used in RoomLocalAnonymousRepositoryImpl (the name is a bit shorter than I prefer, but it’ll have to do), to make life very easier:

class RoomLocalAnonymousRepositoryImpl(private val noteDao: AnonymousNoteDao) : ILocalNoteRepository {
override suspend fun deleteAll(): Result<Exception, Unit> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}

override suspend fun updateAll(list: List<Note>): Result<Exception, Unit> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}

override suspend fun updateNote(note: Note): Result<Exception, Unit> {
val updated = noteDao.insertOrUpdateNote(note.toAnonymousRoomNote)

return when {
//TODO verify that if nothing is updated, the resulting value will be 0
updated == 0L -> Result.build { throw SpaceNotesError.LocalIOException }
else -> Result.build { Unit }
}
}

override suspend fun getNote(id: String): Result<Exception, Note?> = Result.build { noteDao.getNoteById(id).toNote }


override suspend fun getNotes(): Result<Exception, List<Note>> = Result.build { noteDao.getNotes().toNoteListFromAnonymous() }


override suspend fun deleteNote(note: Note): Result<Exception, Unit> {
noteDao.deleteNote(note.toAnonymousRoomNote)
return Result.build { Unit }
}
}

Also in DataExt.kt, notice that I used an extension function to map from List<Note> to List<AnonymousRoomNote>:

internal fun List<AnonymousRoomNote>.toNoteListFromAnonymous(): List<Note> = this.flatMap {
listOf(it.toNote)
}

You may be wondering, when should I use an extension property versus an extension function? Although I’d say extension functions are more generally applicable, my decision use extension properties for the Note is mostly because I found it more aesthetically pleasing to do so (one less set of brackets).

It honestly depends on the situation at hand, but like all of these things, a good start is to just try one or the other and go with what works, followed by what makes your code easier to read.

Closing Thoughts

Extensions provide three terrifically useful benefits:

  • Elimination of redundant code (Don’t Repeat Yourself!)
  • They can drastically improve code legibility if you use good names
  • They can allow you to extend the functionality of existing sources, such as Note, without having to modify their source itself

The article was meant as a very light introduction to their practical usage; so expect more complicated examples in the future!

Support wiseAss Brogrammers and Siscripters:

Follow wiseAss:
https://www.instagram.com/wiseassbrand/
https://www.facebook.com/wiseassblog/
https://twitter.com/wiseass301
http://wiseassblog.com/
https://www.linkedin.com/in/ryan-kay-808388114

Join the community:
https://join.slack.com/t/wiseass/shared_invite/enQtMjg5OTc3NTk0MjI5LTc5ZmNiYjAyNzJmMzFiNGFjNzhiZTU4ZDdhNDE2NzA0ODEwZTJjYjNjOWNjMDA5YmEzMDlhN2ZjMGI1Yzg5NGM

Support wiseAss here:
https://www.paypal.me/ryanmkay

--

--

Ryan Michael Kay

Self-taught software developer & student of computer science.