Share
Embed
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
Transcript
97 slides
-
@PreusslerBerlin Property Delegates public fun <T> lazy( initializer: () ->
T) : Lazy<T> = SynchronizedLazyImpl( initializer ) -
@PreusslerBerlin Property Delegates public fun <T> lazy( initializer: () ->
T) : Lazy<T> = SynchronizedLazyImpl( initializer ) -
@PreusslerBerlin Property Delegates public fun <T> lazy( initializer: () ->
T) : Lazy<T> = SynchronizedLazyImpl( initializer ) -
@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) } -
@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) } -
@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) -
@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) -
@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) } -
@PreusslerBerlin Property Delegates public interface Lazy<out T> { public val
value: T public fun isInitialized(): Boolean } -
@PreusslerBerlin Property Delegates public inline operator fun <T> Lazy<T>.getValue( thisRef:
Any?, property: KProperty<*> ): T = value -
@PreusslerBerlin Property Delegates val something by 123 operator fun Int.getValue(
thisRef: Any?, property: KProperty<*> ): Int = this -
@PreusslerBerlin Property Delegates val something by 123 operator fun Int.getValue(
thisRef: Any?, property: KProperty<*> ): Int = 456 -
@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 ) { ??? } -
@PreusslerBerlin A Property Delegate fun censored(message: String = ““) =
CensorProperty(message) -
@PreusslerBerlin A Property Delegate class CensorProperty(..): ReadWriteProperty<..>{ override fun setValue(..){..}
override fun getValue(..){..} } -
@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”) } -
@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”) } } -
@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”) } } -
@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> … -
@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”) } } -
@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”) } } -
@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”) } } -
@PreusslerBerlin A Property Delegate var message: String by censored() fun
censored(message: String = ““) = CensorProperty(message) -
@PreusslerBerlin Your Property delegates •Extend ReadOnlyProperty, ReadWriteProperty or •Use extension
operator functions: getValue/setValue -
@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. -
@PreusslerBerlin Our delegates class PlayerLiveFragment : Fragment() { val factory:
LiveGameViewModel.Provider by inject() val viewModel: LiveGameViewModel by unsafeLazy { factory(currentClip) } -
@PreusslerBerlin Our delegates fun <T> unsafeLazy(initializer: () -> T) =
lazy(LazyThreadSafetyMode.NONE, initializer) -
@PreusslerBerlin Our delegates fun <T> unsafeLazy(initializer: () -> T) =
lazy(LazyThreadSafetyMode.NONE, initializer) -
@PreusslerBerlin Our delegates val initialised by toggleOnFirstAccess(false) fun init() {
if (not(initialised)) { subscribeToStore() } } -
@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()) } -
@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()) } -
@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()) } -
@PreusslerBerlin Our delegates var searchDisposable by disposable() searchDisposable = useCase.searchFor(query)
override fun onDestroy() { searchDisposable = null } -
@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 } -
@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 } -
@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 } -
@PreusslerBerlin Our delegates var playingAd by observable(false) .distinctUntilChanged() .doOnTrue {
onAdsStarted() } .doOnFalse { onAdsStopped() } -
@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 } -
@PreusslerBerlin Observable? var max: Int by observable(0) { property, oldValue,
newValue ->. observed = true -
@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 -> … } -
@PreusslerBerlin Observable? •We didn‘t use the Kotlin observable: •Dont care
about previous value or kproperty •Would not work with the chaining -
@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) { } -
@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) { } -
@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) { } -
@PreusslerBerlin We love databinding val label = ObservableField("") @Bindable var
label: CharSequence = "" set(value) { field = value notifyPropertyChanged(BR.label) } val label = ”some text" -
@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) } -
@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) -
@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) -
@PreusslerBerlin Our delegates @get:Bindable var logoVisibility by bindable(View.VISIBLE) @get:Bindable("logoVisibility") val
upVisibility: Int get() = logoVisibility.invertVisibility() -
@PreusslerBerlin Our delegates @get:Bindable var selected: Boolean by bindable(selected) .doOnNext
{ callback?.invoke(it) } -
@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() } }