I have many properties that follow this pattern, basically the only things that change from the template below are:
the initialized value
the property name
code
var foo: Double = 0.0
get() {
update()
return field
}
var foo2: Double = 1.23
get() {
update()
return field
}
question
is there any way that I can use delegation to to simplify (reduce the verbosity of) the code?
Sure
private fun <T> publishingDelegate(value: T): ReadWriteProperty<Any?, T> = object: ReadWriteProperty<Any?, T> {
private var initValue = value
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
update()
return initValue
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
update()
initValue = value
}
}
var foo: Double by publishingDelegate(0.0)
var foo2: Double by publishingDelegate(1.23)
Related
I was told that MutableState just like MutableLiveData in Kotlin, and MutableState fit Compose, MutableLiveDataenter code here fit XML layout.
In Code A, I need to assign data to bb.value, but why do I assign directly to aa ?
Code A
private var aa by mutableStateOf(-1)
private var bb= MutableLiveData<Int>(-1)
fun onEditDone() {
aa = 2
bb.value = 2
}
It's because of Kotlin's delegation feature where you delegate values using by keyword.
Simple implementation for remember and mutableState, to display how it works when you build something similar to that, is as
// Delegation Functions for setting and getting value
operator fun <T> State<T>.getValue(thisObj: Any?, property: KProperty<*>): T = value
operator fun <T> MutableState<T>.setValue(thisObj: Any?, property: KProperty<*>, value: T) {
this.value = value
}
/*
* State
*/
interface State<out T> {
val value: T
}
interface MutableState<T> : State<T> {
override var value: T
}
class MutableStateImpl<T>(value: T) : MutableState<T> {
override var value: T = value
}
fun <T> mutableStateOf(value: T): MutableState<T> = MutableStateImpl(value)
/*
* Remember
*/
inline fun <T> remember(calculation: () -> T): T {
return calculation()
}
And you can use it as
fun main() {
val isSelected: MutableState<Boolean> = remember { mutableStateOf(true) }
isSelected.value = false
var selected by remember { mutableStateOf(false) }
selected = false
}
I've come accross a case where I want to "chain" mutliple delegates (piping the output of one into the other).
This seems to be possible:
private val errorLogList by listSO(listOf<StateObject<Luxeption>>(), SODest.NONE, publicSOAccessRights())
val errorLog: StateObject<List<StateObject<Luxeption>>> by addToSet(errorLogList)
However, this does not look too well :). I'd like to do it in one line like this:
val errorLog: StateObject<List<StateObject<Luxeption>>> by addToSet(
listSO(listOf<StateObject<Luxeption>>(), SODest.NONE, publicSOAccessRights())
)
My question: Is this type of creating properties through delegates possible in Kotlin?
Here are both implementations of my delegates:
addToSet:
open class ChildSOReturner {
val set: Set<StateObject<*>> = setOf()
inline fun <reified T> addToSet(so: T) = object: ReadOnlyProperty<Any?, T> {
override operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
if (thisRef is T) {
set.plus(so)
return so
} else throw IllegalArgumentException()
}
}
}
listSo:
fun <O> listSO(
initialState: List<StateObject<O>>,
soDest: SODest,
soAccessRights: SOAccessRights
) = object : ReadOnlyProperty<Any?, StateObject<List<StateObject<O>>>> {
override operator fun getValue(thisRef: Any?, property: KProperty<*>): StateObject<List<StateObject<O>>> {
val meta = SOMeta(SOId(property.name), soDest, soAccessRights)
return StateObjectList(initialState, meta)
}
}
It turned out to be quite tricky, but possible (unless I am missing something, and it isn't tested but the idea should work):
fun <T, U, V> composeProperties(prop: ReadOnlyProperty<T, U>, f: (U) -> ReadOnlyProperty<T, V>) : ReadOnlyProperty<T, V> {
var props = mutableMapOf<Pair<T, KProperty<*>>, ReadOnlyProperty<T, V>>()
return object : ReadOnlyProperty<T, V> {
override operator fun getValue(thisRef: T, property: KProperty<*>): V {
val prop1 = props.getOrPut(Pair(thisRef, property)) {
f(prop.getValue(thisRef, property))
}
return prop1.getValue(thisRef, property)
}
}
}
And then to use
val errorLog: ... by composeProperties(listSO(...)) { addToSet(it) }
The following code is from a sample project about Kotlin, I can use Code 1 to get a value of a shared preferences, but I can set a value of a shared preferences?
I can't find those code in the sample project, could you tell me how I can do? Thanks!
Code 1
class SettingsActivity : AppCompatActivity() {
companion object {
val ZIP_CODE = "zipCode"
val DEFAULT_ZIP = 94043L
}
var zipCode: Long by DelegatesExt.preference(this, ZIP_CODE, DEFAULT_ZIP)
}
Code 2
object DelegatesExt {
fun <T> notNullSingleValue() = NotNullSingleValueVar<T>()
fun <T> preference(context: Context, name: String, default: T) = Preference(context, name, default)
}
class NotNullSingleValueVar<T> {
private var value: T? = null
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value ?: throw IllegalStateException("${property.name} not initialized")
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = if (this.value == null) value
else throw IllegalStateException("${property.name} already initialized")
}
}
class Preference<T>(val context: Context, val name: String, val default: T) {
val prefs: SharedPreferences by lazy { context.getSharedPreferences("default", Context.MODE_PRIVATE) }
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return findPreference(name, default)
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
putPreference(name, value)
}
#Suppress("UNCHECKED_CAST")
private fun findPreference(name: String, default: T): T = with(prefs) {
val res: Any = when (default) {
is Long -> getLong(name, default)
is String -> getString(name, default)
is Int -> getInt(name, default)
is Boolean -> getBoolean(name, default)
is Float -> getFloat(name, default)
else -> throw IllegalArgumentException("This type can be saved into Preferences")
}
res as T
}
private fun putPreference(name: String, value: T) = with(prefs.edit()) {
when (value) {
is Long -> putLong(name, value)
is String -> putString(name, value)
is Int -> putInt(name, value)
is Boolean -> putBoolean(name, value)
is Float -> putFloat(name, value)
else -> throw IllegalArgumentException("This type can't be saved into Preferences")
}.apply()
}
}
And More
If the function putPreference is public, I can set value of a shared preferences using the code below, but it's ugly
class SettingsActivity : AppCompatActivity() {
companion object {
val ZIP_CODE = "zipCode"
val DEFAULT_ZIP = 94043L
}
DelegatesExt.Preference(this, ZIP_CODE, DEFAULT_ZIP).putPreference( ZIP_CODE,"99999L");
}
That's what operator fun setValue is for: you just write
activity.zipCode = 1L
(where activity is a SettingsActivity) or
zipCode = 1L
(inside SettingsActivity or a class extending it) and it'll call setValue(activity, activity::zipCode, 1L) which calls putPreference("zipCode", 1L). See https://kotlinlang.org/docs/reference/delegated-properties.html for more.
In Kotlin, is there any shorter syntax for this code:
if(swipeView == null){
swipeView = view.find<MeasureTypePieChart>(R.id.swipeableView)
}
First i tried this:
swipeView ?: view.find<MeasureTypePieChart>(R.id.swipeableView)
but then i realised that wasn't an assignment, so that code does nothing. Then i tried:
swipeView = swipeView ?: view.find<MeasureTypePieChart>(R.id.swipeableView)
Which works, but it a bit verbose. I would expect something like this:
swipeView ?= view.find<MeasureTypePieChart>
But unfortunately that doesn't work. Is there any way of accomplish this with a short syntax?
I know i can do this:
variable?.let { it = something } which works.
Shorter syntax would be to avoid swipeView from ever being null.
Local variable
If swipeView is a local variable then you can declare it non-null when initially assigning it:
val swipeView = ... ?: view.find<MeasureTypePieChart>(R.id.swipeableView)
Function argument
If swipeView is a function argument then you can use a default argument to ensure it is never null:
fun something(swipeView: View = view.find<MeasureTypePieChart>(R.id.swipeableView))
Class property
Read-only
If swipeView is a read-only class property (i.e. val) then you can use Kotlin's built-in Lazy:
val swipeView by lazy { view.find<MeasureTypePieChart>(R.id.swipeableView) }
Mutable
If swipeView is a mutable class property (i.e. var) then you can define your own delegate similar to Lazy but mutable. e.g. The following is based on kotlin/Lazy.kt:
interface MutableLazy<T> : Lazy<T> {
override var value: T
}
fun <T> mutableLazy(initializer: () -> T): MutableLazy<T> = SynchronizedMutableLazyImpl(initializer)
fun <T> mutableLazy(lock: Any?, initializer: () -> T): MutableLazy<T> = SynchronizedMutableLazyImpl(initializer, lock)
operator fun <T> MutableLazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
operator fun <T> MutableLazy<T>.setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
}
private object UNINITIALIZED_VALUE
private class SynchronizedMutableLazyImpl<T>(initializer: () -> T, lock: Any? = null) : MutableLazy<T>, Serializable {
private var initializer: (() -> T)? = initializer
#Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this
override var value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
#Suppress("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
#Suppress("UNCHECKED_CAST") (_v2 as T)
} else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
set(value) {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
_value = value
} else synchronized(lock) {
_value = value
initializer = null
}
}
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
override fun toString(): String = if (isInitialized()) value.toString() else "MutableLazy value not initialized yet."
}
Usage:
var swipeView by mutableLazy { view.find<MeasureTypePieChart>(R.id.swipeableView) }
The initializer will only be called if swipeView is read and is not initialized yet (from a previous read or write).
I'm trying to get my head around property delegates, and I have an interesting use case. Is it possible to have something like this:
class MyClass {
val properties = mutableMapOf<String, Any>()
val fontSize: Any by MapDelegate(properties, "font-size")
}
That would allow me to store fontSize using the map as a delegate, but with a custom key (i.e. "font-size").
The specific use case if for storing things like CSS property tags that can be accessed through variables (fontSize) for use in code, but can be rendered properly when iterating through the map (font-size: 18px;).
The documentation on the delegated properties is a good source of information on the topic. It probably is a bit longer read than the following examples:
fun <T, TValue> T.map(properties: MutableMap<String, TValue>, key: String): ReadOnlyProperty<T, TValue> {
return object : ReadOnlyProperty<T, TValue> {
override fun getValue(thisRef: T, property: KProperty<*>) = properties[key]!!
}
}
class MyClass {
val properties = mutableMapOf<String, Any>()
val fontSize: Any by map(properties, "font-size")
}
You can ease up things a little bit and avoid typing the CSS property name by converting Kotlin property names to CSS attributes equivalents like so:
fun <T, TValue> map(properties: Map<String, TValue>, naming:(String)->String): ReadOnlyProperty<T, TValue?> {
return object : ReadOnlyProperty<T, TValue?> {
override fun getValue(thisRef: T, property: KProperty<*>) = properties[naming(property.name)]
}
}
object CamelToHyphen : (String)->String {
override fun invoke(camelCase: String): String {
return CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, camelCase)
}
}
fun <T, TValue> T.cssProperties(properties: Map<String,TValue>) = map(properties, CamelToHyphen)
class MyClass {
val properties = mutableMapOf<String, Any>()
val fontSize: Any? by cssProperties(properties)
}
The above sample uses Guava's CaseFormat.
If you'd like to have mutable property your delegate will have to implement setter method:
fun <T, TValue> map(properties: MutableMap<String, TValue?>, naming: (String) -> String): ReadWriteProperty<T, TValue?> {
return object : ReadWriteProperty<T, TValue?> {
override fun setValue(thisRef: T, property: KProperty<*>, value: TValue?) {
properties[naming(property.name)] = value
}
override fun getValue(thisRef: T, property: KProperty<*>) = properties[naming(property.name)]
}
}