In case you haven’t heard yet, Kotlin is dope 👌🏼. Like, really, seriously, super dope. Don’t believe me? Well allow me to try and convince you. Today I want to share with you one of my favorite features of the language - Delegated Properties - and some of the ways I’ve used them in REAL apps that I’ve REALLY shipped. And if by the end of the post you are not convinced, well that would be a shame, but on the bright side at least I got you to read a post that used the word “dope” in the first sentence.
What Are Delegated Properties?
Delegated Properties are regular ol’ properties who delegate how they are either written or read (think getters or setters) to some other function. For example, let’s say we have the following class.
class Dog {
var isAGoodDoggo: Boolean = true
}
Now we all know that ALL dogs are good dogs 🐶, so let’s say we want
isAGoodDoggo
to always return true
. Now you’re probably thinking, “Amanda,
we can just make the property an immutable val
and then it can’t be changed.”
And you’d be totally right, but that wouldn’t make for a very fun example. So
let’s assume we have some requirement that this must be a mutable var
. As you
might have guessed, another way to ensure that the value for isAGoodDoggo
will
always return true
is by creating a custom property delegate. In this case,
since it’s a var
we’ll need a to implement a ReadWriteProperty
.
Let’s look at the definition for a ReadWriteProperty
:
public interface ReadWriteProperty<in R, T> {
/**
* Returns the value of the property for the given object.
* @param thisRef the object for which the value is requested.
* @param property the metadata for the property.
* @return the property value.
*/
public operator fun getValue(thisRef: R, property: KProperty<*>): T
/**
* Sets the value of the property for the given object.
* @param thisRef the object for which the value is requested.
* @param property the metadata for the property.
* @param value the value to set.
*/
public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
Now for the fun part, writing our own!
Writing Our Own Delegate
class GoodDoggoDelegate : ReadWriteProperty<Any?, Boolean> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean {
return true
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) {
}
}
You might notice I didn’t implement anything for setValue
and that’s because
it doesn’t matter what we put here since getValue
is always going to return
true
.
Now let’s update our Dog
class:
class Dog {
var isAGoodDoggo: Boolean by GoodDoggoDelegate()
}
And that’s it! When we read from isAGoodDoggo
, the property will delegate to
the GoodDoggoDelegate
instance and call the getValue()
function.
This was admittedly a very basic and not very useful example just to get you comfortable with the concept. Now I’m going to share some more practical examples that I have stolen straight out of apps that I have developed.
ObservableProperty
Kotlin provides some custom implementations of delegated properties for us to
use. One example is ObservableProperty
which mimics all of the basic
functionality of a read/write property, but also calls a callback function when
the property is changed. The real “dope” aspect to the callback function used in
ObservableProperty
is that it passes through the old and the new value. As you
might imagine this can be very useful in applications - as it is a much cleaner
alternatives to callbacks.
Delegates.observable
Example
Example time ⏰! Below are some snippets from an audio recording app. The app
allowed a user to record some audio, play it back, and then upload it to a
server. These various states were reflected in code in the following sealed
class
.
sealed class RecordingViewState {
class PreRecord : RecordingViewState()
class Recording : RecordingViewState()
class Reviewing : RecordingViewState()
class Transmitting : RecordingViewState()
}
In our Activity
, or wherever you want to hold onto this state, we will use a
Delegates.observable
to listen to changes to the state:
private var state: RecordingViewState by Delegates.observable<RecordingViewState>(PreRecord())
{ _, old, new ->
when (new) {
is PreRecord -> { }
is Recording -> { }
is Reviewing -> { }
is Transmitting -> { }
}
}
Now, we can now easily update the necessary views depending on the new state,
while also utilizing the old
state in cases where a particular transition or
animation is necessary. For example, if the new state is Recording
you might
want different animations if the old (or previous) state was PreRecord
vs if
it was Reviewing
. Like this…
private var state: RecordingViewState by Delegates.observable<RecordingViewState>(PreRecord())
{ _, old, new ->
control_record_btn.state = new
when (new) {
is PreRecord -> { }
is Recording -> {
+ if (old is PreRecord) {
+ animatePreRecordViewsAway()
+ }
+ if (old is Reviewing) {
+ hideReviewingButtons()
+ }
}
is Reviewing -> { }
is Transmitting -> { }
}
}
Custom Properties As Custom Data Structures
Another use case I have found for custom delegated properties is to use them to create my own data structures. On a recent project, I was developing an app that centered around baseball ⚾️. One of the screens in the app showed a list of all the players who threw the ball during a given play and I needed a backing data structure to reflect this list. The data structure needed to meet the following requirements:
- It must be a list containing only items of type
Player
- The list was ordered by the order of throws - i.e. the player at index 0 threw the ball to the player at index 1, and so on.
- The list cannot have consecutive duplicates - i.e. a player cannot throw to himself.
I couldn’t use a regular ol’ List
as that would not prohibit me from adding
duplicates 🙅🏻. I couldn’t use a Set
as the same Player
might be in the list
twice - just not directly before or after himself 🙅🏻. So what was I to do!?
🤷🏻♀️
One option might have been to use a List
and just have a crazy custom setter.
But that felt ugly and easy for someone in the future to remove or edit
incorrectly. As you probably guessed, the option I chose was to use a custom
ObservableProperty
.
The custom delegate looks like this:
class NoConsecutiveDuplicates<T>(initialValue: List<T>) : ObservableProperty<List<T>>(initialValue) {
override fun beforeChange(property: KProperty<*>, oldValue: List<T>, newValue: List<T>): Boolean {
return oldValue.isEmpty() || newValue.isEmpty() || oldValue.last() != newValue.last()
}
}
And to use this custom delegate, the code looks like this:
private var playersInvolved: List<Player> by NoConsecutiveDuplicates(emptyList())
So when we attempt to add a Player
to the list, the delegate’s beforeChange
method is called, which returns true
if the new value is being added to the
list, and returns false
, if the new value is discarded and the list remains
the same.
While this same functionality can certainly be achieved in other ways, I like
the clarity and reusability of using a custom ObservableProperty
and I hope
you do too! And maybe you might even start telling your friends how dope
Kotlin’s delegated properties are!?