Un requisito básico para la mayoría de aplicaciones móviles a nivel profesional suele ser la persistencia de datos, con ella conseguimos no solo persistir información del usuario, sino que podemos almacenar contenido que ayuden a un funcionamiento mas óptimo, por ejemplo cacheando contenido que recuperamos de internet para no repetir las consultas cada vez que pasamos por dicha activity.

Las tres formas de almacenar información son a través de las Shared Preferences, ficheros o bases de datos. Como norma general no se suelen usar ficheros así que los descartaremos por ahora y en este capítulo veremos las Shared Preferences.

¿Que son las Shared Preferences?

Las Shared están destinadas a almacenar pequeñas cantidades de información a través de clave-valor. Debe ser información no comprometida puesto que Android generará un fichero XML donde almacenará toda esta información sin cifrar. Estos ficheros pueden ser compartidos o privados.

En este proyecto vamos a crear una aplicación muy sencilla en la cual si no hay ningún nombre guardado nos pedirá que lo pongamos, si por el contrario ya disponemos de uno nos saldrá en pantalla junto a un botón de borrar. Otra parte importante es que haremos nuestro primer patrón de diseño, el Singleton.

Lo primero que haremos será crear un nuevo proyecto y haremos una maquetación muy simple del layout.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.cursokotlin.sharedpreferences.MainActivity">

    <TextView
        android:id="@+id/tvName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/etName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="Escribe tu nombre"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btnDeleteValue"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Borrar"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>

    <Button
        android:id="@+id/btnSaveValue"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Guardar"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>

</android.support.constraint.ConstraintLayout>

Veréis que hay componentes que se sobreponen unos sobre otros, esto es porque así de paso os voy a enseñar los posibles casos que puede tener una vista.

Lo siguiente que haremos será crear una clase donde definiremos todo lo necesario para trabajar con shared, la llamaremos Prefs. Esta clase recibirá un contexto, en este caso el de la aplicación, para poder instanciarlo una sola vez al iniciar la aplicación y tener un objeto pref.

class Prefs (context: Context) {
    val PREFS_NAME = "com.cursokotlin.sharedpreferences"
    val SHARED_NAME = "shared_name"
    val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, 0)

    var name: String
        get() = prefs.getString(SHARED_NAME, "")
        set(value) = prefs.edit().putString(SHARED_NAME, value).apply()
}

Lo primero que hemos hecho ha sido definir dos constantes, PREFS_NAME y SHARED_NAME la primera será la clave del objeto pref que crearemos más adelante y la segunda la clave del nombre que almacenaremos. Recordad que las shared preferences se almacenan con clave-valor, lo que significa que para pedir el valor de «name» necesitamos pedirlo a través de la clave SHARED_NAME.

Observad también que hemos definido una variable name que será donde almacenemos el nombre como dije antes, pero que hemos sobreescrito el método get y set, así que cuando pidamos el valor de name, este accederá a el objeto prefs y pedirá dicho valor que corresponde la clave SHARED_NAME. Lo mismo con el set, que a través de prefs.edit().putString(SHARED_NAME, value).apply() almacenará el valor que le digamos. Obviamente si fuera otro tipo de variable, por ejemplo un Int, cambiaríamos el putString() por un putInt() y así con cada tipo de variable.

Ahora vamos a crear una clase algo diferente. Esta clase va a extender de Application() y eso significa que será lo primero en ejecutarse al abrirse la aplicación.

Para extender de dicha clase en Kotlin es muy sencillo, simplemente debemos crear una clase como siempre y a continuación del nombre después de dos puntos, pondremos la clase en cuestión.

class SharedApp : Application() {
    companion object {
        lateinit var prefs: Prefs
    }

    override fun onCreate() {
        super.onCreate()
        prefs = Prefs(applicationContext)
    }
}

Aunque la clase sea bastante pequeña vamos a comentar unas cosas. Para empezar hemos declarado un companion object que será el objeto de nuestra clase Prefs que usaremos en todo el proyecto, así que para que lo entendáis el companion object es una forma de tener un objeto disponible en todo el proyecto (un objeto estático para los que conozcan java). Y delante lleva un lateinit  que quiere decir que será instanciado más tarde, en este ejemplo en el método onCreate() de esta clase.

