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.
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 impemented Reassignable Property Delegate
internal object UNINITIALIZED_VALUE
class SynchronizedReassignableImpl<out T>(private val initializer: () -> T,
private val expiredPredicate: (T) -> Boolean,
lock: Any? = null) : Reassignable<T> {
#Volatile
private var _value: Any? = UNINITIALIZED_VALUE
private val lock = lock ?: this
override val value: T
get() {
if (!isExpired()) {
#Suppress("UNCHECKED_CAST") (_value as T)
}
return synchronized(lock) {
val _v2 = _value
#Suppress("UNCHECKED_CAST")
if (_v2 !== UNINITIALIZED_VALUE && !expiredPredicate.invoke(_value as T)) {
_v2 as T
} else {
val typedValue = initializer()
_value = typedValue
typedValue
}
}
}
#Suppress("UNCHECKED_CAST")
override fun isExpired(): Boolean = !isInitialized() || expiredPredicate.invoke(_value as T)
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
override fun toString(): String = if (isInitialized()) value.toString() else "Reassignable value not initialized yet."
operator fun getValue(any: Any, property: KProperty<*>): T = value
operator fun getValue(any: Nothing?, property: KProperty<*>): T = value
}
fun <T> reassignable(initializer: () -> T, expiredPredicate: (T) -> Boolean, lock: Any? = null): SynchronizedReassignableImpl<T> {
return SynchronizedReassignableImpl(initializer, expiredPredicate, lock)
}
interface Reassignable<out T> {
val value: T
fun isInitialized(): Boolean
fun isExpired(): Boolean
}
This code declares the Delegate Property is working like lazy but on each getter's call, the predicate will be invoked to define a state of value (is expired or not). If the value is expired the one will be reassigned.
It's working, for example
class SynchronizedReassignableImplTests {
#Test
fun isReassignable() {
val initializer = { mutableListOf<String>() }
val expiredPredicate = { l: List<String> -> l.size == 2 }
val list by reassignable(initializer, expiredPredicate)
Assertions.assertEquals(0, list.size)
list.add("item ${list.size}")
Assertions.assertEquals(1, list.size)
list.add("item ${list.size}") // list size is 2 on next getter's call it will be reassigned
Assertions.assertEquals(0, list.size)
list.add("item ${list.size}")
Assertions.assertEquals(1, list.size)
}
}
but I'm working with Kotlin for only two days and think my solution not so beautiful.
Can somebody give me the advice to do this? Or maybe Kotlin has a native solution?
I need to define a property isBackgroundWindow by delegate class PreferenceTool, but the following code will cause NullPointerException error.
I know that private val prefs: SharedPreferences by lazy { } in PreferenceTool<T> is lazy, so the object this is not initialized when system invoke PreferenceTool(this, getString(R.string.IsBackgroundName) , false), it will cause null error.
I hope to use the code private lateinit var isBackgroundWindow: Boolean by PreferenceTool(this, getString(R.string.IsBackgroundName) , false), but
'lateinit' modifier is not allowed on delegated properties .
How can I do?
Main
class UIHome : AppCompatActivity() {
//I think the object this is not initialized, it will cause null error.
private var isBackgroundWindow: Boolean by PreferenceTool(this, getString(R.string.IsBackgroundName) , false)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_home)
isBackgroundWindow=false
}
}
Delegate Class
class PreferenceTool<T>(private val context: Context, private val name: String, private val default: T) {
private val prefs: SharedPreferences by lazy {
context.defaultSharedPreferences
}
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = 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
}
#SuppressLint("CommitPrefEdits")
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()
}
}
You can lateinit the variable isBackground like this way .Your UIHome class should be
class UIHome : AppCompatActivity() {
var isBackground: Boolean by Delegates.notNull<Boolean>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_uihome)
isBackground = isBackgroundWindow()
}
fun isBackgroundWindow(): Boolean {
val isBackgroundWindow: Boolean by PreferenceTool(
this, getString(R.string.IsBackgroundName), false
)
return isBackgroundWindow
}
}
This is avoid null pointer exception
isBackground?.let {
// your code
}
The following Code A is from https://github.com/antoniolg/Kotlin-for-Android-Developers/blob/master/app/src/main/java/com/antonioleiva/weatherapp/extensions/DelegatesExtensions.kt
I can use private var zipCode: Long by DelegatesExt.preference(this, ZIP_CODE, DEFAULT_ZIP) to invoke when I use Code A.
I don't understand why the author wrap class Preference(...) with object DelegatesExt
I think Code B is more simple, I can use private val zipCode: Long by Preference(this, ZIP_CODE, DEFAULT_ZIP) to invoke when I use Code B
Why need the class Preference be wrapped with object ?
Code A
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 =
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>(private val context: Context, private val name: String,
private val default: T) {
private val prefs: SharedPreferences by lazy {
context.getSharedPreferences("default", Context.MODE_PRIVATE)
}
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = 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
}
#SuppressLint("CommitPrefEdits")
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()
}
}
Code B
class NotNullSingleValueVar<T> {
private var value: T? = null
operator fun getValue(thisRef: Any?, property: KProperty<*>): T =
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>(private val context: Context, private val name: String,
private val default: T) {
private val prefs: SharedPreferences by lazy {
context.getSharedPreferences("default", Context.MODE_PRIVATE)
}
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = 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
}
#SuppressLint("CommitPrefEdits")
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()
}
}
I can use private var zipCode: Long by DelegatesExt.preference(this, ZIP_CODE, DEFAULT_ZIP) to invoke when I use Code A.
I think Code B is more simple, I can use private val zipCode: Long by Preference(this, ZIP_CODE, DEFAULT_ZIP) to invoke when I use Code B
In the first case you can also import DelegatesExt.* or DelegatesExt.preference instead of DelegatesExt and write by preference.
Why need the class Preference be wrapped with object ?
It doesn't need to be (and I wouldn't do it), that's just the author's preference.
Suppose I only want one or two fields to be included in the generated equals and hashCode implementations (or perhaps exclude one or more fields). For a simple class, e.g.:
data class Person(val id: String, val name: String)
Groovy has this:
#EqualsAndHashCode(includes = 'id')
Lombok has this:
#EqualsAndHashCode(of = "id")
What is the idiomatic way of doing this in Kotlin?
My approach so far
data class Person(val id: String) {
// at least we can guarantee it is present at access time
var name: String by Delegates.notNull()
constructor(id: String, name: String): this(id) {
this.name = name
}
}
Just feels wrong though... I don't really want name to be mutable, and the extra constructor definition is ugly.
I've used this approach.
data class Person(val id: String, val name: String) {
override fun equals(other: Person) = EssentialData(this) == EssentialData(other)
override fun hashCode() = EssentialData(this).hashCode()
override fun toString() = EssentialData(this).toString().replaceFirst("EssentialData", "Person")
}
private data class EssentialData(val id: String) {
constructor(person: Person) : this(id = person.id)
}
This approach may be suitable for property exclusion:
class SkipProperty<T>(val property: T) {
override fun equals(other: Any?) = true
override fun hashCode() = 0
}
SkipProperty.equals simply returns true, which causes the embeded property to be skipped in equals of parent object.
data class Person(
val id: String,
val name: SkipProperty<String>
)
I also don't know "the idomatic way" in Kotlin (1.1) to do this...
I ended up overriding equals and hashCode:
data class Person(val id: String,
val name: String) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as Person
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id.hashCode()
}
}
Isn't there a "better" way?
This builds on #bashor's approach and uses a private primary and a public secondary constructor. Sadly the property to be ignored for equals cannot be a val, but one can hide the setter, so the result is equivalent from an external perspective.
data class ExampleDataClass private constructor(val important: String) {
var notSoImportant: String = ""
private set
constructor(important: String, notSoImportant: String) : this(important) {
this.notSoImportant = notSoImportant
}
}
Here's a somewhat creative approach:
data class IncludedArgs(val args: Array<out Any>)
fun includedArgs(vararg args: Any) = IncludedArgs(args)
abstract class Base {
abstract val included : IncludedArgs
override fun equals(other: Any?) = when {
this identityEquals other -> true
other is Base -> included == other.included
else -> false
}
override fun hashCode() = included.hashCode()
override fun toString() = included.toString()
}
class Foo(val a: String, val b : String) : Base() {
override val included = includedArgs(a)
}
fun main(args : Array<String>) {
val foo1 = Foo("a", "b")
val foo2 = Foo("a", "B")
println(foo1 == foo2) //prints "true"
println(foo1) //prints "IncludedArgs(args=[a])"
}
Reusable solution: to have an easy way to select which fields to include in equals() and hashCode(), I wrote a little helper called "stem" (essential core data, relevant for equality).
Usage is straightforward, and the resulting code very small:
class Person(val id: String, val name: String) {
private val stem = Stem(this, { id })
override fun equals(other: Any?) = stem.eq(other)
override fun hashCode() = stem.hc()
}
It's possible to trade off the backing field stored in the class with extra computation on-the-fly:
private val stem get() = Stem(this, { id })
Since Stem takes any function, you are free to specify how the equality is computed. For more than one field to consider, just add one lambda expression per field (varargs):
private val stem = Stem(this, { id }, { name })
Implementation:
class Stem<T : Any>(
private val thisObj: T,
private vararg val properties: T.() -> Any?
) {
fun eq(other: Any?): Boolean {
if (thisObj === other)
return true
if (thisObj.javaClass != other?.javaClass)
return false
// cast is safe, because this is T and other's class was checked for equality with T
#Suppress("UNCHECKED_CAST")
other as T
return properties.all { thisObj.it() == other.it() }
}
fun hc(): Int {
// Fast implementation without collection copies, based on java.util.Arrays.hashCode()
var result = 1
for (element in properties) {
val value = thisObj.element()
result = 31 * result + (value?.hashCode() ?: 0)
}
return result
}
#Deprecated("Not accessible; use eq()", ReplaceWith("this.eq(other)"), DeprecationLevel.ERROR)
override fun equals(other: Any?): Boolean =
throw UnsupportedOperationException("Stem.equals() not supported; call eq() instead")
#Deprecated("Not accessible; use hc()", ReplaceWith("this.hc(other)"), DeprecationLevel.ERROR)
override fun hashCode(): Int =
throw UnsupportedOperationException("Stem.hashCode() not supported; call hc() instead")
}
In case you're wondering about the last two methods, their presence makes the following erroneous code fail at compile time:
override fun equals(other: Any?) = stem.equals(other)
override fun hashCode() = stem.hashCode()
The exception is merely a fallback if those methods are invoked implicitly or through reflection; can be argued if it's necessary.
Of course, the Stem class could be further extended to include automatic generation of toString() etc.
Simpler, faster, look at there, or into the Kotlin documentation.
https://discuss.kotlinlang.org/t/ignoring-certain-properties-when-generating-equals-hashcode-etc/2715/2
Only fields inside the primary constructor are taken into account to build automatic access methods like equals and so on. Do keep the meaningless ones outside.
Here is another hacky approach if you don't want to touch the data class.
You can reuse the entire equals() from data classes while excluding some fields.
Just copy() the classes with fixed values for excluded fields:
data class Person(val id: String,
val name: String)
fun main() {
val person1 = Person("1", "John")
val person2 = Person("2", "John")
println("Full equals: ${person1 == person2}")
println("equals without id: ${person1.copy(id = "") == person2.copy(id = "")}")
}
Output:
Full equals: false
equals without id: true
Consider the following generic approach for the implementation of equals/hashcode. The code below should have no performance impact because of the use of inlining and kotlin value classes:
#file:Suppress("EXPERIMENTAL_FEATURE_WARNING")
package org.beatkit.common
import kotlin.jvm.JvmInline
#Suppress("NOTHING_TO_INLINE")
#JvmInline
value class HashCode(val value: Int = 0) {
inline fun combineHash(hash: Int): HashCode = HashCode(31 * value + hash)
inline fun combine(obj: Any?): HashCode = combineHash(obj.hashCode())
}
#Suppress("NOTHING_TO_INLINE")
#JvmInline
value class Equals(val value: Boolean = true) {
inline fun combineEquals(equalsImpl: () -> Boolean): Equals = if (!value) this else Equals(equalsImpl())
inline fun <A : Any> combine(lhs: A?, rhs: A?): Equals = combineEquals { lhs == rhs }
}
#Suppress("NOTHING_TO_INLINE")
object Objects {
inline fun hashCode(builder: HashCode.() -> HashCode): Int = builder(HashCode()).value
inline fun hashCode(vararg objects: Any?): Int = hashCode {
var hash = this
objects.forEach {
hash = hash.combine(it)
}
hash
}
inline fun hashCode(vararg hashes: Int): Int = hashCode {
var hash = this
hashes.forEach {
hash = hash.combineHash(it)
}
hash
}
inline fun <T : Any> equals(
lhs: T,
rhs: Any?,
allowSubclasses: Boolean = false,
builder: Equals.(T, T) -> Equals
): Boolean {
if (rhs == null) return false
if (lhs === rhs) return true
if (allowSubclasses) {
if (!lhs::class.isInstance(rhs)) return false
} else {
if (lhs::class != rhs::class) return false
}
#Suppress("unchecked_cast")
return builder(Equals(), lhs, rhs as T).value
}
}
With this in place, you can easily implement/override any equals/hashcode implementation in a uniform way:
data class Foo(val title: String, val bytes: ByteArray, val ignore: Long) {
override fun equals(other: Any?): Boolean {
return Objects.equals(this, other) { lhs, rhs ->
this.combine(lhs.title, rhs.title)
.combineEquals { lhs.bytes contentEquals rhs.bytes }
// ignore the third field for equals
}
}
override fun hashCode(): Int {
return Objects.hashCode(title, bytes) // ignore the third field for hashcode
}
}
You can create an annotation that represents the exclusion of the property as #ExcludeToString or with #ToString(Type.EXCLUDE) parameters by defining enum.
And then using reflection format the value of the getToString().
#Target(AnnotationTarget.FIELD)
#Retention(AnnotationRetention.RUNTIME)
annotation class ExcludeToString
data class Test(
var a: String = "Test A",
#ExcludeToString var b: String = "Test B"
) {
override fun toString(): String {
return ExcludeToStringUtils.getToString(this)
}
}
object ExcludeToStringUtils {
fun getToString(obj: Any): String {
val toString = LinkedList<String>()
getFieldsNotExludeToString(obj).forEach { prop ->
prop.isAccessible = true
toString += "${prop.name}=" + prop.get(obj)?.toString()?.trim()
}
return "${obj.javaClass.simpleName}=[${toString.joinToString(", ")}]"
}
private fun getFieldsNotExludeToString(obj: Any): List<Field> {
val declaredFields = obj::class.java.declaredFields
return declaredFields.filterNot { field ->
isFieldWithExludeToString(field)
}
}
private fun isFieldWithExludeToString(field: Field): Boolean {
field.annotations.forEach {
if (it.annotationClass == ExcludeToString::class) {
return true
}
}
return false
}
}
GL
Gist