I put some utility in Tool.kt, Both Method A and Method B can work well.
I think Method B will keep in memory when I start an app even if I never invoke fun <T> preference(context: Context, name: String, default: T)
I think Method A only allocate memory when I invoke DelegatesExt.preference(this,"ZipCode",100L)
So I think Method A is the better than Method B, right?
Method A
object DelegatesExt {
fun <T> preference(context: Context, name: String, default: T) = Preference(context, name, default)
}
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()
}
}
Method B
fun <T> preference(context: Context, name: String, default: T) = Preference(context, name, default)
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()
}
}
Method A will allocate object DelegatesExt during static class initialization - as soon as you reference DelegatesExt in your code, because object in Kotlin is singleton with lazy initialization.
Then, when you'll call DelegatesExt.preference(...), it will allocate your Preference<T> object. By the way, it will allocate a new instance on each call, which is not a good idea.
Then, when you'll call either getValue or setValue, SharedPreferences will be allocated (once only per Preference<T> instance).
Method B doesn't allocate a redundant object DelegatesExt, and Preference<T> will be allocated on each method call as well.
This will be compiled to effectively the same code as a class with a static method in Java.
But Preference<T> won't be allocated before a preference method call (in both cases).
Long story short, both options are almost the same, except of the object DelegatesExt being allocated or not. But it's worth to stop allocating a new Preference<T> on each preference method call.
I think Method B will keep in memeory when I start a app even if I never invoke fun <T> preference(context: Context, name: String, default: T)
What exactly would it keep in memory?
No, the methods are the same except for use when invoking in Kotlin. But in fact, the method B preference is inside class ToolKt which you can see if you try to call it from Java.
Why define either of the preference functions instead of using Preference constructor directly? Kotlin constructors don't have issues with type inference like Java's do.
Related
I try to write a LiveData class which wraps the original ( nullable ) value into a Result class, clearly indicating whether there is a value ( Result.Success ) or not ( Result.Failure ).
Throughout the entire application I used this class to distinguish whether the process of loading some files was successful or not. So for that reason I decided to use it in my LiveData subclass as well, even though the naming is not as reasonable as it could be. To make life easier there're some fluent helper methods to execute code in the different cases.
sealed class Result<out T: Any> {
data class Success<out T: Any>(val value: T): Result<T>()
object Failure: Result<Nothing>()
fun onSuccess(block: (T) -> Unit): Result<T> {
if(this is Success) {
block(value)
}
return this
}
fun onFailure(block: () -> Unit): Result<T> {
if(this is Failure) {
block()
}
return this
}
fun handle(onSuccess: (T) -> Unit, onFailure: () -> Unit): Result<T> {
if(this is Success) {
onSuccess(value)
} else {
onFailure()
}
return this
}
}
So I need the ability to set a value of type T but get a wrapping Result - object.
The easy way is to use functions like setValue(value: T) to change the value and getValue(): Result to receive the value safely. But I would like to use kotlin's delegated properties ( SafeMutableLiveDataDelegation ) to do so.
abstract class SafeLiveData<T: Any> {
abstract val value: Result<T>
}
class SafeMutableLiveData<T: Any>: SafeLiveData<T>() {
private val mutableValue = MutableLiveData<T>()
override var value by SafeMutableLiveDataDelegation(mutableValue)
operator fun getValue(thisRef: Any?, property: KProperty<*>): SafeMutableLiveData<T> {
return this
}
fun ifAvailable(block: (T) -> Unit): SafeMutableLiveData<T> {
value.onSuccess(block)
return this
}
fun ifNotAvailable(block: () -> Unit): SafeMutableLiveData<T> {
value.onFailure(block)
return this
}
fun handle(ifAvailable: (T) -> Unit, ifNotAvailable: () -> Unit): SafeMutableLiveData<T> {
value.handle(ifAvailable, ifNotAvailable)
return this
}
}
Using only readonly access ( override val value by... ) everything's fine but how to create a delegation class which can do so? Something like that:
private class SafeMutableLiveDataDelegation<T: Any>(private val mutableLiveData: MutableLiveData<T>) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Result<T> {
return if(mutableLiveData.value != null)
Result.Success(mutableLiveData.value!!)
else
Result.Failure
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
mutableLiveData.value = value
}
}
Well, you can't. The requirements include:
getValue() must return the same type as the property (or its subtype).
value [argument of setValue] must be of the same type as the property (or its supertype).
and there's no type P you could use for property such that both: Result<T> is P or a subtype of P; and T is P or a supertype of P.
But you can just add a method taking T instead of making it a setter:
// inside SafeMutableLiveData
fun set(value: T) {
mutableLiveData.value = Result.Success(value)
}
I am working on an Android app in which I have to make changes to my HomeScreen by observing LiveData of SharedPreferences which lives in Settings screen. I am following MVVM architecture for it.
I have already checked this LiveData with shared preferences, but it has a lot of boilerplate in the form of creating different classes for different types of SharedPreferences data. I am looking for something more generic. That is why I have created a LiveSharedPreference class and getter for SharedPreferences with Kotlin extension functions and reified types. Here is the custom LiveData class and SharedPreferences getter.
/**************** LiveSharedPreferences.kt ****************/
class LiveSharedPreferences<T : Any>(
private val sharedPreferences: SharedPreferences, private val preferenceKey: String,
private val defValue: T, private val clas: Class<T>
) : LiveData<T>() {
private val preferenceChangeListener =
SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
if (key == preferenceKey) {
// error in this line
value = sharedPreferences.get(key, defValue)
}
}
override fun onActive() {
super.onActive()
// error in this line
value = sharedPreferences.get(preferenceKey, defValue)
sharedPreferences.registerOnSharedPreferenceChangeListener(preferenceChangeListener)
}
override fun onInactive() {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener)
super.onInactive()
}
}
/****** Custom getter with reified type T defined in Extensions.kt ******/
inline fun <reified T: Any> SharedPreferences.get(prefKey: String?, defaultValue: T? = null): T? {
return when (T::class) {
String::class -> getString(prefKey, defaultValue as? String) as T?
Int::class -> getInt(prefKey, defaultValue as? Int ?: -1) as T?
Boolean::class -> getBoolean(prefKey, defaultValue as? Boolean ?: false) as T?
Float::class -> getFloat(prefKey, defaultValue as? Float ?: -1f) as T?
Long::class -> getLong(prefKey, defaultValue as? Long ?: -1) as T?
else -> throw UnsupportedOperationException("Invalid operation")
}
}
But using this get function gives me an error:
Cannot use 'T' as reified type parameter. Use a class instead
I know it has something to do with the way I am dealing with type T. Is there a way I can deal with this problem without much boilerplate?
kotlin 1.2.51
I have the following shared preferences that uses a generic extension function.
class SharedUserPreferencesImp(private val context: Context,
private val sharedPreferenceName: String): SharedUserPreferences {
private val sharedPreferences: SharedPreferences by lazy {
context.getSharedPreferences(sharedPreferenceName, Context.MODE_PRIVATE)
}
override fun <T : Any> T.getValue(key: String): T {
with(sharedPreferences) {
val result: Any = when (this#getValue) {
is String -> getString(key, this#getValue)
is Boolean -> getBoolean(key, this#getValue)
is Int -> getInt(key, this#getValue)
is Long -> getLong(key, this#getValue)
is Float -> getFloat(key, this#getValue)
else -> {
throw UnsupportedOperationException("Cannot find preference casting error")
}
}
#Suppress("unchecked_cast")
return result as T
}
}
}
I am trying to write a unit test for this method. As you can see in my test method the testName.getValue("key") the getValue is not recognized.
class SharedUserPreferencesImpTest {
private lateinit var sharedUserPreferences: SharedUserPreferences
private val context: Context = mock()
#Before
fun setUp() {
sharedUserPreferences = SharedUserPreferencesImp(context, "sharedPreferenceName")
assertThat(sharedUserPreferences).isNotNull
}
#Test
fun `should get a string value from shared preferences`() {
val testName = "this is a test"
testName.getValue("key")
}
}
What is the best way to test a extension function that has a generic type?
Many thanks for any suggestions,
There is a conflict between T.getValue(key: String) being a extension function and SharedUserPreferencesImp member function.
You can make T.getValue(key: String) high-level function and this solves a problem. Here is example code:
fun <T : Any> T.getValue(key: String, sharedPreferences: SharedUserPreferencesImp): T {
with(sharedPreferences.sharedPreferences) {
val result: Any = when (this#getValue) {
is String -> getString(key, this#getValue)
is Boolean -> getBoolean(key, this#getValue)
is Int -> getInt(key, this#getValue)
is Long -> getLong(key, this#getValue)
is Float -> getFloat(key, this#getValue)
else -> {
throw UnsupportedOperationException("Cannot find preference casting error")
}
}
#Suppress("unchecked_cast")
return result as T
}
}
class SharedUserPreferencesImp(private val context: Context,
private val sharedPreferenceName: String): SharedUserPreferences {
val sharedPreferences: SharedPreferences by lazy {
context.getSharedPreferences(sharedPreferenceName, Context.MODE_PRIVATE)
}
}
You can also take a look at this two great libraries:
https://github.com/chibatching/Kotpref
https://github.com/MarcinMoskala/PreferenceHolder
Android Studio 3.4
Kotlin 1.3.10
I have the following method that calls findPreferences to return the correct value that is stored in shared preferences. However, as I am using reified the findPreferences give me an error: Cannot use type T as a reified parameter.
Is there anyway I can get this to work?
fun <T: Any> getValue(key: String, defaultValue: T?): T {
return findPreferences(key, defaultValue)
}
This is the method that will return the value based on the key
#Suppress("unchecked_cast")
inline fun <reified T: Any> findPreferences(key: String, defaultValue: T?): T {
with(sharedPreferences) {
val result: Any = when(defaultValue) {
is Boolean -> getBoolean(key, default)
is Int -> getInt(key, defaultValue)
is Long -> getLong(key, defaultValue)
is Float -> getFloat(key, defaultValue)
is String -> getString(key, defaultValue)
else -> {
throw UnsupportedOperationException("Cannot find preference casting error")
}
}
return result as T
}
}
Remove reified or change getValue to inline fun <reified T: Any> getValue....
With reified, we pass a type to this function(https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters). reified requires type to be known at compile time and obviously there is no enough type information in the getValue.
Kotlin has delegated properties which is a very nice feature. But I am figuring out how to get and set the values. Let's say I want to get value of the property which is delegated. In a get() method how i can access the value?
Here's an example of how I have implemented:
class Example() {
var p: String by DelegateExample()
}
class DelegateExample {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "${property.name} "
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("${value.trim()} '${property.name.toUpperCase()} '")
}
}
fun delegate(): String {
val e = Example()
e.p = "NEW"
return e.p
}
The main question I am unable to understand is, How can I set the value to the actual property on which the delegation class is assigned. When I assign "NEW" to property p, how can I store that value to the variable p or read that new value passed on to p with get? Am I missing something basic here? Any help will be much appreciated. Thanks in advance.
Just create property in delegate which will hold the value
class DelegateExample {
private var value: String? = null
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return value ?: throw IllegalStateException("Initalize me!")
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
this.value = value
}
}
To clarify - delegates aren't values holder, they are handlers of get/set operations. You can take a look how it works under the hood if you decompile your Example class (Tools -> Kotlin -> Show Kotlin bytecode -> Decompile).
public final class Example {
// $FF: synthetic field
static final KProperty[] $$delegatedProperties = ...
#NotNull
private final DelegateExample p$delegate = new DelegateExample();
#NotNull
public final String getP() {
return (String)this.p$delegate.getValue(this, $$delegatedProperties[0]);
}
public final void setP(#NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.p$delegate.setValue(this, $$delegatedProperties[0], var1);
}
}
No magic here, just creating instance of the DelegateExample and its get/set method invoking