Recordad que para que esta clase se lance al abrir la app debemos ir al AndroidManifest.xml y añadir android:name=”.SharedApp ” dentro de la etiqueta <Application>

Ahora iremos a nuestro MainActivity a desarrollar un poco de lógica que falta.

La idea es crear dos métodos, uno mostrará una vista para invitados y el otro la vista del perfil, la diferencia entre ellos será que si eres invitada te mostrará un EditText y un botón de guardar y por el contrario si ya hay un nombre guardado en persistencia de datos pues te saludará y tendrá un botón para borrar dicho campo de memoria.

fun showProfile(){
      tvName.visibility = View.VISIBLE
      tvName.text = "Hola ${SharedApp.prefs.name}"
      btnDeleteValue.visibility = View.VISIBLE
      etName.visibility = View.INVISIBLE
      btnSaveValue.visibility = View.INVISIBLE
  }

  fun showGuest(){
      tvName.visibility = View.INVISIBLE
      btnDeleteValue.visibility = View.INVISIBLE
      etName.visibility = View.VISIBLE
      btnSaveValue.visibility = View.VISIBLE
  }

Como ya había dicho al comienzo del post, os quiero hablar sobre la visibilidad. Un componente puede estar en tres estados visible, invisible y gone.

  • Visible: El componente se ve en la pantalla, por defecto viene esta opción activada.
  • Invisible: El componente no se ve pero sigue estando en la pantalla, por lo que se puede seguir trabajando con él, por ejemplo poner algo a la derecha de un componente invisible.
  • Gone: El componente NO está en la pantalla por lo que no hay interacción posible.

Fijaros que el método showProfile asigna un valor a tvName, lo que significa que está accediendo a las shared preferences. Para ello simplemente llamamos a la clase SharedApp (la que contiene el companion object), el objeto del cual estamos hablando y el atributo name que como vimos al principio hemos modificado para que cuando hagamos un get (sacar la información que almacena) le pida nuestras shared preference el valor de SHARED_NAME.

Ahora necesitamos un método que compruebe si hay información en name y así comprobar si el usuario ha guardado su nombre.

fun configView(){
    if(isSavedName()) showProfile() else showGuest()
}

fun isSavedName():Boolean{
    val myName = SharedApp.prefs.name
    return myName != EMPTY_VALUE
}

Para finalizar solo debemos guardar o borrar la información del usuario cada vez que pulsemos el botón correspondiente y acto seguido volver a llamar a configView() para que muestre la vista oportuna.

btnSaveValue.setOnClickListener {
    SharedApp.prefs.name = etName.text.toString()
    configView() }
btnDeleteValue.setOnClickListener {
    SharedApp.prefs.name = EMPTY_VALUE
    configView()}

La clase completa quedaría así.

class MainActivity : AppCompatActivity() {

    val EMPTY_VALUE = ""

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        configView()

        btnSaveValue.setOnClickListener {
            SharedApp.prefs.name = etName.text.toString()
            configView() }
        btnDeleteValue.setOnClickListener {
            SharedApp.prefs.name = EMPTY_VALUE
            configView()}
    }


    fun showProfile(){
        tvName.visibility = View.VISIBLE
        tvName.text = "Hola ${SharedApp.prefs.name}"
        btnDeleteValue.visibility = View.VISIBLE
        etName.visibility = View.INVISIBLE
        btnSaveValue.visibility = View.INVISIBLE
    }

    fun showGuest(){
        tvName.visibility = View.INVISIBLE
        btnDeleteValue.visibility = View.INVISIBLE
        etName.visibility = View.VISIBLE
        btnSaveValue.visibility = View.VISIBLE
    }

    fun configView(){
        if(isSavedName()) showProfile() else showGuest()
    }

    fun isSavedName():Boolean{
        val myName = SharedApp.prefs.name
        return myName != EMPTY_VALUE
    }
}

curso android en kotlin

Con esto ya tendríamos lista nuestra primera app con persistencia de datos. Este ha sido un ejemplo muy básico pero prefiero ir poco a poco con este tema pues puede ser muy abundante si no vamos poco a poco.

Continúa con el curso: Capítulo 17 – Bases de datos con Room