Kotlin "By" Property Delegation: Create Reusable Code

In my previous post Kotlin “By” Class Delegation: Favor Composition Over Inheritance I covered using by keyword for class delegations. You can easily reuse and compose existing concrete implementations in your code. In this post I will be going over using the same by keyword for properties and how you can use them to create reusable code and reduce awkward boilerplate code. I will also include some Android specific samples with delegated properties.

Delegated Properties Basis

JetBrains has a really detailed official documentation about Delegated Properties. If you would like to read official one or you already have some ideas about it, you can skip this section.

For me the best way to learn a new language feature is always to start with some real life use cases. Let’s consider a simple requirement: I have a Resource object and it is expensive to create, so I just want to create it once and keep reusing the same instance in my code.

You might create a field and a dedicate method to handle to creation and assignment of the shared instance:

1
2
3
4
5
6
7
8
private Resource resource;

private Resource getResource() {
    if (resource == null) {
        resource = new Resource();
    }
    return resource;
}

Yes, I know this method would not work properly in multi-threading environment and you might need to use object locks or double-checked locking. For now let’s assume the method will be always called from the same thread just like all the lifecycle methods are called in the “main thread” in Android.

Now we have a “lazily initialized” filed and it’s created only once. But now we would like to have more fields like that and because the implementation will be identical to the previous one, we will basically end up with replicating the existing code, which is simply a bad approach.

To reuse the code we write above, we can use Kotlin’s delegation for properties to encapsulate common logics. All we need for creating a “read-only” delegated property is to implement the following method in any class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class CreateOnceResource {

    private var resource: Resources? = null

    override operator fun getValue(thisRef: Any, property: KProperty<*>): Resources {
        var r = resource
        if (r == null) {
            r = Resources()
            resource = r
        }
        return r
    }
}

Note that the class does not have to implement any special interface or abstract class but a specific function getValue(). The compiler will check the signature and make calls to the method when you try to use the delegated property. With the class implemented, we can use it directly for any fields as if they were normal properties:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Arbitrary {

    private val resource1 by CreateOnceResource()    
    private val resource2 by CreateOnceResource()

    fun method() {
        resource1.configuration
        resource2.configuration
    }
}

Convenient interfaces: ReadOnlyProperty and ReadWriteProperty

Even though Kotlin does not force you to implement any interface, but the function signatures are just awkward to memorize by heart. Therefore Kotlin has two interfaces defined for you to implement read-only and read-write delegated properties:

1
2
3
4
5
6
7
8
interface ReadOnlyProperty<in R, out T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
}

interface ReadWriteProperty<in R, T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
    operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}

Built-in Delegated Properties

There are a few standard delegate implementations that Kotlin provides in the library so we don’t have to reinvent the wheel again. The following content are referenced from the official doc for Delegated Properties. Check it out if you would like to read more about it.

Standard Delegates:

  • Lazy: As the name suggested, the internal property will be only initialized once based on the specified LazyThreadSafetyMode. By default SYNCHRONIZED is used. In some special cases, you may use NONE to avoid the cost of synchronization.
  • Delegates.observable() & Delegates.vetoable(): observable() allows you to “see” the changes made to the property. You might want to trigger some actions when the value changes. User authentication states will be a good example. vetoable() is built on top of observable(). It allows you to even “veto” the change depends on your use case. Say you would like to have odd number for a specific property, you can cancel all the assignment of even numbers to it.
  • Delegated values in one map: Kotlin actually allows you to use one single map for multiple delegated properties. You can delegate a Int property using the syntax val age: Int by map. The key of the map is always String and name of properties, but value can be in arbitrary types.

Examples in Android

One reason why Kotlin has been picked by developers from different areas is that you can quickly turn existing libraries for business logic into reusable functions that is more concise and readable using Kotlin language features. Android SDK APIs sometimes can be verbose. Let’s see what we can use Kotlin property delegations in Android development.

Before jumping directly into the usage, let’s review the sample app without delegation. The complete sample can be downloaded here. The app has one custom Application and one Activity that shows a “launch count”. The count is only incremented in Application#onCreate() when the app is launched, and the actual value is stored in SharedPreferences.

Screenshot

screenshot

Code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class DelegateApplication : Application() {

    lateinit var appComponent: AppComponent

    @Inject internal lateinit var preferencesManager: PreferencesManager

    override fun onCreate() {
        super.onCreate()

        appComponent = DaggerAppComponent
                .builder()
                .application(this)
                .build()

        appComponent.inject(this)

        preferencesManager.launchCount += 1
    }

}

class DelegateActivity : AppCompatActivity() {

    @Inject lateinit var preferencesManager: PreferencesManager

    private lateinit var textView: TextView
    private var textSize: Float = 0f

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_delegate)

        (application as DelegateApplication).appComponent.inject(this)

        textSize = resources.getDimension(R.dimen.font_size)

        textView = findViewById(R.id.main_text)
        textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
        textView.text = getString(R.string.app_launch_count, preferencesManager.launchCount)
    }
}

