Why need the class Preference<T> be wrapped with object? - kotlin

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.

Related

Best way to implement a Reassignable Property Delegate

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?

How to define a delegate property which need to be initialized delayed in Kotlin?

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
}

Kotlin type auto boxing vs primitive

class Remember private constructor() {
private var data: ConcurrentMap<String, Any> = ConcurrentHashMap()
private fun <T> saveValue(key: String, value: T): Remember {
data[key] = value
return this
}
private fun <T> getValue(key: String, clazz: Class<T>): T? {
val value = data[key]
var castedObject: T? = null
//Failed here
if (clazz.isInstance(value)) {
castedObject = clazz.cast(value)
}
return castedObject
}
fun putInt(key: String, value: Int): Remember {
return saveValue(key, value)
}
fun getInt(key: String, fallback: Int): Int {
val value = getValue(key, Int::class.java)
return value ?: fallback
}
}
When I putInt(key, 123), 123 is autoboxed to java.lang.Integer. When I get value from the Map, how do I compare value typed Any with Class<T> in which T is Int:class.java in this case? Currently, clazz.isInstance(value) always fails. It works if this class is written in Java
I think that's not kotlin but Java. Map only accepts Object type. So the primitive type will be autoboxed to put in a Map. So value returns from Map is alway Object.

Property getter simplification via delegation

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)

How to set a value preference in Kotlin?

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.