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. 2.
  2. 3.
  3. 6.
  4. 10.
  5. 12.

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

    T) : Lazy<T> = SynchronizedLazyImpl( initializer )
  6. 13.

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

    T) : Lazy<T> = SynchronizedLazyImpl( initializer )
  7. 14.

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

    T) : Lazy<T> = SynchronizedLazyImpl( initializer )
  8. 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) }
  9. 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) }
  10. 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)
  11. 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)
  12. 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) }
  13. 22.
  14. 27.
  15. 28.
  16. 29.
  17. 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 ) { ??? }
  18. 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”) }
  19. 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”) } }
  20. 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”) } }
  21. 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> …
  22. 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”) } }
  23. 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”) } }
  24. 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”) } }
  25. 43.

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

    censored(message: String = ““) = CensorProperty(message)
  26. 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.
  27. 48.

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

    LiveGameViewModel.Provider by inject() val viewModel: LiveGameViewModel by unsafeLazy { factory(currentClip) }
  28. 49.
  29. 50.
  30. 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()) }
  31. 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()) }
  32. 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()) }
  33. 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 }
  34. 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 }
  35. 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 }
  36. 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 }
  37. 70.
  38. 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 -> … }
  39. 75.

    @PreusslerBerlin Observable? •We didn‘t use the Kotlin observable: •Dont care

    about previous value or kproperty •Would not work with the chaining
  40. 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) { }
  41. 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) { }
  42. 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) { }
  43. 87.

    @PreusslerBerlin We love databinding val label = ObservableField("") @Bindable var

    label: CharSequence = "" set(value) { field = value notifyPropertyChanged(BR.label) } val label = ”some text"
  44. 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) }
  45. 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)
  46. 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)
  47. 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() } }
  48. 96.
  49. 97.