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

A8b79d304b5184e5a5b0a109590f6683?s=128

Danny Preussler

June 20, 2019
Tweet

Transcript

  1. Property delegates Danny Preussler, Kotlin Meetup Berlin

  2. None
  3. None
  4. @PreusslerBerlin Property delegates?

  5. @PreusslerBerlin Delegated properties?

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

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

    defaults
  9. @PreusslerBerlin Example Property delegates?

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

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

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

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

    T) : Lazy<T> = SynchronizedLazyImpl( initializer )
  15. @PreusslerBerlin Property Delegates val tracker: Tracker by inject()

  16. @PreusslerBerlin Property Delegates inline fun <reified T : Any> ComponentCallbacks.inject(

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

    name: String = "", scope: Scope? = null, noinline parameters: ParameterDefinition = emptyParameterDefinition() ) = lazy { get<T>(name, scope, parameters) }
  18. @PreusslerBerlin Property Delegates val viewModel: ToolbarViewModel by viewModel()

  19. @PreusslerBerlin Property Delegates inline fun <reified T : ViewModel> LifecycleOwner.viewModel(

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

    key: String? = null, name: String? = null, noinline parameters: ParameterDefinition = emptyParameterDefinition() ) = viewModelByClass(T::class, key, name, null, parameters)
  21. @PreusslerBerlin Property Delegates fun <T : ViewModel> LifecycleOwner.viewModelByClass( clazz: KClass<T>,

    key: String? = null, name: String? = null, from: ViewModelStoreOwnerDefinition? = null, parameters: ParameterDefinition = emptyParameterDefinition() ) = lazy { getViewModelByClass(clazz, key, name, from, parameters) }
  22. @PreusslerBerlin Property Delegates public interface Lazy<out T> { public val

    value: T public fun isInitialized(): Boolean }
  23. So only Lazy allowed?

  24. @PreusslerBerlin Property Delegates val something by 123

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

  26. @PreusslerBerlin Property Delegates public inline operator fun <T> Lazy<T>.getValue( thisRef:

    Any?, property: KProperty<*> ): T = value
  27. @PreusslerBerlin Property Delegates val something by 123 operator fun Int.getValue(

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

    thisRef: Any?, property: KProperty<*> ): Int = 456
  29. None
  30. @PreusslerBerlin A Property Delegate var message: String by censored()

  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 ) { ??? }
  32. @PreusslerBerlin A Property Delegate fun censored(message: String = ““) =

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

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

    }
  35. @PreusslerBerlin A Property Delegate class CensorProperty(var message: String?) : ReadWriteProperty<Any?,

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

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

    String?> { override fun getValue( thisRef: Any?, property: KProperty<*>) = message override fun setValue( thisRef: Any?, property: KProperty<*>, value: String? ) { message = value?.replace( “sucks”, “is suboptimal”) } }
  38. @PreusslerBerlin A Property Delegate package kotlin.reflect public interface KProperty<out R>

    : KCallable<R> { @SinceKotlin("1.1") public val isLateinit: Boolean @SinceKotlin("1.1") public val isConst: Boolean public val getter: Getter<R> …
  39. @PreusslerBerlin A Property Delegate class CensorProperty(var message: String?) : ReadWriteProperty<Any?,

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

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

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

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

    censored(message: String = ““) = CensorProperty(message)
  44. @PreusslerBerlin Your Property delegates •Extend ReadOnlyProperty, ReadWriteProperty or •Use extension

    operator functions: getValue/setValue
  45. Your Property delegates

  46. @PreusslerBerlin Your Property delegates •There is a third function that

    can be implemented: operator fun provideDelegate( thisRef: Any?, property: KProperty<*> ): ReadOnlyProperty<MyUI, T> { … } 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.
  47. @PreusslerBerlin Custom delegates

  48. @PreusslerBerlin Our delegates class PlayerLiveFragment : Fragment() { val factory:

    LiveGameViewModel.Provider by inject() val viewModel: LiveGameViewModel by unsafeLazy { factory(currentClip) }
  49. @PreusslerBerlin Our delegates fun <T> unsafeLazy(initializer: () -> T) =

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

    lazy(LazyThreadSafetyMode.NONE, initializer)
  51. Show me more

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

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

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

    if (not(initialised)) { subscribeToStore() } }
  55. @PreusslerBerlin Our delegates fun toggleOnFirstAccess(initial: Boolean) = object : ReadOnlyProperty<Any?,

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

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

    Boolean> { private val value = AtomicBoolean(initial) override fun getValue( thisRef: Any?, property: KProperty<*>) = value.getAndSet(initial.not()) }
  58. Show me more

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

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

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

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

    override fun onDestroy() { searchDisposable = null }
  63. @PreusslerBerlin Our delegates class DisposableProperty( private var disposable: Disposable?) :ReadWriteProperty<Any?,

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

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

    Disposable?> { override fun setValue( thisRef: Any?, property: KProperty<*>, value: Disposable?) { disposable?.dispose() disposable = value } override fun getValue( thisRef: Any?, property: KProperty<*>) = disposable }
  66. Show me more

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

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

    onAdsStarted() } .doOnFalse { onAdsStopped() }
  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 }
  70. None
  71. @PreusslerBerlin Observable? •ReadOnlyProperty •ReadWriteProperty

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

  73. @PreusslerBerlin Observable? var max: Int by observable(0) { property, oldValue,

    newValue ->. observed = true
  74. @PreusslerBerlin Observable? inline fun <T> observable( initialValue: T, crossinline onChange:

    ( property: KProperty<*>, oldValue: T, newValue: T ) -> Unit ): ReadWriteProperty<Any?, T> var max: Int by observable(0) { property, oldValue, newValue -> … }
  75. @PreusslerBerlin Observable? •We didn‘t use the Kotlin observable: •Dont care

    about previous value or kproperty •Would not work with the chaining
  76. @PreusslerBerlin Did you know… by vetoable

  77. @PreusslerBerlin Since Kotlin 1.3 var max by vetoable(0) { property,

    old, new -> … }
  78. @PreusslerBerlin Since Kotlin 1.3 public inline fun <T> vetoable( initialValue:

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

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

    T, crossinline onChange: ( property: KProperty<*>, oldValue: T, newValue: T) -> Boolean) : ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) { }
  81. Show me more

  82. @PreusslerBerlin We love databinding!

  83. We love databinding!

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

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

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

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

    label: CharSequence = "" set(value) { field = value notifyPropertyChanged(BR.label) } val label = ”some text"
  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) }
  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)
  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)
  91. @PreusslerBerlin Our delegates @get:Bindable var chatRelatedVisibility by bindable(View.VISIBLE) private set

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

    upVisibility: Int get() = logoVisibility.invertVisibility()
  93. @PreusslerBerlin Our delegates @get:Bindable var selected: Boolean by bindable(selected) .doOnNext

    { callback?.invoke(it) }
  94. @PreusslerBerlin @get:Bindable var currentMainScreen by bindable<Screen>(Screen.Home.Main()) .eager() .doOnNext { when

    (it) { is Home -> mainNavigator.goToStart() is Games -> mainNavigator.goToSports() is Profile -> mainNavigator.goToProfile() } }
  95. @PreusslerBerlin https://github.com/Aidanvii7/Toolbo x

  96. None
  97. Thanks