# 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>
1
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)
1
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)
1

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()
}
1
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)
   }
}
1
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)
}
1
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
}
1
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))
    }
}
1
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()
}
1
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()
}
1
2
3
4
5

Esta función sería la misma que la función save.