Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Delightful Delegate Design (Kotlin Budapest User Group meetup - April)

Delightful Delegate Design (Kotlin Budapest User Group meetup - April)

When developing a library, designing an easy to use API while hiding unnecessary implementation details from clients is fundamental. This talk and article looks at some of the API design choices we’ve made for our library Krate, an Android SharedPreferences wrapper.

Talk recording: https://www.youtube.com/watch?v=jTmHNo48zs0

Marton Braun

April 11, 2019
Tweet

More Decks by Marton Braun

Other Decks in Programming

Transcript

  1. Delightful Delegate Design
    Braun Márton Szabolcs
    zsmb.co zsmb13
    [email protected]

    View Slide

  2. View Slide

  3. val oneToOne: Pair = 1 to "one"

    View Slide

  4. with(user) {
    println(name)
    }
    val oneToOne: Pair = 1 to "one"

    View Slide

  5. val reader = BufferedReader(...)
    reader.use {
    val line = it.readLine()
    // ...
    }
    with(user) {
    println(name)
    }
    val oneToOne: Pair = 1 to "one"

    View Slide

  6. val pi: Double by lazy {
    val sum = (1..50_000).sumByDouble { 1.0 / (it * it) }
    sqrt(sum * 6.0)
    }
    println(pi)
    println(pi)
    val reader = BufferedReader(...)
    reader.use {
    val line = it.readLine()
    // ...
    }
    with(user) {
    println(name)
    }
    val oneToOne: Pair = 1 to "one"

    View Slide

  7. val pi: Double by
    val sum = (1..50_000).sumByDouble { 1.0 / (it * it) }
    sqrt(sum * 6.0)
    println(pi)
    println(pi)
    lazy
    }
    {

    View Slide

  8. val pi: Double by
    val sum = (1..50_000).sumByDouble { 1.0 / (it * it) }
    sqrt(sum * 6.0)
    println(pi)
    println(pi)
    lazy
    }
    {
    https://forums.swift.org/t/pitch-property-delegates/21895

    View Slide

  9. val pi: Double by
    val sum = (1..50_000).sumByDouble { 1.0 / (it * it) }
    sqrt(sum * 6.0)
    println(pi)
    println(pi)
    LazyDelegate(
    })
    {

    View Slide

  10. class Constants {
    private var pi_: Double? = null
    val pi: Double
    get() {
    if (pi_ == null) {
    val sum = (1..50_000).sumByDouble { 1.0 / (it * it) }
    pi_ = sqrt(sum * 6.0)
    }
    return pi_!!
    }
    private var e_: Double? = null
    val e: Double
    get() {
    if (e_ == null) {
    val sum = (0..20).sumByDouble { 1.0 / (1..it).fold(1, { a, x -> a * x }) }
    e_ = sum
    }
    return e_!!
    }
    }

    View Slide

  11. class Constants {
    private var pi_: Double? = null
    val pi: Double
    get() {
    if (pi_ == null) {
    val sum = (1..50_000).sumByDouble { 1.0 / (it * it) }
    pi_ = sqrt(sum * 6.0)
    }
    return pi_!!
    }
    private var e_: Double? = null
    val e: Double
    get() {
    if (e_ == null) {
    val sum = (0..20).sumByDouble { 1.0 / (1..it).fold(1, { a, x -> a * x }) }
    e_ = sum
    }
    return e_!!
    }
    }

    View Slide

  12. class Constants {
    private var pi_: Double? = null
    val pi: Double
    get() {
    if (pi_ == null) {
    val sum = (1..50_000).sumByDouble { 1.0 / (it * it) }
    pi_ = sqrt(sum * 6.0)
    }
    return pi_!!
    }
    private var e_: Double? = null
    val e: Double
    get() {
    if (e_ == null) {
    val sum = (0..20).sumByDouble { 1.0 / (1..it).fold(1, { a, x -> a * x }) }
    e_ = sum
    }
    return e_!!
    }
    }

    View Slide

  13. class Constants {
    private var pi_: Double? = null
    val pi: Double
    get() {
    if (pi_ == null) {
    val sum = (1..50_000).sumByDouble { 1.0 / (it * it) }
    pi_ = sqrt(sum * 6.0)
    }
    return pi_!!
    }
    private var e_: Double? = null
    val e: Double
    get() {
    if (e_ == null) {
    val sum = (0..20).sumByDouble { 1.0 / (1..it).fold(1, { a, x -> a * x }) }
    e_ = sum
    }
    return e_!!
    }
    }

    View Slide

  14. val pi: Double by LazyDelegate({
    val sum = (1..50_000).sumByDouble { 1.0 / (it * it) }
    sqrt(sum * 6.0)
    })
    println(pi)
    println(pi)

    View Slide

  15. val pi: Double by LazyDelegate({
    val sum = (1..50_000).sumByDouble { 1.0 / (it * it) }
    sqrt(sum * 6.0)
    })
    println(pi)
    println(pi)
    class LazyDelegate(private val initializer: () -> T) {
    }

    View Slide

  16. val pi: Double by LazyDelegate({
    val sum = (1..50_000).sumByDouble { 1.0 / (it * it) }
    sqrt(sum * 6.0)
    })
    println(pi)
    println(pi)
    class LazyDelegate(private val initializer: () -> T) {
    }
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
    }

    View Slide

  17. val pi: Double by LazyDelegate({
    val sum = (1..50_000).sumByDouble { 1.0 / (it * it) }
    sqrt(sum * 6.0)
    })
    println(pi)
    println(pi)
    class LazyDelegate(private val initializer: () -> T) {
    }
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
    }
    private var value: T? = null

    View Slide

  18. class LazyDelegate(private val initializer: () -> T) {
    }
    val pi: Double by LazyDelegate({
    val sum = (1..50_000).sumByDouble { 1.0 / (it * it) }
    sqrt(sum * 6.0)
    })
    println(pi)
    println(pi)
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
    }
    private var value: T? = null
    if (value == null) { value = initializer() }
    return value!!

    View Slide

  19. val pi: Double by LazyDelegate({
    val sum = (1..50_000).sumByDouble { 1.0 / (it * it) }
    sqrt(sum * 6.0)
    })
    println(pi) // 12597400 ns
    println(pi) // 72100 ns
    class LazyDelegate(private val initializer: () -> T) {
    private var value: T? = null
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
    if (value == null) { value = initializer() }
    return value!!
    }
    }

    View Slide

  20. val pi: Double by LazyDelegate({
    val sum = (1..50_000).sumByDouble { 1.0 / (it * it) }
    sqrt(sum * 6.0)
    })
    println(pi) // 12597400 ns
    println(pi) // 72100 ns
    class LazyDelegate(private val initializer: () -> T) {
    private var value: T? = null
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
    if (value == null) { value = initializer() }
    return value!!
    }
    }

    View Slide

  21. val pi: Double by LazyDelegate {
    val sum = (1..50_000).sumByDouble { 1.0 / (it * it) }
    sqrt(sum * 6.0)
    }
    println(pi) // 12597400 ns
    println(pi) // 72100 ns
    class LazyDelegate(private val initializer: () -> T) {
    private var value: T? = null
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
    if (value == null) { value = initializer() }
    return value!!
    }
    }

    View Slide

  22. val pi: Double by lazy {
    val sum = (1..50_000).sumByDouble { 1.0 / (it * it) }
    sqrt(sum * 6.0)
    }
    println(pi) // 12597400 ns
    println(pi) // 72100 ns
    class LazyDelegate(private val initializer: () -> T) {
    private var value: T? = null
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
    if (value == null) { value = initializer() }
    return value!!
    }
    }

    View Slide

  23. val pi: Double by lazy {
    val sum = (1..50_000).sumByDouble { 1.0 / (it * it) }
    sqrt(sum * 6.0)
    }
    println(pi) // 12597400 ns
    println(pi) // 72100 ns
    class lazy(private val initializer: () -> T) {
    private var value: T? = null
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
    if (value == null) { value = initializer() }
    return value!!
    }
    }

    View Slide

  24. val pi: Double by lazy {
    val sum = (1..50_000).sumByDouble { 1.0 / (it * it) }
    sqrt(sum * 6.0)
    }
    println(pi) // 12597400 ns
    println(pi) // 72100 ns
    class LazyDelegate(private val initializer: () -> T) {
    private var value: T? = null
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
    if (value == null) { value = initializer() }
    return value!!
    }
    }

    View Slide

  25. fun lazy(initializer: () -> T) = LazyDelegate(initializer)
    class LazyDelegate(private val initializer: () -> T) {
    private var value: T? = null
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
    if (value == null) { value = initializer() }
    return value!!
    }
    }
    val pi: Double by lazy {
    val sum = (1..50_000).sumByDouble { 1.0 / (it * it) }
    sqrt(sum * 6.0)
    }
    println(pi) // 12597400 ns
    println(pi) // 72100 ns

    View Slide

  26. https://github.com/AutSoft/Krate

    View Slide

  27. krate
    interface Krate {
    val sharedPreferences: SharedPreferences
    }

    View Slide

  28. krate
    interface Krate {
    val sharedPreferences: SharedPreferences
    }
    abstract class SimpleKrate(context: Context) : Krate {
    override val sharedPreferences =
    PreferenceManager.getDefaultSharedPreferences(context)
    }

    View Slide

  29. krate
    interface Krate {
    val sharedPreferences: SharedPreferences
    }
    abstract class SimpleKrate(context: Context) : Krate {
    override val sharedPreferences =
    PreferenceManager.getDefaultSharedPreferences(context)
    }
    app

    View Slide

  30. app
    krate
    class MyKrate(context: Context) : SimpleKrate(context) {
    var onboarded by booleanPref("onboarded")
    var appOpenCount by intPref("appOpenCount")
    var username by stringPref("username")
    }
    myKrate.onboarded = true
    myKrate.appOpenCount++
    myKrate.username = "t1gg3r"
    interface Krate {
    val sharedPreferences: SharedPreferences
    }
    abstract class SimpleKrate(context: Context) : Krate {
    override val sharedPreferences =
    PreferenceManager.getDefaultSharedPreferences(context)
    }

    View Slide

  31. app
    krate
    interface Krate {
    val sharedPreferences: SharedPreferences
    }

    View Slide

  32. krate
    app
    interface Krate {
    val sharedPreferences: SharedPreferences
    }

    View Slide

  33. krate
    app
    interface Krate {
    val sharedPreferences: SharedPreferences
    }
    class MyKrate(context: Context) : SimpleKrate(context) {
    }

    View Slide

  34. krate
    app
    interface Krate {
    val sharedPreferences: SharedPreferences
    }
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by ???
    }

    View Slide

  35. app
    krate
    interface Krate {
    val sharedPreferences: SharedPreferences
    }
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by ???
    }
    println(myKrate.score) // 0
    myKrate.score = 34
    println(myKrate.score) // 34

    View Slide

  36. app
    krate
    interface Krate {
    val sharedPreferences: SharedPreferences
    }
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by ???
    }
    println(myKrate.score) // 0
    myKrate.score = 34
    println(myKrate.score) // 34
    class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) {
    }

    View Slide

  37. krate
    app
    interface Krate {
    val sharedPreferences: SharedPreferences
    }
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by ???
    }
    println(myKrate.score) // 0
    myKrate.score = 34
    println(myKrate.score) // 34
    class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) {
    }

    View Slide

  38. app
    krate
    interface Krate {
    val sharedPreferences: SharedPreferences
    }
    class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    }
    }
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by ???
    }
    println(myKrate.score) // 0
    myKrate.score = 34
    println(myKrate.score) // 34

    View Slide

  39. app
    krate
    interface Krate {
    val sharedPreferences: SharedPreferences
    }
    class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    return sharedPreferences.getInt(key, 0)
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by ???
    }
    println(myKrate.score) // 0
    myKrate.score = 34
    println(myKrate.score) // 34

    View Slide

  40. app
    krate
    interface Krate {
    val sharedPreferences: SharedPreferences
    }
    class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    return sharedPreferences.getInt(key, 0)
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by ???
    }
    println(myKrate.score) // 0
    myKrate.score = 34
    println(myKrate.score) // 34

    View Slide

  41. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by IntDelegate(sharedPreferences, "score")
    }
    krate
    interface Krate {
    val sharedPreferences: SharedPreferences
    }
    class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    return sharedPreferences.getInt(key, 0)
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    }
    println(myKrate.score) // 0
    myKrate.score = 34
    println(myKrate.score) // 34

    View Slide

  42. app
    krate
    interface Krate {
    val sharedPreferences: SharedPreferences
    }
    class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    return sharedPreferences.getInt(key, 0)
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by IntDelegate(sharedPreferences, "score")
    }
    fun Krate.intPref(key: String): IntDelegate {
    return IntDelegate(sharedPreferences, key)
    }

    View Slide

  43. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by intPref("score")
    }
    krate
    interface Krate {
    val sharedPreferences: SharedPreferences
    }
    class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    return sharedPreferences.getInt(key, 0)
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    fun Krate.intPref(key: String): IntDelegate {
    return IntDelegate(sharedPreferences, key)
    }

    View Slide

  44. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by intPref("score")
    }
    krate
    interface Krate {
    val sharedPreferences: SharedPreferences
    }
    class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    return sharedPreferences.getInt(key, 0)
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    fun Krate.intPref(key: String): IntDelegate {
    return IntDelegate(sharedPreferences, key)
    }

    View Slide

  45. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by intPref("score")
    }
    krate
    interface Krate {
    val sharedPreferences: SharedPreferences
    }
    internal class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    return sharedPreferences.getInt(key, 0)
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    fun Krate.intPref(key: String): IntDelegate {
    return IntDelegate(sharedPreferences, key)
    }

    View Slide

  46. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by intPref("score")
    }
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    internal class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    return sharedPreferences.getInt(key, 0)
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    public fun Krate.intPref(key: String): IntDelegate {
    return IntDelegate(sharedPreferences, key)
    }

    View Slide

  47. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by intPref("score")
    }
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    internal class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    return sharedPreferences.getInt(key, 0)
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    public fun Krate.intPref(key: String): IntDelegate {
    return IntDelegate(sharedPreferences, key)
    }

    View Slide

  48. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by intPref("score")
    }
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    internal class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    return sharedPreferences.getInt(key, 0)
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    public fun Krate.intPref(key: String): IntDelegate {
    return IntDelegate(sharedPreferences, key)
    }

    View Slide

  49. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by intPref("score")
    }
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    internal class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    return sharedPreferences.getInt(key, 0)
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    public fun Krate.intPref(key: String): IntDelegate {
    return IntDelegate(sharedPreferences, key)
    }

    View Slide

  50. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by intPref("score")
    }
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    internal class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) : ReadWriteProperty {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    return sharedPreferences.getInt(key, 0)
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    public fun Krate.intPref(key: String): IntDelegate {
    return IntDelegate(sharedPreferences, key)
    }

    View Slide

  51. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by intPref("score")
    }
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    internal class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    return sharedPreferences.getInt(key, 0)
    }
    override operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    public fun Krate.intPref(key: String): IntDelegate {
    return IntDelegate(sharedPreferences, key)
    }

    View Slide

  52. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by intPref("score")
    }
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    internal class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    return sharedPreferences.getInt(key, 0)
    }
    override operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    public fun Krate.intPref(key: String): IntDelegate {
    return IntDelegate(sharedPreferences, key)
    }

    View Slide

  53. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by intPref("score")
    }
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    internal class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    return sharedPreferences.getInt(key, 0)
    }
    override operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    public fun Krate.intPref(key: String): IntDelegate {
    return IntDelegate(sharedPreferences, key)
    }

    View Slide

  54. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by intPref("score")
    }
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    internal class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    return sharedPreferences.getInt(key, 0)
    }
    override operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    public fun Krate.intPref(key: String): IntDelegate {
    return IntDelegate(sharedPreferences, key)
    }

    View Slide

  55. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by intPref("score")
    }
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    internal class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): Int {
    return sharedPreferences.getInt(key, 0)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: Int) {
    sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    public fun Krate.intPref(key: String): IntDelegate {
    return IntDelegate(sharedPreferences, key)
    }

    View Slide

  56. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by intPref("score")
    }
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    internal class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): Int {
    return sharedPreferences.getInt(key, 0)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: Int) {
    sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    public fun Krate.intPref(key: String): IntDelegate {
    return IntDelegate(sharedPreferences, key)
    }

    View Slide

  57. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by intPref("score")
    }
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    internal class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): Int {
    return sharedPreferences.getInt(key, 0)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: Int) {
    sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    public fun Krate.intPref(key: String): IntDelegate {
    return IntDelegate(sharedPreferences, key)
    }

    View Slide

  58. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by intPref("score")
    }
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    internal class IntDelegate(
    private val sharedPreferences: SharedPreferences,
    private val key: String
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): Int {
    return thisRef.sharedPreferences.getInt(key, 0)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: Int) {
    thisRef.sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    public fun Krate.intPref(key: String): IntDelegate {
    return IntDelegate(sharedPreferences, key)
    }

    View Slide

  59. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by intPref("score")
    }
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    internal class IntDelegate(
    private val key: String
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): Int {
    return thisRef.sharedPreferences.getInt(key, 0)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: Int) {
    thisRef.sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    public fun Krate.intPref(key: String): IntDelegate {
    return IntDelegate(key)
    }

    View Slide

  60. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    var score: Int by intPref("score")
    }
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    internal class IntDelegate(
    private val key: String
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): Int {
    return thisRef.sharedPreferences.getInt(key, 0)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: Int) {
    thisRef.sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    public fun Krate.intPref(key: String): ReadWriteProperty {
    return IntDelegate(key)
    }

    View Slide

  61. app
    krate

    View Slide

  62. app
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    class MyKrate(context: Context) : SimpleKrate(context) {
    var users: List? by ???
    }
    myKrate.users = listOf(User("William"), User("Virginia"))
    println(myKrate.users)

    View Slide

  63. app
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    class MyKrate(context: Context) : SimpleKrate(context) {
    va users: List? by gsonPref("users")
    }
    myKrate.users = listOf(User("William"), User("Virginia"))
    println(myKrate.users)
    r

    View Slide

  64. app
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    class MyKrate(context: Context) : SimpleKrate(context) {
    va users: List? by gsonPref("users")
    }
    myKrate.users = listOf(User("William"), User("Virginia"))
    println(myKrate.users)
    l

    View Slide

  65. app
    myKrate.users = listOf(User("William"), User("Virginia"))
    println(myKrate.users)
    class MyKrate(context: Context) : SimpleKrate(context) {
    val users: List? by gsonPref("users")
    }
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    internal class GsonDelegate(
    private val key: String
    ) : ReadOnlyProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val string = thisRef.sharedPreferences.getString(key, null)
    return Gson().fromJson(string, object : TypeToken() {}.type)
    }
    }

    View Slide

  66. app
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    class MyKrate(context: Context) : SimpleKrate(context) {
    val users: List? by gsonPref("users")
    }
    internal class GsonDelegate(
    private val key: String
    ) : ReadOnlyProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val string = thisRef.sharedPreferences.getString(key, null)
    return Gson().fromJson(string, object : TypeToken() {}.type)
    }
    }
    public fun Krate.gsonPref(
    key: String
    ): ReadOnlyProperty {
    return GsonDelegate(key)
    }

    View Slide

  67. app
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    class MyKrate(context: Context) : SimpleKrate(context) {
    val users: List? by gsonPref("users")
    }
    internal class GsonDelegate(
    private val key: String
    ) : ReadOnlyProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): Object? {
    val string = thisRef.sharedPreferences.getString(key, null)
    return Gson().fromJson(string, object : TypeToken() {}.type)
    }
    }
    public fun Krate.gsonPref(
    key: String
    ): ReadOnlyProperty {
    return GsonDelegate(key)
    }

    View Slide

  68. app
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    class MyKrate(context: Context) : SimpleKrate(context) {
    val users: List? by gsonPref("users")
    }
    internal class GsonDelegate(
    private val key: String
    ) : ReadOnlyProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val string = thisRef.sharedPreferences.getString(key, null)
    return Gson().fromJson(string, object : TypeToken() {}.type)
    }
    }
    public fun Krate.gsonPref(
    key: String
    ): ReadOnlyProperty {
    return GsonDelegate(key)
    }

    View Slide

  69. app
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    class MyKrate(context: Context) : SimpleKrate(context) {
    val users: List? by gsonPref("users")
    }
    public inline fun Krate.gsonPref(
    key: String
    ): ReadOnlyProperty {
    return GsonDelegate(key)
    }
    object : TypeToken() {}.type)
    ) : ReadOnlyProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val string = thisRef.sharedPreferences.getString(key, null)
    return Gson().fromJson(string,
    }
    }
    internal class GsonDelegate(
    private val key: String

    View Slide

  70. app
    gsonPref("users")
    class MyKrate(context: Context) : SimpleKrate(context) {
    val users: List? by
    }
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    public inline fun Krate.gsonPref(
    key: String
    ): ReadOnlyProperty {
    return GsonDelegate(key, object : TypeToken() {}.type)
    }
    object : TypeToken() {}.type)
    ) : ReadOnlyProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val string = thisRef.sharedPreferences.getString(key, null)
    return Gson().fromJson(string,
    }
    }
    internal class GsonDelegate(
    private val key: String

    View Slide

  71. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    val users: List? by
    }
    gsonPref("users")
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    public inline fun Krate.gsonPref(
    key: String
    ): ReadOnlyProperty {
    return GsonDelegate(key, object : TypeToken() {}.type)
    }
    ) : ReadOnlyProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val string = thisRef.sharedPreferences.getString(key, null)
    return Gson().fromJson(string,
    }
    }
    ,
    private val type: Type
    type)
    internal class GsonDelegate(
    private val key: String

    View Slide

  72. app
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    public inline fun Krate.gsonPref(
    key: String
    ): ReadOnlyProperty {
    return GsonDelegate(key, object : TypeToken() {}.type)
    }
    GsonDelegate(key, object : TypeToken() {}.type)
    class MyKrate(context: Context) : SimpleKrate(context) {
    val users: List? by
    } gsonPref("users")
    ) : ReadOnlyProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val string = thisRef.sharedPreferences.getString(key, null)
    return Gson().fromJson(string,
    }
    }
    ,
    private val type: Type
    type)
    internal class GsonDelegate(
    private val key: String

    View Slide

  73. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    val users: List? by
    }
    GsonDelegate( , object : TypeToken< >() {}.type)
    "users"
    gsonPref("users")
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    public inline fun Krate.gsonPref(
    key: String
    ): ReadOnlyProperty {
    return GsonDelegate(key, object : TypeToken() {}.type)
    }
    ) : ReadOnlyProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val string = thisRef.sharedPreferences.getString(key, null)
    return Gson().fromJson(string,
    }
    }
    ,
    private val type: Type
    type)
    internal class GsonDelegate(
    private val key: String
    T

    View Slide

  74. app
    GsonDelegate( , object : TypeToken< >() {}.type)
    "users" List
    gsonPref("users")
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    public inline fun Krate.gsonPref(
    key: String
    ): ReadOnlyProperty {
    return GsonDelegate(key, object : TypeToken() {}.type)
    }
    class MyKrate(context: Context) : SimpleKrate(context) {
    val users: List? by
    }
    ) : ReadOnlyProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val string = thisRef.sharedPreferences.getString(key, null)
    return Gson().fromJson(string,
    }
    }
    ,
    private val type: Type
    type)
    internal class GsonDelegate(
    private val key: String

    View Slide

  75. app
    gsonPref("users")
    class MyKrate(context: Context) : SimpleKrate(context) {
    val users: List? by
    }
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    public inline fun Krate.gsonPref(
    key: String
    ): ReadOnlyProperty {
    return GsonDelegate(key, object : TypeToken() {}.type)
    }
    ) : ReadOnlyProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val string = thisRef.sharedPreferences.getString(key, null)
    return Gson().fromJson(string,
    }
    }
    ,
    private val type: Type
    type)
    internal class GsonDelegate(
    private val key: String

    View Slide

  76. app
    gsonPref("users")
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    public inline fun Krate.gsonPref(
    key: String
    ): ReadOnlyProperty {
    return GsonDelegate(key, object : TypeToken() {}.type)
    }
    ) : ReadOnlyProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val string = thisRef.sharedPreferences.getString(key, null)
    return Gson().fromJson(string,
    }
    }
    ,
    private val type: Type
    type)
    internal class GsonDelegate(
    private val key: String
    class MyKrate(context: Context) : SimpleKrate(context) {
    val users: List? by
    }

    View Slide

  77. app
    class MyKrate(context: Context) : SimpleKrate(context) {
    val users: List? by
    } gsonPref("users")
    GsonDelegate(...)
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    public inline fun Krate.gsonPref(
    key: String
    ): ReadOnlyProperty {
    return GsonDelegate(key, object : TypeToken() {}.type)
    }
    ) : ReadOnlyProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val string = thisRef.sharedPreferences.getString(key, null)
    return Gson().fromJson(string,
    }
    }
    ,
    private val type: Type
    type)
    internal class GsonDelegate(
    private val key: String

    View Slide

  78. app
    krate
    public interface Krate {
    public val sharedPreferences: SharedPreferences
    }
    class MyKrate(context: Context) : SimpleKrate(context) {
    val users: List? by gsonPref("users")
    }
    @PublishedApi
    internal class GsonDelegate(
    private val key: String,
    private val type: Type
    ) : ReadOnlyProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val string = thisRef.sharedPreferences.getString(key, null)
    return Gson().fromJson(string, type)
    }
    }
    public inline fun Krate.gsonPref(
    key: String
    ): ReadOnlyProperty {
    return GsonDelegate(key, object : TypeToken() {}.type)
    }

    View Slide

  79. https://zsmb.co/maintaining-compatibility-in-kotlin-libraries/

    View Slide

  80. Krate*
    class MyKrate(context: Context) : SimpleKrate(context) {
    var onboarded
    var appOpenCount
    var username
    }
    myKrate.onboarded = true
    myKrate.appOpenCount++
    println(myKrate.username)
    by booleanPref("onboarded"
    by intPref("appOpenCount"
    by stringPref("username"
    )
    )
    )

    View Slide

  81. class MyKrate(context: Context) : SimpleKrate(context) {
    var onboarded: Boolean by booleanPref("onboarded", defaultValue = false)
    var appOpenCount: Int by intPref("appOpenCount", defaultValue = 0)
    var username: String? by stringPref("username")
    }
    Krate*
    myKrate.onboarded = true
    myKrate.appOpenCount++
    println(myKrate.username)

    View Slide

  82. class MyKrate(context: Context) : SimpleKrate(context) {
    // non-nullable, has default value
    var onboarded: Boolean by booleanPref("onboarded", defaultValue = false)
    var appOpenCount: Int by intPref("appOpenCount", defaultValue = 0)
    // nullable, null by default
    var username: String? by stringPref("username")
    }
    Krate*
    myKrate.onboarded = true
    myKrate.appOpenCount++
    println(myKrate.username)

    View Slide

  83. class MyKrate(context: Context) : SimpleKrate(context {
    // non-nullable, has default value
    var onboarded: Boolean
    by booleanPref("onboarded", defaultValue = false)
    var appOpenCount: Int
    by intPref("appOpenCount", defaultValue = 0)
    // nullable, null by default
    var username: String?
    by stringPref("username")
    }
    Krate*
    myKrate.onboarded = true
    myKrate.appOpenCount++
    println(myKrate.username)
    )

    View Slide

  84. Krate*
    myKrate.onboarded = true
    myKrate.appOpenCount++
    println(myKrate.username)
    class MyKrate(context: Context) : SimpleKrate(context , AppSettings {
    // non-nullable, has default value
    override var onboarded: Boolean
    by booleanPref("onboarded", defaultValue = false)
    override var appOpenCount: Int
    by intPref("appOpenCount", defaultValue = 0)
    // nullable, null by default
    override var username: String?
    by stringPref("username")
    }
    )

    View Slide

  85. class MyKrate @Inject constructor(context: Context)
    // non-nullable, has default value
    override var onboarded: Boolean
    by booleanPref("onboarded", defaultValue = false)
    override var appOpenCount: Int
    by intPref("appOpenCount", defaultValue = 0)
    // nullable, null by default
    override var username: String?
    by stringPref("username")
    }
    : SimpleKrate(context), AppSetti
    Krate*
    myKrate.onboarded = true
    myKrate.appOpenCount++
    println(myKrate.username)

    View Slide

  86. class MyKrate @Inject constructor(context: Context)
    // non-nullable, has default value
    override var onboarded: Boolean
    by booleanPref("onboarded", defaultValue = false)
    override var appOpenCount: Int
    by intPref("appOpenCount", defaultValue = 0)
    // nullable, null by default
    override var username: String?
    by stringPref("username")
    }
    Krate*
    myKrate.onboarded = true
    myKrate.appOpenCount++
    println(myKrate.username)
    : SimpleKrate(context), AppSettings {

    View Slide

  87. References
    • Krate
     https://github.com/AutSoft/Krate
    • Delightful Delegate Design
     https://blog.autsoft.hu/delightful-delegate-design/
    • Further reading
     Maintaining Compatibility in Kotlin libraries
     https://zsmb.co/maintaining-compatibility-in-kotlin-libraries/
     Swift Pitch: Property Delegates
     https://forums.swift.org/t/pitch-property-delegates/21895
     DSL Design
     https://zsmb.co/kotlin-dsl-design-with-village-dsl/
     Tips for writing a library
     https://www.kotlindevelopment.com/tips-for-writing-a-library-in-kotlin/

    View Slide

  88. Questions?
    zsmb.co zsmb13
    [email protected]
    Photo by Agnieszka Kowalczyk on Unsplash

    View Slide