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 } }
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
Buenas, os recuerdo que las novedades se irán publicando en la página de Facebook de la web. https://www.facebook.com/DevKotlin/
Saludos!
Simplemente genial, los artículos son muy concisos, explicas lo justo y necesario para entenderlos sin que sean pesados. Estoy deseando de que continues!!.
Por favor no lo dejes y muchas gracias por tu dedicación.
Un saludo.
Estoy terminando un nuevo artículo ^^
[…] el capítulo pasado, empezamos a conocer la persistencia de datos y su utilidad, el problema es que como ya comenté […]
Holaaaa, soy ivan (si de nuevo :V) ¿En que momento se instancia la clase Prefs ? 🙁 mi app se crashea al lanzarla
Buenas Iván, la clase Prefs se instancia en la clase ShareApp que extiende de application.
class SharedApp : Application() {
companion object {
lateinit var prefs: Prefs
}
override fun onCreate() {
super.onCreate()
prefs = Prefs(applicationContext)
}
}
Me he fijado bien y faltaba una cosilla que he añadido.
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
hola soy nuevo me gustaria saber si puedo tener uno o mas valores almacenados con los shared preference saludos desde mexico muy buen tuto
Buenas, puedes almacenar todos los valores que quieras, aunque si van a ser muchos es recomendable usar una base de datos 🙂
Hola, soy nuevo y me encontré tus artículos, que son muy buenos por cierto, iba bien hasta este momento me tope con un error,
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property prefs has not been initialized
Ojala me pudieras ayudar.
Buenas, eso pasa porque la variable prefs no ha sido inicializada. Se hace en el onCreate() de la clase SharedApp que extiende de application.
class SharedApp : Application() {
companion object {
lateinit var prefs: Prefs
}
override fun onCreate() {
super.onCreate()
prefs = Prefs(applicationContext)
}
}
«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«
Lo hice pero por alguna extraña razón no funcionaba,
al parecer la clase SharedApp no extendía a Aplication(), la solución fue agregar esa misma linea al MainActivity quedando así:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
SharedApp.prefs = Prefs(applicationContext)
…//}
fue lo mismo con la aplicación del artículo 17, ante todo te agradezco hermano.
SI SEÑOR!!!!
Me estaba volviendo loco con el mismo problema y hasta que no hice esto no se me arregló.
Enhorabuena!!!
Al parecer no funciona si lo quieres llamar desde otra actividad me aparece el siguiente mensaje
java.lang.ClassCastException: java.lang.Boolean cannot be cast to java.lang.String
at android.app.SharedPreferencesImpl.getString(SharedPreferencesImpl.java:288)
Buenas, si puedes llamarlo de otra actividad. Tiene que haber algún problema de código en esta segunda actividad porque esta espera un String y le llega un Boolean. ¿Podrías añadir el código de la segunda activity a ver si encontramos el error 🙂 ?
Tengo un problema al lanza la app, ¿me podrías ayudar?
Así tengo mi manifest
Tengo un problema al lanza la app, ¿me podrías ayudar?
Así tengo mi manifest
¿Cómo?
Puedes darme un ejemplo de como queda el manifest?
No me corre la app
Que error te pone? Un saludo
Hola
Lo primero de todo muchas gracias por el esfuerzo que realizas creando este curso.
Tengo un problema con este tema. La app que he creado (CursoKotlin2) siguiendo tus instrucciones, aparentemente está correcta, se genera y se ejecuta, pero no consigo que muestre la pantalla para solicitar/mostrar el nombre y los botones.
Aparece la pantalla pero en blanco. He hecho un debug y veo que se ejecuta la clase SharedApp, pero nunca pasa por el onCreate de MainActivity y lo que me muestra es una pantalla en blanco.
Cambie la activity y la formatee colocando los campos en su sitio y no dejándolos superpuestos unos con otros como se quedaban en tu ejemplo, pero nada.
No sé si me falta algo en el Manifest o en la clase SharedApp que haga que ejecute la MainActivity
Agradecería cualquier ayuda.
Muchas gracias
Puedes mostrarme el código de las 2 activities por favor? 🙂
Hola
Te pongo el código que tengo. Te hago un Copy-Paste.
También puedes ver el proyecto completo CursoKotlin2 en:
https://drive.google.com/drive/folders/1BmG6vWye2Gnkcdez-tOR4telPZjafsI1?usp=sharing
Gracias
PANTALLA
MAINACTIVITY
class MainActivity : AppCompatActivity() {
class MainActivity : AppCompatActivity() {
val EMPTY_VALUE = «»
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.pantalla)
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
}
}
}
SHAREDAPP
class SharedApp : Application() {
companion object {
lateinit var prefs: Prefs
}
override fun onCreate() {
super.onCreate()
prefs = Prefs(applicationContext)
}
}
PREFS
class Prefs (context: Context) {
val PREFS_NAME = «com.martruchagr.cursokotlin2»
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()
}
class MainActivity : AppCompatActivity() { Está dos veces, entiendo que ha sido un error copiando aquí no?
Otra cosilla, puedes pasar el contenido de AndroidManifest.xml 🙂
Parece que la pantalla no se ha pegado
Esta es:
Borra la app y vuelte a intentarlo. Se habrá quedado información persistida y como lo has cambiado ve conflictos.
Hola! Muchas gracias por el artículo, me ha resultado muy útil. Aún así, tengo un problema en la clase Prefs.kt. Android Studio me dice que en la línea get() = prefs.getString(SHARED_NAME, «») hay un error:
Type mismatch.
Required: String
Found: String?
He copiado y pegado la clase tal cual está en tu ejemplo pero me obliga a poner un «toString()» o a declarar la variable name como String?. ¿Qué puede estar pasando?
Buenas,
Primero de todo, felicidades y muchas gracias por tu proyecto. Yo no pasaba de tocar macros en VBA para excel, y estoy aprendiendo muchísimo tu curso.
En referencia a este apartado, me está ocurriendo una cosa que no acabo de entender. He seguido todo el video, copiando paso por paso todo el código y no me compila correctamente. Por otro lado, me he fijado que dentro del código. Por ejemplo:
btnContinue.setOnClickListener() { accessToDetail() }
Al poner esta sentencia, btnContinue no se me relacionaba con el layaout. Al final, introduje dentro de una constante a la que llamé btnContinue, R.id.btnContinue para que me la relacionara. Sin embargo, el problema no ha quedad sólo ahi. setOnClickListener tampoco lo detectaba y Android Studio me obliga a crear una función específica para relacionarla. ¿Qué estoy haciendo mal? Porque en tú vídeo he visto que no te ha sido necesario hacer tanta parafernalia.
Un saludo cordial
Buenas, eso se basa en que para los nuevos proyectos de Android ahora hay que trabajar con ViewBinding que es el nuevo sistema para conectar vistas. Te dejo el capítulo 29 donde lo explico todo https://cursokotlin.com/capitulo-29-view-binding-en-kotlin/
Justo ahora lo estaba viendo. Muchísimas gracias. Ya funciona 🙂