Skip to content

Instantly share code, notes, and snippets.

@yongjhih
Last active November 22, 2023 15:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yongjhih/f63d7f49087bbf6df546e7bb196591c5 to your computer and use it in GitHub Desktop.
Save yongjhih/f63d7f49087bbf6df546e7bb196591c5 to your computer and use it in GitHub Desktop.
/**
* 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