This article shows an example of creating a delegate for SharedPreferences, which reduces the boilerplate and makes using SharedPrefernces more convenient. Those who want to see the result, can go to a ready-made solution , the rest welcome under cat.
One of the urgent tasks of development for android is to save any data between sessions. The main ways to do this: store on the server or in files on the executable device. One of the very first ways that any novice Android developer can get to know this is to store it in a file using the ready-made SharedPreferences tool.
Let's say that we need to record the username and then display it somewhere in the application.
class UserStore(private val preferences: SharedPreferences) { fun getUserName(): String? { return preferences.getString(USER_NAME, "") } fun saveUserName(userName: String) { preferences.edit().putString(USER_NAME, userName).apply() } companion object { private const val USER_NAME = "user_name" } }
In a nutshell, this is a class that encapsulates setting and getting a property. Who wants to know more official documentation .
To make a class a delegate, you must implement the ReadOnlyProperty interface for val and ReadWriteProperty for var. We pass SharedPreferences, the key by which the property and the default value will be stored through the constructor. In setValue set the value to getValue get the value.
class StringPreferencesDelegate( private val preferences: SharedPreferences, private val name: String, private val defValue: String ) : ReadWriteProperty<Any?, String?> { override fun setValue(thisRef: Any?, property: KProperty<*>, value: String?) { preferences.edit().putString(name, value).apply() } override fun getValue(thisRef: Any?, property: KProperty<*>): String? { return preferences.getString(name, defValue) } }
class UserStore(private val preferences: SharedPreferences) { var userName: String by StringPreferencesDelegate(preferences, USER_NAME, "") companion object { private const val USER_NAME = "user_name" } }
Assignment to a delegate property is done with the by keyword. Now, every time this property is requested or set, the getValue and setValue methods of the created delegate will be launched.
The same can now be done with other fields, for example, if you want to save the user's phone in the same way.
var userPhone: String by StringPreferencesDelegate(preferences, USER_PHONE, "")
In order not to make a separate delegate for each data type, we use generalizations of the generic official documentation .
Usually the first unconscious acquaintance with generic occurs when you instantiate the List class. For it, the specific data type with which it works is determined.
val names :List<String> = listOf("Jon","Bob","Max")
To specify a generalized data type for a class after its name, you must specify the name of this variable in angle brackets.
PreferencesDelegate<TValue>(...)
Now you need to specify that the set value and the default value are of type TValue.
class PreferencesDelegate<TValue>( val preferences: SharedPreferences, private val name: String, private val defValue: TValue ) : ReadWriteProperty<Any?, TValue> { override fun getValue(thisRef: Any?, property: KProperty<*>): TValue { ... } override fun setValue(thisRef: Any?, property: KProperty<*>, value: TValue) { ... } }
Accordingly, now creating an instance of the class looks like this:
var userName: String? by StringPreferencesDelegate<String?>(...)
It remains to do the mapping of obtaining and setting properties, we determine the data type by delaultValue, at the same time it gives a smart cast of this value to a specific data type, if something went wrong and a property of a non-TValue type returns defValue.
override fun getValue(thisRef: Any?, property: KProperty<*>): TValue { with(preferences) { return when (defValue) { is Boolean -> (preferences.getBoolean(name, defValue) as? TValue) ?: defValue ... } } } override fun setValue(thisRef: Any?, property: KProperty<*>, value: TValue) { with(preferences.edit()) { when (value) { is Boolean -> putBoolean(name, value) ... } apply() } }
With other data types it is similar.
The question remains what to do with the else branch, since the TValue type can be absolutely anything.
It will be a good form to make your custom mistake. If an exception occurs, then it will be as clear as possible what happened.
class NotFoundRealizationException(value: Any?) : Exception("not found realization for ${value?.javaClass}") ... else -> throw NotFoundRealizationException(value) ...
In total, we get a delegate ready for use:
@Suppress("UNCHECKED_CAST") class PreferencesDelegate<TValue>( val preferences: SharedPreferences, private val name: String, private val defValue: TValue ) : ReadWriteProperty<Any?, TValue> { override fun getValue(thisRef: Any?, property: KProperty<*>): TValue { with(preferences) { return when (defValue) { is Boolean -> (getBoolean(name, defValue) as? TValue) ?: defValue is Int -> (getInt(name, defValue) as TValue) ?: defValue is Float -> (getFloat(name, defValue) as TValue) ?: defValue is Long -> (getLong(name, defValue) as TValue) ?: defValue is String -> (getString(name, defValue) as TValue) ?: defValue else -> throw NotFoundRealizationException(defValue) } } } override fun setValue(thisRef: Any?, property: KProperty<*>, value: TValue) { with(preferences.edit()) { when (value) { is Boolean -> putBoolean(name, value) is Int -> putInt(name, value) is Float -> putFloat(name, value) is Long -> putLong(name, value) is String -> putString(name, value) else -> throw NotFoundRealizationException(value) } apply() } } class NotFoundRealizationException(defValue: Any?) : Exception("not found realization for $defValue") }
Application example
class UserStore(private val preferences: SharedPreferences) { var userName: String by PreferencesDelegate(preferences, USER_NAME, "") var userPhone: String by PreferencesDelegate(preferences, USER_PHONE, "") var isShowLicence: Boolean by PreferencesDelegate(preferences, USER_LICENCE, false) companion object { private const val USER_NAME = "user_name" private const val USER_PHONE = "user_phone" private const val USER_LICENCE = "user_licence" } }
Source: https://habr.com/ru/post/461161/
All Articles