Kotlin Demystified: When to use custom accessors

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.