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

Property Delegation with Kotlin for Android

Property Delegation with Kotlin for Android

What is property delegation in Kotlin?
How can you write your own custom delegates?
Examples of how we used delegation at sporttotal.tv and how it made Android databinding much simpler for us

Danny Preussler

June 20, 2019
Tweet

More Decks by Danny Preussler

Other Decks in Programming

Transcript

  1. Property delegates
    Danny Preussler, Kotlin Meetup Berlin

    View Slide

  2. View Slide

  3. View Slide

  4. @PreusslerBerlin
    Property delegates?

    View Slide

  5. @PreusslerBerlin
    Delegated properties?

    View Slide

  6. View Slide

  7. @PreusslerBerlin
    Properties:
    val shareEnabled: Boolean
    get() = clip.sharable

    View Slide

  8. @PreusslerBerlin
    Delegation:
    class FirebaseFeatureFlags(
    defaults: DefaultFeatureFlags
    ) : FeatureFlags by defaults

    View Slide

  9. @PreusslerBerlin
    Example
    Property delegates?

    View Slide

  10. View Slide

  11. @PreusslerBerlin
    Property Delegates
    val label by lazy {
    resources.getString(R.string.featured)
    }

    View Slide

  12. @PreusslerBerlin
    Property Delegates
    public fun lazy(
    initializer: () -> T)
    : Lazy =
    SynchronizedLazyImpl(
    initializer
    )

    View Slide

  13. @PreusslerBerlin
    Property Delegates
    public fun lazy(
    initializer: () -> T)
    : Lazy =
    SynchronizedLazyImpl(
    initializer
    )

    View Slide

  14. @PreusslerBerlin
    Property Delegates
    public fun lazy(
    initializer: () -> T)
    : Lazy =
    SynchronizedLazyImpl(
    initializer
    )

    View Slide

  15. @PreusslerBerlin
    Property Delegates
    val tracker: Tracker by inject()

    View Slide

  16. @PreusslerBerlin
    Property Delegates
    inline fun ComponentCallbacks.inject(
    name: String = "",
    scope: Scope? = null,
    noinline parameters: ParameterDefinition =
    emptyParameterDefinition()
    ) = lazy { get(name, scope, parameters) }

    View Slide

  17. @PreusslerBerlin
    Property Delegates
    inline fun ComponentCallbacks.inject(
    name: String = "",
    scope: Scope? = null,
    noinline parameters: ParameterDefinition =
    emptyParameterDefinition()
    ) = lazy { get(name, scope, parameters) }

    View Slide

  18. @PreusslerBerlin
    Property Delegates
    val viewModel: ToolbarViewModel by viewModel()

    View Slide

  19. @PreusslerBerlin
    Property Delegates
    inline fun LifecycleOwner.viewModel(
    key: String? = null,
    name: String? = null,
    noinline parameters: ParameterDefinition =
    emptyParameterDefinition()
    ) = viewModelByClass(T::class, key, name, null, parameters)

    View Slide

  20. @PreusslerBerlin
    Property Delegates
    inline fun LifecycleOwner.viewModel(
    key: String? = null,
    name: String? = null,
    noinline parameters: ParameterDefinition =
    emptyParameterDefinition()
    ) = viewModelByClass(T::class, key, name, null, parameters)

    View Slide

  21. @PreusslerBerlin
    Property Delegates
    fun LifecycleOwner.viewModelByClass(
    clazz: KClass,
    key: String? = null,
    name: String? = null,
    from: ViewModelStoreOwnerDefinition? = null,
    parameters: ParameterDefinition =
    emptyParameterDefinition()
    ) = lazy { getViewModelByClass(clazz, key, name, from,
    parameters) }

    View Slide

  22. @PreusslerBerlin
    Property Delegates
    public interface Lazy {
    public val value: T
    public fun isInitialized(): Boolean
    }

    View Slide

  23. So only Lazy allowed?

    View Slide

  24. @PreusslerBerlin
    Property Delegates
    val something by 123

    View Slide

  25. @PreusslerBerlin
    Property Delegates
    val label by lazy {
    resources.getString(R.string.featured)
    }

    View Slide

  26. @PreusslerBerlin
    Property Delegates
    public inline operator fun Lazy.getValue(
    thisRef: Any?,
    property: KProperty
    ): T = value

    View Slide

  27. @PreusslerBerlin
    Property Delegates
    val something by 123
    operator fun Int.getValue(
    thisRef: Any?,
    property: KProperty
    ): Int = this

    View Slide

  28. @PreusslerBerlin
    Property Delegates
    val something by 123
    operator fun Int.getValue(
    thisRef: Any?,
    property: KProperty
    ): Int = 456

    View Slide

  29. View Slide

  30. @PreusslerBerlin
    A Property Delegate
    var message: String by censored()

    View Slide

  31. @PreusslerBerlin
    A Property Delegate
    fun censored(): String = ““
    operator fun String.getValue(
    thisRef: Any?,
    property: KProperty
    ): Int = this.replace(“sucks”, “is suboptimal”)
    operator fun String.setValue(
    thisRef: Any?,
    property: KProperty,
    String: value
    ) { ??? }

    View Slide

  32. @PreusslerBerlin
    A Property Delegate
    fun censored(message: String = ““)
    = CensorProperty(message)

    View Slide

  33. @PreusslerBerlin
    A Property Delegate
    class CensorProperty(..): ReadWriteProperty{
    override fun setValue(..){..}
    override fun getValue(..){..}
    }

    View Slide

  34. @PreusslerBerlin
    A Property Delegate
    class CensorProperty(..): ReadOnlyProperty{
    override fun getValue(..){..}
    }

    View Slide

  35. @PreusslerBerlin
    A Property Delegate
    class CensorProperty(var message: String?) :
    ReadWriteProperty {
    override fun getValue(
    thisRef: Any?,
    property: KProperty) = message
    override fun setValue(
    thisRef: Any?,
    property: KProperty,
    value: String?
    ) {
    message = value?.replace(
    “sucks”, “is suboptimal”)
    }

    View Slide

  36. @PreusslerBerlin
    A Property Delegate
    class CensorProperty(var message: String?) :
    ReadWriteProperty {
    override fun getValue(
    thisRef: Any?,
    property: KProperty) = message
    override fun setValue(
    thisRef: Any?,
    property: KProperty,
    value: String?
    ) {
    message = value?.replace(
    “sucks”, “is suboptimal”)
    }
    }

    View Slide

  37. @PreusslerBerlin
    A Property Delegate
    class CensorProperty(var message: String?) :
    ReadWriteProperty {
    override fun getValue(
    thisRef: Any?,
    property: KProperty) = message
    override fun setValue(
    thisRef: Any?,
    property: KProperty,
    value: String?
    ) {
    message = value?.replace(
    “sucks”, “is suboptimal”)
    }
    }

    View Slide

  38. @PreusslerBerlin
    A Property Delegate
    package kotlin.reflect
    public interface KProperty : KCallable {
    @SinceKotlin("1.1")
    public val isLateinit: Boolean
    @SinceKotlin("1.1")
    public val isConst: Boolean
    public val getter: Getter

    View Slide

  39. @PreusslerBerlin
    A Property Delegate
    class CensorProperty(var message: String?) :
    ReadWriteProperty {
    override fun getValue(
    thisRef: Any?,
    property: KProperty) = message
    override fun setValue(
    thisRef: Any?,
    property: KProperty,
    value: String?
    ) {
    message = value?.replace(
    “sucks”, “is suboptimal”)
    }
    }

    View Slide

  40. @PreusslerBerlin
    A Property Delegate
    class CensorProperty(var message: String?) :
    ReadWriteProperty {
    override fun getValue(
    thisRef: Any?,
    property: KProperty) = message
    override fun setValue(
    thisRef: Any?,
    property: KProperty,
    value: String?
    ) {
    message = value?.replace(
    “sucks”, “is suboptimal”)
    }
    }

    View Slide

  41. @PreusslerBerlin
    A Property Delegate
    class CensorProperty(var message: String?) :
    ReadWriteProperty {
    override fun getValue(
    thisRef: FrameLayout?,
    property: KProperty) = message
    override fun setValue(
    thisRef: FrameLayout?,
    property: KProperty,
    value: String?
    ) {
    message = value?.replace(
    “sucks”, “is suboptimal”)
    }
    }

    View Slide

  42. @PreusslerBerlin
    A Property Delegate
    var message by CensorProperty()

    View Slide

  43. @PreusslerBerlin
    A Property Delegate
    var message: String by censored()
    fun censored(message: String = ““)
    = CensorProperty(message)

    View Slide

  44. @PreusslerBerlin
    Your Property delegates
    •Extend
    ReadOnlyProperty, ReadWriteProperty
    or
    •Use extension operator functions:
    getValue/setValue

    View Slide

  45. Your Property delegates

    View Slide

  46. @PreusslerBerlin
    Your Property delegates
    •There is a third function that can be implemented:
    operator fun provideDelegate(
    thisRef: Any?,
    property: KProperty
    ): ReadOnlyProperty {

    }
    One of the possible use cases of provideDelegate is to check
    property consistency when the property is created, not only in
    its getter or setter.

    View Slide

  47. @PreusslerBerlin
    Custom delegates

    View Slide

  48. @PreusslerBerlin
    Our delegates
    class PlayerLiveFragment : Fragment() {
    val factory: LiveGameViewModel.Provider by inject()
    val viewModel: LiveGameViewModel by unsafeLazy {
    factory(currentClip)
    }

    View Slide

  49. @PreusslerBerlin
    Our delegates
    fun unsafeLazy(initializer: () -> T)
    = lazy(LazyThreadSafetyMode.NONE, initializer)

    View Slide

  50. @PreusslerBerlin
    Our delegates
    fun unsafeLazy(initializer: () -> T)
    = lazy(LazyThreadSafetyMode.NONE, initializer)

    View Slide

  51. Show me more

    View Slide

  52. @PreusslerBerlin
    Our delegates
    val initialised by toggleOnFirstAccess(false)

    View Slide

  53. @PreusslerBerlin
    Our delegates
    val initialised by toggleOnFirstAccess(false)

    View Slide

  54. @PreusslerBerlin
    Our delegates
    val initialised by toggleOnFirstAccess(false)
    fun init() {
    if (not(initialised)) {
    subscribeToStore()
    }
    }

    View Slide

  55. @PreusslerBerlin
    Our delegates
    fun toggleOnFirstAccess(initial: Boolean) =
    object : ReadOnlyProperty {
    private val value = AtomicBoolean(initial)
    override fun getValue(
    thisRef: Any?,
    property: KProperty)
    = value.getAndSet(initial.not())
    }

    View Slide

  56. @PreusslerBerlin
    Our delegates
    fun toggleOnFirstAccess(initial: Boolean) =
    object : ReadOnlyProperty {
    private val value = AtomicBoolean(initial)
    override fun getValue(
    thisRef: Any?,
    property: KProperty)
    = value.getAndSet(initial.not())
    }

    View Slide

  57. @PreusslerBerlin
    Our delegates
    fun toggleOnFirstAccess(initial: Boolean) =
    object : ReadOnlyProperty {
    private val value = AtomicBoolean(initial)
    override fun getValue(
    thisRef: Any?,
    property: KProperty)
    = value.getAndSet(initial.not())
    }

    View Slide

  58. Show me more

    View Slide

  59. @PreusslerBerlin
    Our delegates
    var searchDisposable by disposable()

    View Slide

  60. @PreusslerBerlin
    Our delegates
    var searchDisposable by disposable()

    View Slide

  61. @PreusslerBerlin
    Our delegates
    var searchDisposable by disposable()
    searchDisposable = useCase.searchFor(query)

    View Slide

  62. @PreusslerBerlin
    Our delegates
    var searchDisposable by disposable()
    searchDisposable = useCase.searchFor(query)
    override fun onDestroy() {
    searchDisposable = null
    }

    View Slide

  63. @PreusslerBerlin
    Our delegates
    class DisposableProperty(
    private var disposable: Disposable?)
    :ReadWriteProperty {
    override fun setValue(
    thisRef: Any?,
    property: KProperty,
    value: Disposable?) {
    disposable?.dispose()
    disposable = value
    }
    override fun getValue(
    thisRef: Any?, property: KProperty)
    = disposable
    }

    View Slide

  64. @PreusslerBerlin
    Our delegates
    class DisposableProperty(
    private var disposable: Disposable?)
    :ReadWriteProperty {
    override fun setValue(
    thisRef: Any?,
    property: KProperty,
    value: Disposable?) {
    disposable?.dispose()
    disposable = value
    }
    override fun getValue(
    thisRef: Any?, property: KProperty)
    = disposable
    }

    View Slide

  65. @PreusslerBerlin
    Our delegates
    class DisposableProperty(
    private var disposable: Disposable?)
    :ReadWriteProperty {
    override fun setValue(
    thisRef: Any?,
    property: KProperty,
    value: Disposable?) {
    disposable?.dispose()
    disposable = value
    }
    override fun getValue(
    thisRef: Any?, property: KProperty)
    = disposable
    }

    View Slide

  66. Show me more

    View Slide

  67. @PreusslerBerlin
    Our delegates
    var query by observable(““)
    .doOnNext { usecase.search(it) }

    View Slide

  68. @PreusslerBerlin
    Our delegates
    var playingAd by observable(false)
    .distinctUntilChanged()
    .doOnTrue { onAdsStarted() }
    .doOnFalse { onAdsStopped() }

    View Slide

  69. @PreusslerBerlin
    Our delegates
    var playingAd by observable(false)
    .distinctUntilChanged()
    .doOnTrue { onAdsStarted() }
    .doOnFalse { onAdsStopped() }
    override fun onTimelineChanged(
    timeline: Timeline?, manifest: Any?, reason: Int)
    {
    playingAd = exoPlayer.isPlayingAd
    }

    View Slide

  70. View Slide

  71. @PreusslerBerlin
    Observable?
    •ReadOnlyProperty
    •ReadWriteProperty

    View Slide

  72. @PreusslerBerlin
    Observable?
    •ReadOnlyProperty
    •ReadWriteProperty
    •ObservableProperty

    View Slide

  73. @PreusslerBerlin
    Observable?
    var max: Int by observable(0) {
    property, oldValue, newValue ->. observed = true

    View Slide

  74. @PreusslerBerlin
    Observable?
    inline fun observable(
    initialValue: T,
    crossinline onChange: (
    property: KProperty,
    oldValue: T,
    newValue: T
    ) -> Unit
    ): ReadWriteProperty
    var max: Int by observable(0) {
    property, oldValue, newValue -> …
    }

    View Slide

  75. @PreusslerBerlin
    Observable?
    •We didn‘t use the Kotlin observable:
    •Dont care about previous value or kproperty
    •Would not work with the chaining

    View Slide

  76. @PreusslerBerlin
    Did you know…
    by vetoable

    View Slide

  77. @PreusslerBerlin
    Since Kotlin 1.3
    var max by vetoable(0) {
    property, old, new -> …
    }

    View Slide

  78. @PreusslerBerlin
    Since Kotlin 1.3
    public inline fun vetoable(
    initialValue: T,
    crossinline onChange: (
    property: KProperty,
    oldValue: T,
    newValue: T) -> Boolean)
    : ReadWriteProperty =
    object : ObservableProperty(initialValue) { }

    View Slide

  79. @PreusslerBerlin
    Since Kotlin 1.3
    public inline fun vetoable(
    initialValue: T,
    crossinline onChange: (
    property: KProperty,
    oldValue: T,
    newValue: T) -> Boolean)
    : ReadWriteProperty =
    object : ObservableProperty(initialValue) { }

    View Slide

  80. @PreusslerBerlin
    Since Kotlin 1.3
    public inline fun vetoable(
    initialValue: T,
    crossinline onChange: (
    property: KProperty,
    oldValue: T,
    newValue: T) -> Boolean)
    : ReadWriteProperty =
    object : ObservableProperty(initialValue) { }

    View Slide

  81. Show me more

    View Slide

  82. @PreusslerBerlin
    We love databinding!

    View Slide

  83. We love databinding!

    View Slide

  84. @PreusslerBerlin
    We love databinding
    android:text="@{viewModel.label}"
    name="viewModel"
    type="tv.sporttotal.android.LiveGamesViewModel" />

    View Slide

  85. @PreusslerBerlin
    We love databinding
    val label = ”some text"

    View Slide

  86. @PreusslerBerlin
    We love databinding
    val label = ObservableField("")
    val label = ”some text"

    View Slide

  87. @PreusslerBerlin
    We love databinding
    val label = ObservableField("")
    @Bindable
    var label: CharSequence = ""
    set(value) {
    field = value
    notifyPropertyChanged(BR.label)
    }
    val label = ”some text"

    View Slide

  88. @PreusslerBerlin
    We love databinding
    @Bindable
    var title: CharSequence = ""
    set(value) {
    field = value
    notifyPropertyChanged(BR.
    title)
    }
    @Bindable
    var showLoader: Boolean = false
    set(value) {
    field = value
    notifyPropertyChanged(BR.
    showLoader)
    }
    @Bindable
    var error: String? = null
    set(value) {
    field = value
    notifyPropertyChanged(BR.
    error)
    }

    View Slide

  89. @PreusslerBerlin
    We love databinding
    @get:Bindable
    var title: CharSequence by bindable(BR.title, "")
    @get:Bindable
    var showLoader: Boolean by bindable(BR.showLoader, false)
    @get:Bindable
    var error: String? by bindable(BR.error, null)

    View Slide

  90. @PreusslerBerlin
    We love databinding
    @get:Bindable
    var title: CharSequence by bindable("")
    @get:Bindable
    var showLoader: Boolean by bindable(false)
    @get:Bindable
    var error: String? by bindable(null)

    View Slide

  91. @PreusslerBerlin
    Our delegates
    @get:Bindable
    var chatRelatedVisibility by bindable(View.VISIBLE)
    private set

    View Slide

  92. @PreusslerBerlin
    Our delegates
    @get:Bindable
    var logoVisibility by bindable(View.VISIBLE)
    @get:Bindable("logoVisibility")
    val upVisibility: Int
    get() = logoVisibility.invertVisibility()

    View Slide

  93. @PreusslerBerlin
    Our delegates
    @get:Bindable
    var selected: Boolean by bindable(selected)
    .doOnNext { callback?.invoke(it) }

    View Slide

  94. @PreusslerBerlin
    @get:Bindable
    var currentMainScreen by bindable(Screen.Home.Main())
    .eager()
    .doOnNext {
    when (it) {
    is Home -> mainNavigator.goToStart()
    is Games -> mainNavigator.goToSports()
    is Profile -> mainNavigator.goToProfile()
    }
    }

    View Slide

  95. @PreusslerBerlin
    https://github.com/Aidanvii7/Toolbo
    x

    View Slide

  96. View Slide

  97. Thanks

    View Slide