# XML con SharedPreferences
# Introducción
En ocasiones, necesitamos persistir información para conocer la personalización de la app hecha por el usuario o para acelerar la carga de pantallas ya vistas recuperando la información previamente descargada.
Cuando la información a persistir es pequeña o no requiere de consultas anidadas, podemos usar SharedPreferences como almacén de datos.
SharedPreferences es una API que facilita la gestión de los datos en un fichero xml.
# API
SharedPreferences es una API que almacena la información en formato Clave -> Valor, es decir, si necesito guardar o recuperar un dato, tengo que dar una clave para recuperar o almacenar la información.
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="1">{"id":1,"name":"User1","username":"Username1"}</string>
<string name="2">{"id":2,"name":"User2","username":"Username2"}</string>
<string name="3">{"id":3,"name":"User3","username":"Username3"}</string>
<string name="4">{"id":4,"name":"User4","username":"Username4"}</string>
<string name="6">{"id":6,"name":"User6","username":"Username6"}</string>
<string name="7">{"id":7,"name":"User7","username":"Username7"}</string>
</map>
2
3
4
5
6
7
8
9
Donde name
sería la clave y el json {"id":1,"name":"User1","username":"Username1"}
el valor.
Existe dos formas de instanciar un SharedPreferences:
- A nivel de Aplicación, es decir, desde cualquier sitio de la app se tiene acceso al fichero xml.
- A nivel de Activity, es decir, sólo la actividad donde se cree el SharedPreferences tendrá acceso.
El elegir una u otra opción dependerá de la funcionalidad que se está desarrollando. Básicamente la pregunta que nos tenemos que hacer es la siguiente: ¿Se quiere acceder a esta información sólo en la actividad o en toda la aplicación?
# Uso a nivel de Aplicación
Para crear un SharedPreferences que pueda ser accedido desde toda la aplicación se hará así:
val sharedPreferences =
context.getSharedPreferences("nombre_del_fichero_xml", Context.MODE_PRIVATE)
2
Se necesita:
- El contexto de una actividad o aplicación.
- Nombre del fichero xml. Este nombre debería estar declarado en el fichero strings.xml.
- El modo en el que será accesible en otras aplicaciones. En este caso, sólo será accesible desde esta aplicación.
# Uso a nivel de Activity
Para crear un SharedPreferences que pueda ser accedido sólo desde la actividad que lo crea:
val sharedPreferences = activity.getPreferences(Context.MODE_PRIVATE)
Se necesita:
- La actividad que va a crear el SharedPreferences.
- El modo en el que será accesible en otras aplicaciones. En este caso, sólo será accesible desde esta aplicación.
Información
Cuando se crea un SharedPreferences para que sólo sea accesible desde la actividad que lo crea, al no pasarle el nombre del fichero xml que se va a crear, la API SharedPreferences pone el nombre de la actividad que lo crea.
# Guardar
Para guardar o escribir información en un fichero, debemos tener en cuenta que se hace con el formato clave | valor. La clave debe ser única para cada uno de los objetos que vamos a almacenar. Si esto no fuera así, la información se sobreescribiría.
Por otro lado, para poder guardar objetos necesitamos serializarlos a un formato que no permita recuperarlos después. Esto lo haremos con una librería que nos permita serializar objetos a json y viceversa.
Ejemplo:
fun save(superHeroes: List<SuperHeroe>) {
val editor = sharedPreferences.edit()
superHeroes.forEach { superHeroe ->
editor.putString(superHeroe.id.toString(), gson.toJson(superHeroe))
}
editor.apply()
}
2
3
4
5
6
7
Para poder escribir información con SharedPreferences necesitamos obtener una instancia de la
clase Editor val editor = sharedPreferences.edit()
.
En el ejemplo estamos almacenando un listado de objetos SuperHeroes. Almacenamos objeto a objeto
y elegimos como su clave el 'id' del superHeroe superHeroe.id.toString
. La clave siempre tiene
que ser de tipo String, por eso convertimos el id
, que es entero, a String.
Por último, para confirmar la información de guardado necesitamos hacer un apply()
o commit()
.
Si no se ejecuta ninguna de estas dos acciones, la información no guarda.
WARNING
Aunque commit y apply hacen la misma operación: confirmar la operación de escritura en SharedPreferences,
lo hacen de forma diferente. apply()
lo hace de forma asíncrona, es decir, no bloquea el hilo principal y
commit()
lo hace de manera síncrona, es decir, bloquería el hilo que está ejecutando el código.
WARNING
Es importante no confundir apply()
con apply{ }
La primera es la correcta y la segunda está
relacionada con las functions scope
de Kotlin.
Ejemplo del fichero xml tras guardar un listado de SuperHeroes:
# Recuperar un elemento específico
Para recuperar un elemento específico almacenado con SharedPreferences, se necesita la clave que
lo identifica en el fichero xml. Siguiendo el ejemplo anterior, necesitamos el id
del objeto.
fun findById(superHeroe: SuperHeroe): SuperHeroe? {
val jsonSuperHeroe = sharedPreferences.getString(superHeroe.id.toString(), null)
return jsonSuperHeroe?.let {
gson.fromJson(it, SuperHeroe::class.java)
}
}
2
3
4
5
6
En esta ocasión, al no escribir en el fichero no es necesario obtener un editor()
, basta con
la instancia sharedPreferences
.
A través del id
recuperar la serialización del objeto que está almacenado en formato String
.
val jsonSuperHeroe = sharedPreferences.getString(superHeroe.id.toString(), null)
El método sharedPreferences.getString
permite devolver un parámetro por defecto si el id
a buscar no se encuentra. En esta ocasión, si no se encuentra devolvemos null
.
Para deserializarlo usamos la librería Gson
que nos permite convertir de un json en formato String
a un objeto
return jsonSuperHeroe?.let {
gson.fromJson(it, SuperHeroe::class.java)
}
2
3
TIP
Usamos una function scope let
para gestionar el null.
# Recuperar todos los elementos
Para recuperar toda la información almacenada a través de SharedPreferences se realiza de la siguiente forma:
fun getHeroes(superHeroe: SuperHeroe): List<SuperHeroe> {
val superHeroes: MutableList<SuperHeroe> = mutableListOf()
sharedPreferences.all.forEach { map ->
superHeroes.add(gson.fromJson(map.value as String, SuperHeroe::class.java))
}
return superHeroes
}
2
3
4
5
6
7
Para recuperar todas las claves almacenadas en el fichero xml a través de SharedPreferences usamos
sharedPreferences.all
. Esta función devuelve un Map<String, ?>
. El primer parámetro String
corresponde a la clave almacenada en una línea del fichero xml y la ?
corresponde al valor asociada
a la clave. Al poder almacenar como valor un String, Int, etc. hay que castear esa ? al tipo de dato
esperado. En esta ocasión, es un String (
json): superHeroes.add(gson.fromJson(map.value as String, SuperHeroe::class.java))
.
El casting se hace así: map.value as String
.
El método se puede simplificar usando el operador map
que me permite transformar un listado de objetos de tipo A
en otro listado de objetos de tipo B. Quedaría así:
fun getHeroes(superHeroe: SuperHeroe): List<SuperHeroe> {
return sharedPreferences.all.map {
superHeroes.add(gson.fromJson(it.value as String, SuperHeroe::class.java))
}
}
2
3
4
5
# Eliminar
Para eliminar un registro del fichero con SharedPreferences ejecutaremos un método de la api:
fun delete(id: String){
sharedPreferences.edit().remove(id).apply()
}
2
3
Para acceder al método remove de la API, necesitamos el editor
del SharedPreferences. Hay que
pasarle el id que queremos eliminar.
WARNING
Recuerda que para salvar los cambios hay que ejecutar el método apply() o commit()
# Modificar
Para modificar los datos asociados a una clave, basta con pasar el objeto ya modificado y usar la misma clave. La API SharedPreferences busca la clave y sobreescribe el objeto.
fun update(superHero: SuperHeroe) {
val editor = sharedPreferences.edit()
editor.putString(superHeroe.id.toString(), gson.toJson(superHeroe))
editor.apply()
}
2
3
4
5
Esta función sería la misma que la función save
.