Kotlin Demystified: When to use custom accessors

Photo by Barn Images on Unsplash

While working on UAMP, I found it convenient to wrap several support library classes in a helper for a variety of reasons. One of the features I wanted from this wrapper was the ability to quickly reference the “transport controls” class, which is used to give commands such as play and pause.

To make it convenient, I added a property to my wrapper:

val transportControls: MediaControllerCompat.TransportControls
= mediaController.transportControls

The thing to keep in mind is that mediaController is a lateinit var, because it's created during a callback to a service connection. Thus this code crashes immediately when the app is run:

E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.android.uamp.next, PID: 18641
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.android.uamp.next/com.example.android.uamp.MainActivity}:
kotlin.UninitializedPropertyAccessException: lateinit property mediaController has not been initialized

The fix is to add a custom accessor to this property:

val transportControls: MediaControllerCompat.TransportControls
get() = mediaController.transportControls

But why?

The easiest way to understand what’s going on is to look at the bytecode for the two variations. Doing this in Android Studio is as easy as Tools > Kotlin > Show Kotlin Bytecode. Then select the Decompile button to look at the equivalent Java code.

Without get():

@NotNull
private final TransportControls transportControls = mediaController.getTransportControls();
@NotNull
public final TransportControls getTransportControls() {
return this.transportControls;
}

With get():

@NotNull
public final TransportControls getTransportControls() {
return mediaController.getTransportControls();
}

In the first case, a member variable transportControls was being set when the class was constructed. In the second, it actually calls mediaController.getTransportControls() every time the transportControls property is accessed.

In my case, this is exactly what I wanted to do. I don’t want to save the object returned from mediaController.getTransportControls() in my object, I just want to make it more convenient to write that code. Because of this, the code should be val transportControls get() = ....

This isn’t always the right approach, however. Take this code for example:

val ticket get() = findTicketInDb()

Yikes! Each time ticket is accessed, the code will search the database for the ticket! That's probably not what we want. In this case the right approach would be to write:

val ticket = findTicketInDb()

This will call findTicketInDb() once, when the object is created, and return that same value each time the property ticket is accessed.

Kotlin properties can be deceptively simple. Understanding the difference between properties computed once when the object is created, and those with custom get() methods, which are computed each time the property is accessed, can mean the difference between writing clear code and introducing unintended behavior into your app.

Be sure to follow the Android Developers publication for more great content, and stay tuned for more articles on Kotlin!