Delegado de preferências do Android

Este artigo mostra um exemplo de criação de um representante para SharedPreferences, o que reduz o padrão e torna o uso de SharedPrefernces mais conveniente. Quem quiser ver o resultado pode optar por uma solução pronta.


Uma das tarefas urgentes de desenvolvimento para o Android é salvar todos os dados entre as sessões. As principais maneiras de fazer isso: armazene no servidor ou em arquivos no dispositivo executável. Uma das primeiras maneiras que qualquer desenvolvedor iniciante do Android pode saber disso é armazená-lo em um arquivo usando a ferramenta SharedPreferences já pronta.


Digamos que precisamos gravar o nome de usuário e exibi-lo em algum lugar do aplicativo.


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" } } 

O que é um delegado e como ele é preparado


Em poucas palavras, esta é uma classe que encapsula a configuração e a obtenção de uma propriedade. Quem quer saber mais documentação oficial .


Para tornar uma classe um delegado, você deve implementar a interface ReadOnlyProperty para val e ReadWriteProperty para var. Passamos SharedPreferences, a chave pela qual a propriedade e o valor padrão serão armazenados através do construtor. Em setValue, defina o valor para getValue, obtenha o valor.


 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) } } 

Aplicar delegado


 class UserStore(private val preferences: SharedPreferences) { var userName: String by StringPreferencesDelegate(preferences, USER_NAME, "") companion object { private const val USER_NAME = "user_name" } } 

A atribuição a uma propriedade delegada é feita pela palavra-chave by. Agora, toda vez que essa propriedade for solicitada ou configurada, os métodos getValue e setValue do delegado criado serão iniciados.


O mesmo agora pode ser feito com outros campos, por exemplo, se você deseja salvar o telefone do usuário da mesma maneira.


  var userPhone: String by StringPreferencesDelegate(preferences, USER_PHONE, "") 

Genérico


Para não criar um representante separado para cada tipo de dados, usamos generalizações da documentação oficial genérica .


Geralmente, o primeiro conhecimento inconsciente de genéricos ocorre quando você instancia a classe List. Para isso, é determinado o tipo de dados específico com o qual trabalha.


 val names :List<String> = listOf("Jon","Bob","Max") 

Para especificar um tipo de dados generalizado para uma classe após seu nome, você deve especificar o nome dessa variável entre colchetes angulares.


 PreferencesDelegate<TValue>(...) 

Agora você precisa especificar que o valor definido e o valor padrão sejam do tipo 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) { ... } } 

Portanto, agora a criação de uma instância da classe se parece com isso:


 var userName: String? by StringPreferencesDelegate<String?>(...) 

Resta fazer o mapeamento para obter e definir as propriedades, determinamos o tipo de dados por delaultValue, ao mesmo tempo em que fornece uma conversão inteligente desse valor para o tipo de dados específico, se algo der errado e a propriedade não for do tipo TValue, retornamos 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() } } 

Com outros tipos de dados, é semelhante.


Erro personalizado


A questão permanece o que fazer com o ramo else, pois o tipo TValue pode ser absolutamente qualquer coisa.
É uma boa forma de cometer seu erro personalizado. Se ocorrer uma exceção, será o mais claro possível o que aconteceu.


  class NotFoundRealizationException(value: Any?) : Exception("not found realization for ${value?.javaClass}") ... else -> throw NotFoundRealizationException(value) ... 

Conclusão


No total, temos um delegado pronto para uso:


 @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") } 

Exemplo de aplicação


 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/pt461161/


All Articles