class PreferencesManager @Inject constructor(context: Context) {

    private val preferences = context.getSharedPreferences("myApp", Context.MODE_PRIVATE)

    var launchCount: Int
        get() = preferences.getInt(PREF_KEY, 0)
        set(value) {
            preferences.edit().putInt(PREF_KEY, value).apply()
        }

    companion object {
        private const val PREF_KEY = "count"
    }
}

The code works just fine without any delegation. In DelegateApplication we create the AppComponent, inject PreferencesManager, finally increment the launchCount. In DelegateActivity we inject the manager and display the count on a text view. Note that we also get the dimension for the font size in the activity, which is only for demonstration purpose. Normally you would get it from XML resource files.

Even though the sample app seems trivial to implement, it has many common tasks we do in Android development:

  1. SharedPreferences
  2. Resources.getDimension()
  3. View.findViewById()

These are common enough that I think we can use them for showing the magic of property delegations.

#1 SharedPreferences

Let’s start with SharedPreferences. We can encapsulate the common access pattern of integer preferences into a IntPreference class and use them directly in the PreferencesManager :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class IntPreference(
        private val preferences: SharedPreferences,
        private val name: String,
        private val defaultValue: Int = 0
) : ReadWriteProperty<Any, Int> {

    override fun getValue(thisRef: Any, property: KProperty<*>): Int {
        return preferences.getInt(name, defaultValue)
    }

    override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) {
        preferences.edit().putInt(name, value).apply()
    }
}

fun SharedPreferences.int(name: String) = IntPreference(this, name, 0)

The extension function is just another pretty way of initializing a instance of IntPreference. Now the PreferencesManager becomes one line of code and the IntPreference can be reused to create another integer preference:

1
2
3
class PreferencesManager @Inject constructor(context: Context) {
    var launchCount by context.getSharedPreferences("myApp", Context.MODE_PRIVATE).int("count")
}

I have seen other posts about using delegation on shared preferences. Some of them make certain “tweaks” to make the code even shorter. For example instead of passing in a name, they use property argument to read the declared name of the property as the name of the preference. The approach might look awesome at beginning, but you have to be careful if you enable obfuscation for your final app. The final name can change from build to build, and I don’t think that is something you would expect. Also I use 0 as the default value. You might want to change it based on your use cases especially when you use the same approach for string preferences. Android SDK allows you to have a null string preferences, so you would want to either define a “reasonable” default value or make the delegated property “nullable”.

#2 Resources.getDimension()

You might wonder how delegation can help here since the call to get dimension is really a one-liner:

1
textSize = resources.getDimension(R.dimen.font_size)

But if you ever use libraries like ButterKnife, you would know it’s much more readable when you can declare those dimensions in the same line so you don’t have look through entire source file trying to find out where they are assigned:

1
2
3
@BindDimen(R.dimen.spacer1) Float spacer1;
@BindDimen(R.dimen.spacer2) Float spacer2;
@BindDimen(R.dimen.spacer3) Float spacer3;

Here is how you can reuse the built-in Lazy to achieve it:

1
fun Activity.bindDimension(@DimenRes id: Int) = lazy { resources.getDimension(id) }

and then declare your dimension property in your activity:

1
private val textSize by bindDimension(R.dimen.font_size)

The reason why we have to wrap the function call inside lazy is because the activity is not fully “created” when the instance is created. If you try to access resource before Activity#onCreate() you would get a NullPointerException on Context#getResources.

#3 View.findViewById()

Some of you might think you can apply the same mechanism we learn from bindDimension() on the findViewById(). Yes but with some “gotchas”.

The code here will always work properly:

1
fun <T : View> Activity.bindView(@IdRes id: Int) = lazy(LazyThreadSafetyMode.NONE) { findViewById<T>(id) }

But the following might not:

1
fun <T : View> Fragment.bindView(@IdRes id: Int) = lazy(LazyThreadSafetyMode.NONE) { view!!.findViewById<T>(id) }

It does not always work not because of the !! nor the use of LazyThreadSafetyMode.NONE. It’s because of the evil fragments…

Recall that Lazy will only run initializer lambda once and cache the result. That is not a problem for Activity since the content view in an activity is destroyed only when the activity is also destroyed. However that is that also the case for Fragment. A fragment can go to “detached” state and the view will be re-created when the host fragment is re-attached again. When view is created again, the previous instance is still cached by the lazy property and some weird issues happen.

I learned this from Chris Banes’s tweet:

So be very careful when you’re trying to be “lazy”

Conclusion

Property delegation is a powerful feature that allows developers to write reusable and more readable code. The code can be more expressive to your own business logic. There are many built-in delegates, but Kotlin makes it easy to write your own as well. Always be cautious about the language features and the frameworks you work with so that you won’t get surprises.

Happy Kotlining!

comments powered by Disqus
Content written by Eric Lin with passions in programming
Built with Hugo
Theme Stack designed by Jimmy