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