More fun with Kotlin delegates
Asked Answered
T

3

5

If you know Google's experimental Android Architecture Components, you probably know MutableLiveData. Trying to make it a bit more fun to use I came with:

class KotlinLiveData<T>(val default: T) {
    val data = MutableLiveData<T>()

    operator fun getValue(thisRef: Any?, property: KProperty<*>):T {
        return data.value ?: default
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value:T) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            data.value = value
        } else {
            data.postValue(value)
        }
    }
}

And then I can:

var name : String by KotlinLiveData("not given")
name = "Chrzęszczybrzęczykiewicz"

But alas - that makes data which is needed i.e. to register Observer inaccessible:

name.data.observe(this, nameObserver) // won't work :(

Any idea if I can get it somehow?

Tepper answered 30/6, 2017 at 11:3 Comment(1)
Consider posting your solution as an answer not as an update to your question. This will help future readers and avoid confusion. Thank you.Tile
H
3

You can access the delegate object of the property and get the MutableLiveData<T> from it:

inline fun <reified R> KProperty<*>.delegateAs<R>(): R? {
    isAccessible = true
    return getDelegate() as? R
}

Then the usage is:

::name.delegateAs<KotlinLiveData<String>>?.data?.observe(this, nameObserver)

To reference a member property, use this::name or someInstance::name.

This solution requires you to add the Kotlin reflection API, kotlin-reflect, as a dependency to your project. Also, due to the type erasure, the .delegateAs<KotlinLiveData<String>> call is not type-safe: it can only check that the delegate is KotlinLiveData<*> but not that its type argument is String.

Hereford answered 30/6, 2017 at 11:30 Comment(0)
T
3

Thanks to hotkey's solution, here's some better code:

class KotlinLiveData<T>(val default: T, val liveData : MutableLiveData<T>? = null) {
    val data = liveData ?: MutableLiveData<T>()

    operator fun getValue(thisRef: Any?, property: KProperty<*>):T {
        return data.value ?: default
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value:T) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            data.value = value
        } else {
            data.postValue(value)
        }
    }
}

inline fun <reified R> KMutableProperty0<*>.getLiveData(): MutableLiveData<R> {
    isAccessible = true
    return (getDelegate() as KotlinLiveData<R>).data
}

inline fun <reified R> KMutableProperty0<*>.observe(owner: LifecycleOwner, obs : Observer<R>) {
    isAccessible = true
    (getDelegate() as KotlinLiveData<R>).data.observe(owner,obs)
}

Now I can:

someViewModel::name.observe(myActivity, Observer<String>{...})

with

someViewModel.name = "Kowalski, Leon"

working as expected

This class enables using LiveData with Android Data Binding out of the box.

Tepper answered 30/6, 2017 at 13:1 Comment(0)
H
2

the simplest way you can achieve is make the delegator to a field, for example:

@JvmField val dataOfName =  KotlinLiveData("not given")
var name : String by dataOfName

then you can using live data in the class, for example:

dataOfName.data.observe(this, nameObserver)
name = "Chrzęszczybrzęczykiewicz"

OR you can write some syntax suglar, for example:

var name : String by live("not given").observe(this, nameObserver)

Note you can make nameObserver lazily too, for example:

val observers by lazy{mutableListOf<Observer>()}
var name : String by live("not given").observe(this){data->
    observers.forEach{it.dataChanged(data)}
}

then you can do something like as below:

observers+= nameObserver;

name = "Chrzęszczybrzęczykiewicz"

observers-= nameObserver;
Hypoacidity answered 30/6, 2017 at 11:17 Comment(2)
First suggestion won't allow name="something" as it expects KotlinLiveData<String>. Second suggestion would of course work, but it is completely impractical, since I need to register observers for that value in many different places in code...Tepper
@Tepper why the first approach can't? you can lazy the nameObserver in the second approach too. the first approach the name property type is a String.Hypoacidity

© 2022 - 2024 — McMap. All rights reserved.