Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Kotlin custom get execute method call

Tags:

android

kotlin

To increase the readability of calls to SharedPreferences.Editor I want to use a Kotlin variable that will execute 'getSharedPreferences.edit()' each time I need a new SharedPreferences.Editor. Initially I was going to use something like this:

val editPreferences: SharedPreferences.Editor = Application.getSharedPreferences("preferences", Context.MODE_PRIVATE).edit()

But then I was informed that 'editPreferences' will hold the reference to the same editor when what I really want it to create a new editor each time the 'editPreferences' is called.

If a custom getter was used would a new editor be returned each time? Something like this:

val editPreferences: SharedPreferences.Editor 
    get() = Application.getSharedPreferences("preferences", Context.MODE_PRIVATE).edit()

Still getting up and running with Kotlin and am not sure if the get() method would hold reference to the editor instead of creating a new one.

like image 525
hlupi Avatar asked Apr 01 '16 22:04

hlupi


2 Answers

If you implement a property with a custom getter, it will not store any data. The body of the getter will be executed every time you access the property.

like image 181
yole Avatar answered Sep 21 '22 14:09

yole


You could go one step further and wrap the property with a delegate, making use of the variable name on the meta data.

Usage

class SomeActivity : SomeBaseActivity {

     // Declare property the with key "myImportantNumber" 
     // and default value 10
     var myImportantNumber by preference(10)

     //how to access the property
     fun isMyImportantNumberReallyHight() = myImportantNumber > 100

     //how to edit the property
     fun incrementMyImportantNumber(times:Int){
         myImportantNumber = myImportantNumber + times
     }
}

Delegation

The delegate keeps an instance of some preferences manager and uses the meta data on the property to fetch and save values on the shared preference.

class DelegatedPreference<T>(val default: T, val contextProvider:()-> Context) {

    val manager by lazy { PreferencesManager(contextProvider()) }

    @Suppress("UNCHECKED_CAST")
    operator fun getValue(thisRef: Any?, prop: KProperty<*>): T {
        return manager.get(prop.name, default)
    }

    operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: Any) {
        manager.save(prop.name, value)
    }

    class TypeNotImplementedException(val propName:String) : Exception("Type of ${propName} is not implemented on DelegatedPreference and thus invalid")
}

Some sugar

A little extension method:

fun <T> Activity.preference(default:T):DelegatedPreference<T>{
    return DelegatedPreference(default, {this})
}

Which allow us to change this:

var myImportantNumber by DelegatedPreference(10, {this})

By something more readable:

var myImportantNumber by preference(10)

The actual getting and saving

Here what I called PreferencesManager (sorry I didn't come up with a better name) does the heavy lifting, and calls .edit() each time a property needs to be altered. It would look something like:

public class PreferencesManager(context: Context) {
    private val preferences = getSharedPreferences(context)

    companion object Utils {
        public val APP_PREFERENCES: String = "APP_PREFERENCES"

        fun getSharedPreferences(context: Context): SharedPreferences {
            return context.getSharedPreferences(APP_PREFERENCES, Context.MODE_PRIVATE)
        }
    }


    public fun save(label:String, elem:Any){
        when(elem){
            is Int     -> preferences.edit().putInt(label, elem).apply()
            is String  -> preferences.edit().putString(label, elem).apply()
            is Float   -> preferences.edit().putFloat(label, elem).apply()
            is Boolean -> preferences.edit().putBoolean(label, elem).apply()
            else -> throw DelegatedPreference.TypeNotImplementedException(label)
        }
    }

    @Suppress("UNCHECKED_CAST", "IMPLICIT_CAST_TO_ANY")
    public fun <T> get(label:String, default:T):T = when(default){
        is Int     -> preferences.getInt(label, default)
        is String  -> preferences.getString(label, default)
        is Float   -> preferences.getFloat(label, default)
        is Boolean -> preferences.getBoolean(label, default)
        else -> throw DelegatedPreference.TypeNotImplementedException(label)
    } as T

}

There is a lot of room for improvement here (like parametrizing the preference name instead of hardcoding it, giving an extension point for serializing other types, etc), but the general idea remains.

like image 28
Logain Avatar answered Sep 22 '22 14:09

Logain