Instantly share code, notes, and snippets.
Last active
November 22, 2023 15:07
-
Save yongjhih/f63d7f49087bbf6df546e7bb196591c5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Interface representing a read-only property with a value | |
* | |
* Usage: | |
* | |
* ``` | |
* val doubleValue by propertyOf(1).map { it * 2 }.map { it * 2 } | |
* | |
* doubleValue // 4 | |
* ``` | |
* | |
* ``` | |
* fun <T> propertyOf(value: T) = object : Property<T> { | |
* override val value: T = value | |
* } | |
* | |
* fun <T> Property<T>.map(onMap: (T) -> T): Property<T> = object : Property<T> { | |
* override val value: T | |
* get() = onMap(this@map.value) | |
* } | |
* ``` | |
*/ | |
interface Property<T> { | |
val field: T | |
} | |
/** | |
* Interface representing a mutable property. | |
*/ | |
interface MutableProperty<T> : Property<T> { | |
override var field: T | |
operator fun component1(): T = field | |
operator fun component2(): (T) -> Unit = { field } | |
} | |
/** | |
* Allows the value of a given property instance to be retrieved as if it was a value. | |
* | |
* ref. ReadOnlyProperty<Any, T> | |
*/ | |
operator fun <T> Property<T>.getValue(thisObj: Any?, property: KProperty<*>): T = field | |
/** | |
* Allows the value of a given property instance to be set as if it was a value. | |
* | |
* ref. ReadWriteProperty<Any, T> | |
*/ | |
operator fun <T> MutableProperty<T>.setValue(thisObj: Any?, property: KProperty<*>, value: T) { | |
this.field = value | |
} | |
/** | |
* Returns a new instance of MutableProperty with a specified initial value. | |
*/ | |
fun <T> mutablePropertyOf(value: T) = object : MutableProperty<T> { | |
override var field: T = value | |
} | |
/** | |
* Returns a new T type instance of MutableProperty. | |
*/ | |
fun <T> mutablePropertyOf(set: (T) -> Unit = { }, get: () -> T) = object : MutableProperty<T> { | |
override var field: T | |
get() = get() | |
set(it) { set(it) } | |
} | |
/** | |
* Returns a R type MutableProperty after applying mapping functions on a T type MutableProperty. | |
*/ | |
fun <T, R> MutableProperty<T>.map( | |
set: MutableProperty<T>.(R) -> T, | |
get: MutableProperty<T>.(T) -> R, | |
): MutableProperty<R> = object : MutableProperty<R> { | |
override var field: R | |
get() = this@map.get(this@map.field) | |
set(it) { this@map.field = this@map.set(it) } | |
} | |
/** | |
* Returns a T type MutableProperty that applies a function to the value of the original T type MutableProperty. | |
* | |
* Usage: | |
* | |
* ``` | |
* var doubleValue by mutablePropertyOf(0).get { it * 2 } | |
* | |
* doubleValue = 1 // 2 | |
* doubleValue = 2 // 4 | |
* doubleValue = 3 // 6 | |
* ``` | |
*/ | |
fun <T> MutableProperty<T>.get(get: MutableProperty<T>.(T) -> T): MutableProperty<T> = object : MutableProperty<T> { | |
override var field: T | |
get() = this@get.get(this@get.field) | |
set(it) { this@get.field = it } | |
} | |
/** | |
* Returns a new T type MutableProperty that calls the specified functions on get and set. | |
*/ | |
fun <T> MutableProperty<T>.onEach( | |
onGet: (T) -> Unit = {}, | |
onSet: (T) -> Unit, | |
): MutableProperty<T> = object : MutableProperty<T> { | |
override var field: T | |
get() = this@onEach.field.also { onGet(it) } | |
set(v) { this@onEach.field = v.also { onSet(it) } } | |
} | |
/** | |
* Returns a new T type MutableProperty that changes its value if the function onEqual returns false. | |
*/ | |
fun <T> MutableProperty<T>.onChanged( | |
onEqual: (T, T) -> Boolean = { that, it -> that == it }, | |
onInitial: (() -> T)? = null, | |
onAction: (T, T) -> Unit, | |
): MutableProperty<T> = object : MutableProperty<T> { | |
override var field: T = onInitial?.let { it() } ?: this@onChanged.field | |
get() = this@onChanged.field | |
set(it) { | |
if (!onEqual(field, it)) { | |
this@onChanged.field = it | |
onAction(field, it) | |
} | |
field = it | |
} | |
} | |
/** | |
* Returns a T type MutableProperty that returns the result of onDefault function | |
* if the original T? type MutableProperty value is null. | |
* | |
* ``` | |
* fun <T> Value<T?>.or(onDefault: () -> T): Value<T> = object : Value<T> { | |
* override val value: T | |
* get() = this@or.value ?: onDefault() | |
* } | |
* ``` | |
*/ | |
fun <T> MutableProperty<T?>.or(onDefault: () -> T): MutableProperty<T> = object : MutableProperty<T> { | |
override var field: T | |
get() = this@or.field ?: onDefault() | |
set(value) { this@or.field = value } | |
} | |
/** | |
* Serializer interface | |
*/ | |
interface Serializer<T> : OnSerializer<T>, OnDeserializer<T> | |
interface OnSerializer<T> { | |
val onSerialize: (T?) -> String? | |
} | |
interface OnDeserializer<T> { | |
val onDeserialize: (String?) -> T? | |
} | |
/** | |
* Converts a String? type MutableProperty to a MutableProperty<T?> using the provide serializer. | |
*/ | |
fun <T> MutableProperty<String?>.cast( | |
serializer: Serializer<T>, | |
): MutableProperty<T?> = map( | |
set = { serializer.onSerialize(it) }, | |
get = { serializer.onDeserialize(it) }, | |
) | |
/** | |
* Returns a new T type MutableProperty that calls the provided block when its value changes. | |
* | |
* Usage: | |
* | |
* ``` | |
* var name by useState("") { | |
* binding.title = it | |
* } | |
* | |
* name = "Andrew" | |
* ``` | |
*/ | |
fun <T: Any?> useState(value: T, block: (T) -> Unit): MutableProperty<T> = mutablePropertyOf(value).onChanged { _, it -> | |
block(it) | |
} | |
fun <P, R> ReadWriteProperty<P, R?>.or(onDefault: () -> R) = object : ReadWriteProperty<P, R> { | |
override fun getValue(thisRef: P, property: KProperty<*>): R = | |
this@or.getValue(thisRef, property) ?: onDefault() | |
override fun setValue(thisRef: P, property: KProperty<*>, value: R) = | |
this@or.setValue(thisRef, property, value ?: onDefault()) | |
} | |
fun <P, R> ReadOnlyProperty<P, R?>.or(onDefault: () -> R) = | |
ReadOnlyProperty<P, R> { thisRef, property -> this@or.getValue(thisRef, property) ?: onDefault() } | |
fun <T, R> ReadWriteProperty<Any?, T>.map(set: (R) -> T, get: (T) -> R): ReadWriteProperty<Any?, R> = object : ReadWriteProperty<Any?, R> { | |
override fun getValue(thisRef: Any?, property: KProperty<*>): R = | |
get(this@map.getValue(thisRef, property)) | |
override fun setValue(thisRef: Any?, property: KProperty<*>, value: R) = | |
this@map.setValue(thisRef, property, set(value)) | |
} | |
fun <R> ReadWriteProperty<Any?, String?>.cast( | |
serializer: Serializer<R>, | |
): ReadWriteProperty<Any?, R?> = map( | |
set = serializer.onSerialize, | |
get = serializer.onDeserialize, | |
) | |
fun <T> byWeakReference(initialValue: T? = null): MutableProperty<T?> = mutablePropertyOf(WeakReference(initialValue)).map( | |
set = { if (this.field.get() == it) this.field else WeakReference(it) }, | |
get = { it.get() }, | |
) | |
/** | |
* Extension function to get string value from SharedPreferences or null if not found. | |
*/ | |
@SuppressLint("AvoidSharedPreferenceIssue") | |
fun SharedPreferences.getStringOrNull(key: String) = | |
getString(key, null) | |
/** | |
* Extension function to get integer value from SharedPreferences or null if not found. | |
*/ | |
@SuppressLint("AvoidSharedPreferenceIssue") | |
fun SharedPreferences.getIntOrNull(key: String) = | |
if (contains(key)) getInt(key, 0) else null | |
@SuppressLint("AvoidSharedPreferenceIssue") | |
fun SharedPreferences.getLongOrNull(key: String) = | |
if (contains(key)) getLong(key, 0) else null | |
@SuppressLint("AvoidSharedPreferenceIssue") | |
fun SharedPreferences.getFloatOrNull(key: String) = | |
if (contains(key)) getFloat(key, 0f) else null | |
fun SharedPreferences.Editor.put(key: String, value: String?): SharedPreferences.Editor = | |
putString(key, value) | |
fun SharedPreferences.Editor.put(key: String, value: Int?): SharedPreferences.Editor = | |
if (value != null) putInt(key, value) | |
else remove(key) | |
fun SharedPreferences.Editor.put(key: String, value: Long?): SharedPreferences.Editor = | |
if (value != null) putLong(key, value) | |
else remove(key) | |
fun SharedPreferences.Editor.put(key: String, value: Float?): SharedPreferences.Editor = | |
if (value != null) putFloat(key, value) | |
else remove(key) | |
@SuppressLint("AvoidSharedPreferenceIssue") | |
fun SharedPreferences.getBooleanOrNull(key: String) = | |
if (contains(key)) getBoolean(key, false) else null | |
fun SharedPreferences.Editor.put(key: String, value: Boolean?): SharedPreferences.Editor = | |
if (value != null) putBoolean(key, value) | |
else remove(key) | |
/** | |
* Factory function for MutableProperty of type String. | |
* The MutableProperty saves and retrieves its value from SharedPreferences. | |
*/ | |
fun SharedPreferences.byString( | |
scope: CoroutineScope? = null, | |
onKey: () -> String, | |
): MutableProperty<String?> = mutablePropertyOf({ | |
scope?.launch { withContext(Dispatchers.IO) { | |
edit { put(onKey(), it) } | |
} } ?: edit { put(onKey(), it) } | |
}) { getStringOrNull(onKey()) } | |
fun SharedPreferences.byString(): ReadWriteProperty<Any?, String?> = object : ReadWriteProperty<Any?, String?> { | |
override fun getValue(thisRef: Any?, property: KProperty<*>): String? = | |
getStringOrNull(property.name) | |
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String?) = | |
edit { put(property.name, value) } | |
} | |
fun SharedPreferences.byInt( | |
scope: CoroutineScope? = null, | |
onKey: () -> String, | |
): MutableProperty<Int?> = mutablePropertyOf({ | |
scope?.launch { withContext(Dispatchers.IO) { | |
edit { put(onKey(), it) } | |
} } ?: edit { put(onKey(), it) } | |
}) { getIntOrNull(onKey()) } | |
fun SharedPreferences.byInt(): ReadWriteProperty<Any, Int?> = object : ReadWriteProperty<Any, Int?> { | |
override fun getValue(thisRef: Any, property: KProperty<*>): Int? = | |
getIntOrNull(property.name) | |
override fun setValue(thisRef: Any, property: KProperty<*>, value: Int?) = | |
edit { put(property.name, value) } | |
} | |
fun SharedPreferences.byLong( | |
scope: CoroutineScope? = null, | |
onKey: () -> String, | |
): MutableProperty<Long?> = mutablePropertyOf({ | |
scope?.launch { withContext(Dispatchers.IO) { | |
edit { put(onKey(), it) } | |
} } ?: edit { put(onKey(), it) } | |
}) { getLongOrNull(onKey()) } | |
fun SharedPreferences.byLong(): ReadWriteProperty<Any, Long?> = object : ReadWriteProperty<Any, Long?> { | |
override fun getValue(thisRef: Any, property: KProperty<*>): Long? = | |
getLongOrNull(property.name) | |
override fun setValue(thisRef: Any, property: KProperty<*>, value: Long?) = | |
edit { put(property.name, value) } | |
} | |
fun SharedPreferences.byFloat( | |
scope: CoroutineScope? = null, | |
onKey: () -> String, | |
): MutableProperty<Float?> = mutablePropertyOf({ | |
scope?.launch { withContext(Dispatchers.IO) { | |
edit { put(onKey(), it) } | |
} } ?: edit { put(onKey(), it) } | |
}) { getFloatOrNull(onKey()) } | |
fun SharedPreferences.byFloat(): ReadWriteProperty<Any, Float?> = object : ReadWriteProperty<Any, Float?> { | |
override fun getValue(thisRef: Any, property: KProperty<*>): Float? = | |
getFloatOrNull(property.name) | |
override fun setValue(thisRef: Any, property: KProperty<*>, value: Float?) = | |
edit { put(property.name, value) } | |
} | |
fun SharedPreferences.byBoolean( | |
scope: CoroutineScope? = null, | |
onKey: () -> String, | |
): MutableProperty<Boolean?> = mutablePropertyOf({ | |
scope?.launch { withContext(Dispatchers.IO) { | |
edit { put(onKey(), it) } | |
} } ?: edit { put(onKey(), it) } | |
}) { getBooleanOrNull(onKey()) } | |
fun SharedPreferences.byBoolean(): ReadWriteProperty<Any, Boolean?> = object : ReadWriteProperty<Any, Boolean?> { | |
override fun getValue(thisRef: Any, property: KProperty<*>): Boolean? = | |
getBooleanOrNull(property.name) | |
override fun setValue(thisRef: Any, property: KProperty<*>, value: Boolean?) = | |
edit { put(property.name, value) } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment