Kotlin is Dope And So Are Its Custom Property Delegates

Amanda Hill

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:

  1. It must be a list containing only items of type Player
  2. 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.
  3. 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!?