Dieser Artikel zeigt ein Beispiel für das Erstellen eines Delegaten für SharedPreferences, wodurch die Boilerplate reduziert und die Verwendung von SharedPrefernces komfortabler wird. Wer das Ergebnis sehen möchte, kann sich für eine vorgefertigte Lösung entscheiden.
Eine der dringenden Aufgaben der Entwicklung für Android ist das Speichern von Daten zwischen Sitzungen. Die wichtigsten Möglichkeiten hierfür: Speichern auf dem Server oder in Dateien auf dem ausführbaren Gerät. Eine der allerersten Möglichkeiten, die ein unerfahrener Android-Entwickler kennenlernen kann, besteht darin, sie mit dem vorgefertigten SharedPreferences-Tool in einer Datei zu speichern.
Angenommen, wir müssen den Benutzernamen aufzeichnen und ihn dann irgendwo in der Anwendung anzeigen.
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" } }
Was ist ein Delegierter und wie wird er vorbereitet?
Kurz gesagt, dies ist eine Klasse, die das Setzen und Abrufen einer Eigenschaft einschließt. Wer möchte mehr offizielle Dokumentation wissen.
Um eine Klasse zu einem Delegaten zu machen, müssen Sie die ReadOnlyProperty-Schnittstelle für val und ReadWriteProperty für var implementieren. Wir übergeben SharedPreferences, den Schlüssel, mit dem die Eigenschaft und der Standardwert über den Konstruktor gespeichert werden. In setValue setzen Sie den Wert auf getValue, um den Wert zu erhalten.
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) } }
Delegierten anwenden
class UserStore(private val preferences: SharedPreferences) { var userName: String by StringPreferencesDelegate(preferences, USER_NAME, "") companion object { private const val USER_NAME = "user_name" } }
Die Zuweisung zu einer Delegate-Eigenschaft erfolgt über das Schlüsselwort by. Jedes Mal, wenn diese Eigenschaft angefordert oder festgelegt wird, werden die Methoden getValue und setValue des erstellten Delegaten gestartet.
Dasselbe kann jetzt auch mit anderen Feldern gemacht werden, wenn Sie beispielsweise das Telefon des Benutzers auf die gleiche Weise speichern möchten.
var userPhone: String by StringPreferencesDelegate(preferences, USER_PHONE, "")
Generisch
Um nicht für jeden Datentyp einen eigenen Delegaten zu erstellen, verwenden wir Verallgemeinerungen der allgemeinen offiziellen Dokumentation .
Normalerweise erfolgt die erste unbewusste Bekanntschaft mit generic, wenn eine Instanz der List-Klasse erstellt wird. Dafür wird der spezifische Datentyp bestimmt, mit dem es arbeitet.
val names :List<String> = listOf("Jon","Bob","Max")
Um einen verallgemeinerten Datentyp für eine Klasse nach ihrem Namen anzugeben, müssen Sie den Namen dieser Variablen in spitzen Klammern angeben.
PreferencesDelegate<TValue>(...)
Jetzt müssen Sie angeben, dass der eingestellte Wert und der Standardwert vom Typ TValue sind.
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) { ... } }
Dementsprechend sieht das Erstellen einer Instanz der Klasse folgendermaßen aus:
var userName: String? by StringPreferencesDelegate<String?>(...)
Es bleibt die Zuordnung zum Abrufen und Festlegen der Eigenschaften, wir bestimmen den Datentyp durch delaultValue. Gleichzeitig gibt es eine intelligente Umwandlung dieses Werts in den spezifischen Datentyp. Wenn ein Fehler aufgetreten ist und die Eigenschaft nicht vom Typ TValue ist, geben wir defValue zurück.
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() } }
Bei anderen Datentypen ist es ähnlich.
Benutzerdefinierter Fehler
Es bleibt die Frage, was mit dem Zweig else zu tun ist, da der TValue-Typ absolut alles sein kann.
Es ist eine gute Form, einen benutzerdefinierten Fehler zu machen. Wenn eine Ausnahme auftritt, ist so klar wie möglich, was passiert ist.
class NotFoundRealizationException(value: Any?) : Exception("not found realization for ${value?.javaClass}") ... else -> throw NotFoundRealizationException(value) ...
Fazit
Insgesamt machen wir einen Delegierten einsatzbereit:
@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") }
Anwendungsbeispiel
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" } }