79
Persistencia Índice 1 Persistencia en Android: ficheros y SQLite................................................................. 3 1.1 Introducción............................................................................................................. 3 1.2 Manejo de ficheros tradicionales en Android.......................................................... 3 1.3 Base de datos SQLite............................................................................................... 6 2 Ejercicios - Persistencia en Android: ficheros y SQLite............................................ 15 2.1 Uso de ficheros (0.5 puntos).................................................................................. 15 2.2 Persistencia con ficheros (0.5 puntos)................................................................... 16 2.3 Base de datos: SQLiteOpenHelper (0.5 puntos).................................................... 16 2.4 Base de datos: inserción y borrado (0.5 puntos).................................................... 17 2.5 Base de datos: probar nuestro adaptador (0.5 puntos)........................................... 18 2.6 Base de datos: cambios en la base de datos (0.5 puntos)....................................... 18 3 Persistencia en Android: proveedores de contenidos y SharedPreferences............... 19 3.1 Shared Preferences................................................................................................. 19 3.2 Proveedores de contenidos..................................................................................... 25 4 Ejercicios - Persistencia en Android: proveedores de contenidos y SharedPreferences........................................................................................................... 34 4.1 Compartir datos entre actividades con Shared Preferences (0.75 puntos)............. 34 4.2 Actividad de preferencias (0.75 puntos)................................................................ 34 4.3 Proveedor de contenidos propio (0.75 puntos)...................................................... 35 4.4 ¿Por qué conviene crear proveedores de contenidos? (0.75 puntos)..................... 36 5 Persistencia de datos en iOS: Ficheros y SQLite....................................................... 38 5.1 Introducción........................................................................................................... 38 5.2 Ficheros plist (Property Lists)................................................................................ 38 5.3 SQLite.................................................................................................................... 44 6 Persistencia de datos en iOS: Ficheros y SQLite - Ejercicios.................................... 54 6.1 Creando y leyendo un fichero plist (1.5 puntos).................................................... 54 6.2 Implementar vista de detalle de la serie (0.5 puntos).............................................55 Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.

Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de

  • Upload
    others

  • View
    5

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de

Persistencia

Iacutendice

1 Persistencia en Android ficheros y SQLite 3

11 Introduccioacuten 3

12 Manejo de ficheros tradicionales en Android 3

13 Base de datos SQLite 6

2 Ejercicios - Persistencia en Android ficheros y SQLite15

21 Uso de ficheros (05 puntos) 15

22 Persistencia con ficheros (05 puntos) 16

23 Base de datos SQLiteOpenHelper (05 puntos) 16

24 Base de datos insercioacuten y borrado (05 puntos) 17

25 Base de datos probar nuestro adaptador (05 puntos) 18

26 Base de datos cambios en la base de datos (05 puntos)18

3 Persistencia en Android proveedores de contenidos y SharedPreferences 19

31 Shared Preferences19

32 Proveedores de contenidos25

4 Ejercicios - Persistencia en Android proveedores de contenidos ySharedPreferences34

41 Compartir datos entre actividades con Shared Preferences (075 puntos)34

42 Actividad de preferencias (075 puntos) 34

43 Proveedor de contenidos propio (075 puntos) 35

44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos) 36

5 Persistencia de datos en iOS Ficheros y SQLite 38

51 Introduccioacuten 38

52 Ficheros plist (Property Lists)38

53 SQLite 44

6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios54

61 Creando y leyendo un fichero plist (15 puntos)54

62 Implementar vista de detalle de la serie (05 puntos)55

Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

63 Uso baacutesico de una base de datos SQLite (1 punto)55

7 Persistencia de datos en iOS User Defaults y Core Data 57

71 Introduccioacuten 57

72 User Defaults 57

73 Core Data 61

8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios77

81 Usando User Defaults (1 punto) 77

82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos) 77

83 Migrando datos con Core Data (05 puntos)78

Persistencia

2Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

1 Persistencia en Android ficheros y SQLite

11 Introduccioacuten

En este moacutedulo trataremos diferentes mecanismos existentes en los sistemas Android eiOS para el almacenamiento de datos tanto en la memoria del dispositivo como enmemoria externa Comenzaremos en primer lugar tratando el manejo de ficheros enAndroid para a continuacioacuten explicar coacutemo utilizar el motor de base de datos SQLite ennuestras aplicaciones en dicho sistema A continuacioacuten presentaremos los proveedores decontenidos los cuales nos proporcionan un mecanismo estaacutendar para el intercambio deinformacioacuten entre aplicaciones Android Veremos coacutemo usar los proporcionados por elsistema y crear los nuestros propios

En las sesiones 3 y 4 comentaremos los meacutetodos maacutes usados para almacenar datos dentrode nuestras aplicaciones iOS Empezaremos explicando de forma detallada el uso de losficheros de propiedades (plist) un tipo de ficheros con formato XML que es muy usadoen sistemas iOS Continuaremos con el manejo de bases de datos SQLite dentro de lasaplicaciones iOS explicando la libreria que disponemos dentro del SDK de iPhone Poruacuteltimo en la sesioacuten 4 del moacutedulo empezaremos explicando el meacutetodo de almacenamientomediante variables de tipo User Defaults para terminar con algo maacutes complejo como es eluso de Core Data forma de almacenamiento de datos muy extendida en estos uacuteltimosantildeos dentro de los sistemas iOS

12 Manejo de ficheros tradicionales en Android

El uso de ficheros tradicionales estaacute permitido para los programas de Android aunquecomo veremos en eacutesta y otras sesiones hay otras tecnologiacuteas mucho maacutes avanzadascomo el uso del motor de base de datos SQLite y para el caso de preferencias de lasaplicaciones las SharedPreferences

121 Apertura de ficheros

En Android podemos abrir un fichero para lectura o para escritura de la siguiente forma

Abrir un fichero de salida privado a la aplicacioacutenFileOutputStream fos = openFileOutput(ficherotxtContextMODE_PRIVATE) Abrir un fichero de entradaFileInputStream fis = openFileInput(ficherotxt)

Al abrir el fichero para salida el modo ContextMODE_PRIVATE hace que eacuteste seaprivado a la aplicacioacuten Si el fichero a abrir no existe eacuteste se creariacutea Existe un paraacutemetropara permitir compartir el fichero pero lo adecuado en este caso seriacutea hacer uso deproveedores de contenidos por lo que no trataremos dicho paraacutemetro

Persistencia

3Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Por otra parte el paraacutemetro ContextMODE_APPEND permite antildeadir contenido a un ficheroque ya se hubiera creado previamente (o crear uno nuevo en el caso en el que eacuteste noexistiera)

Estos meacutetodos tan soacutelo permiten abrir ficheros en la carpeta de la aplicacioacuten por lo que sise intenta antildeadir alguacuten separador de ruta se produciraacute una excepcioacuten El fichero abiertoutilizando OpenFileOutput (usando cualquiera de los dos paraacutemetros anteriores) seguardariacutea dentro del aacuterbol de directorios del dispositivo en la carpetadatadata[paquete]files donde [paquete] seriacutea el nombre del paquete de laaplicacioacuten Se trata de ficheros de texto normales por los que se puede acceder a ellosutilizando el shell de adb y utilizando el comando cat Para acceder al shell podemoshacer lo siguiente

adb shell

Por supuesto no debemos olvidarnos de cerrar el fichero una vez vayamos a dejar deutilizarlo mediante la correspondiente llamada al meacutetodo close()

fosclose()fisclose()

122 Ficheros como recursos

Es posible antildeadir ficheros como recursos de la aplicacioacuten Para ello los almacenamos enla carpeta resraw de nuestro proyecto Estos ficheros seraacuten de soacutelo lectura y paraacceder a ellos llamaremos al meacutetodo openRawResource del objeto Resource de nuestraaplicacioacuten Como resultado obtendremos un objeto de tipo InputStream Un ejemploseriacutea el que se muestra a continuacioacuten

Resources myResources = getResources()InputStream myFile = myResourcesopenRawResource(Rrawfichero)

Antildeadir ficheros a los recursos de la aplicacioacuten es un mecanismo para disponer de fuentesde datos estaacuteticas de gran tamantildeo como podriacutea ser por ejemplo un diccionario Esto seraacuteasiacute en aquellos casos en los que no sea aconsejable (o deseable) almacenar estainformacioacuten en una base de datos

Como en el caso de cualquier otro recurso seriacutea posible guardar diferentes versiones deun mismo archivo para diferentes configuraciones del dispositivo Podriacuteamos tener porejemplo un fichero de diccionario para diferentes idiomas

123 Operar con ficheros

Una alternativa simple para escribir en un fichero o leer de eacutel es utilizar las clasesDataInputStream y DataOutputStream que nos permiten leer o escribir a partir dediferentes tipos de datos como enteros o cadenas En el siguiente ejemplo vemos coacutemo esposible guardar una cadena en un fichero y a continuacioacuten leerla

Persistencia

4Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

FileOutputStream fos = openFileOutput(ficherotxtContextMODE_PRIVATE)String cadenaOutput = new String(Contenido del ficheron)DataOutputStream dos = new DataOutputStream(fos)doswriteBytes(cadenaOutput)fosclose()

FileInputStream fin = openFileInput(ficherotxt)DataInputStream dis = new DataInputStream(fin)String string = disreadLine() Hacer algo con la cadenafinclose()

124 Almacenar datos en la tarjeta de memoria

Veamos ahora queacute pasos debemos seguir para poder escribir datos en la tarjeta dememoria (SD card) Para ello se requiere el uso del meacutetodoEnvironmentgetExternalStorageDirectory() y una instancia de la claseFileWriter tal como se muestra en el siguiente ejemplo

try File raiz = EnvironmentgetExternalStorageDirectory()if (raizcanWrite())

File file = new File(raiz ficherotxt)BufferedWriter out = new BufferedWriter(new

FileWriter(file))outwrite(Mi texto escrito desde Androidn)outclose()

catch (IOException e)

Loge(FILE IO Error en la escritura de fichero +egetMessage())

Para probarlo en el emulador necesitamos tener creada una tarjeta SD lo cual se puedeconseguir mediante la herramienta del Android SDK mksdcard

mksdcard 512M sdcardiso

Podremos cargar esta tarjeta SD en nuestro emulador seleccionando el archivo recieacutencreado dentro del apartado SD card de la ventana de edicioacuten de las propiedades delemulador Para copiar fuera del emulador el archivo que hemos grabado en la tarjeta dememoria podemos utilizar el comando

adb pull sdcardficherotxt ficherotxt

Por uacuteltimo no debemos olvidar antildeadir el permiso correspondiente en el archivo Manifestde nuestra aplicacioacuten para poder escribir datos en un dispositivo de almacenamientoexterno Para ello antildeadimos lo siguiente al elemento manifest del AndroidManifestxml(recuerda que esto no va ni dentro del elemento application ni dentro del elementoactivity)

ltuses-permission androidname=androidpermissionWRITE_EXTERNAL_STORAGEgt

Persistencia

5Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

13 Base de datos SQLite

SQLite es un gestor de bases de datos relacional y es de coacutedigo abierto cumple con losestaacutendares y es extremadamente ligero Ademaacutes guarda toda la base de datos en un uacutenicofichero Su utilidad es evidente en el caso de aplicaciones de pequentildeo tamantildeo pues eacutestasno requeriraacuten la instalacioacuten adicional de ninguacuten gestor de bases de datos Esto tambieacutenseraacute asiacute en el caso de dispositivos empotrados con recursos limitados Android incluyesoporte a SQLite y en esta seccioacuten veremos coacutemo usarlo

Por medio de SQLite podremos crear bases de datos relacionales independientes paranuestras aplicaciones Con ellas podremos almacenar datos complejos y estructuradosAunque el disentildeo de bases de datos quedaraacute fuera del contenido de este curso ya quenecesitariacuteamos de maacutes tiempo para poder tratarlo es conveniente resaltar el hecho de quelas mejores praacutecticas en este campo siguen siendo uacutetiles en el caso de aplicacionesAndroid En particular la normalizacioacuten de datos para evitar redundancia es un paso muyimportante en el caso en el que estemos disentildeando una base de datos para dispositivoscon recursos limitados

En el caso del sistema Android SQLite se implementa como una libreriacutea C compacta enlugar de ejecutarse en un proceso propio Debido a ello cualquier base de datos quecreemos seraacute integrada como parte de su correspondiente aplicacioacuten Esto reducedependencias externas minimiza la latencia y simplifica ciertos procesos como lasincronizacioacuten

NotaLas bases de datos de Android se almacenan en el directoriodatadata[PAQUETE]databases donde [PAQUETE] es el paquetecorrespondiente a la aplicacioacuten Por defecto todas las bases de datos son privadas y tan soacuteloaccesibles por la aplicacioacuten que las creoacute

131 Content Values

Para insertar nuevas filas en tablas haremos uso de objetos de la clase ContentValuesCada objeto de este tipo representa una uacutenica fila en una tabla y se encarga de emparejarnombres de columnas con valores concretos Hay que tener en cuenta que SQLite difierede muchos otros motores de bases de datos en cuanto al tipado de las columnas La ideabaacutesicamente es que los valores para las columnas no tienen por queacute ser de un uacutenico tipoes decir los datos en SQLite son deacutebilmente tipados La consecuencia de esto es que noes necesario comprobar tipos cuando se asignan o extraen valores de cada columna de unafila con lo que se mejora la eficiencia

132 Cursores

Persistencia

6Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Los cursores son una herramienta fundamental en el tratamiento de informacioacuten enAndroid y tambieacuten cuando se trabaja con bases de datos Por lo tanto en esta seccioacutenintroducimos este concepto tan importante

Cualquier consulta a una base de datos en Android devolveraacute un objeto de la claseCursor Los cursores no contienen una copia de todos los resultados de la consulta sinoque maacutes bien se trata de punteros al conjunto de resultados Estos objetos proporcionanun mecanismo para controlar nuestra posicioacuten (fila) en el conjunto de resultadosobtenidos tras la consulta

La clase Cursor incluye entre otras las siguientes funciones de navegacioacuten entreresultados

bull moveToFirst desplaza el cursor a la primera fila de los resultados de la consultabull moveToNext desplaza el cursor a la siguiente filabull moveToPrevious desplaza el cursor a la fila anteriorbull getCount devuelve el nuacutemero de filas del conjunto de resultados de la consultabull getColumnName devuelve el nombre de la columna especificada mediante un iacutendice

que se pasa como paraacutemetrobull getColumnNames devuelve un array de cadenas conteniendo el nombre de todas las

columnas en el cursorbull moveToPosition mueve el cursor a la fila especificadabull getPosition devuelve el iacutendice de la posicioacuten apuntada actualmente por el cursor

A lo largo de esta sesioacuten aprenderemos coacutemo realizar consultas a una base de datos ycomo extraer valores o nombres de columnas concretos a partir de los cursores obtenidospor medio de dichas consultas

133 Trabajar con bases de datos SQLite

Suele considerarse una buena praacutectica el crear una clase auxiliar que nos facilite lainteraccioacuten con la base de datos Se trataraacute de una clase de tipo adaptador que crearaacute unacapa de abstraccioacuten que encapsularaacute dichas interacciones De esta forma podremosantildeadir eliminar o actualizar elementos en la base de datos sin necesidad de introducir niuna uacutenica instruccioacuten en SQL en el resto del coacutedigo permitiendo ademaacutes que se hagan lascomprobaciones de tipos correspondientes

Una clase de este tipo deberiacutea incluir meacutetodos para crear abrir o cerrar la base de datosTambieacuten puede ser un lugar conveniente para publicar constantes estaacuteticas relacionadascon la base de datos como por ejemplo constantes que almacenen el nombre de la tabla olas diferentes columnas

A continuacioacuten se muestra el coacutedigo de lo que podriacutea ser un adaptador para una base dedatos estaacutendar Incluye una extensioacuten de la clase SQLiteOpenHelper que seraacute tratada enmaacutes detalle en la siguiente seccioacuten y que se utiliza para simplificar la apertura lacreacioacuten y la actualizacioacuten de la base de datos

Persistencia

7Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

import androidcontentContextimport androiddatabaseimport androiddatabasesqliteimport androiddatabasesqliteSQLiteDatabaseCursorFactoryimport androidutilLog

public class MiAdaptadorBD private static final String NOMBRE_BASE_DATOS =

mibasededatosdbprivate static final String TABLA_BASE_DATOS = mainTableprivate static final int VERSION_BASE_DATOS = 1

Nombre de la columna de clave primariapublic static final String CP_ID=_id El nombre e iacutendice de cada columna en la base de datospublic static final String COLUMNA_NOMBRE=nombrepublic static final int COLUMNA_INDICE = 1 PENDIENTE crear campos adicionales para el resto de columnas de la base de datos

Sentencia SQL para crear una nueva base de datosprivate static final String CREAR_BASE_DATOS = create table +

TABLA_BASE_DATOS + ( + CP_ID + integer primary key autoincrement +COLUMNA_NOMBRE + text not null)

Variable para almacenar la instancia de la base de datosprivate SQLiteDatabase db Contexto de la aplicacioacuten que estaacute usando la base de datosprivate final Context contexto Clase helper usada para crear o actualizar la base de datos (hablamos de ella en la siguiente seccioacuten)private miHelperBD dbHelper

Crea la base de datos con ayuda del helperpublic MiAdaptadorBD(Context _contexto)

contexto = _contextodbHelper = new miHelperBD(contexto NOMBRE_BASE_DATOS

nullVERSION_BASE_DATOS)

Abre una base de datos ya existentepublic MiAdaptadorBD open() throws SQLException

db = dbHelpergetWritableDatabase()return this

Cierra la base de datos cuando eacutesta ya no va a ser utilizadapublic void close()

dbclose()

Meacutetodo para insertar un elemento en la tabla de la base dedatos

public int insertar(MiObjeto _miObjeto) PENDIENTE crear nuevos elementos ContentValues para

representar al objeto e insertarlos en la base de datos

Devolvemos el iacutendice del nuevo elemento en la base dedatos

return index

Persistencia

8Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Meacutetodo para eliminar un elemento de la tabla de la base dedatos

Devuelve un valor booleano indicando el eacutexito o no de la operacioacutenpublic boolean eliminar(long _indiceFila)

return dbdelete(TABLA_BASE_DATOS CP_ID + = +_indiceFila null) gt 0

Meacutetodo para obtener un Cursor que apunte a todas las filascontenidas

en la tabla de la base de datospublic Cursor obtenerTodos ()

return dbquery(TABLA_BASE_DATOS new String[] CP_IDCOLUMNA_NOMBRE

null null null nullnull)

Meacutetodo para obtener un elemento determinado de la base de datos a partir de su iacutendice de filapublic MiObjeto getObjeto(long _indiceFila)

PENDIENTE obtener un cursor a una fila de la base de datos y usar sus valores para inicializar una instancia de MiObjeto

return objectInstance

Meacutetodo para actualizar un elemento de la tabla de la base dedatos

public boolean actualizar(long _indiceFila MiObjeto _miObjeto) PENDIENTE crear nuevos ContentValues a partir del

objeto y usarlos para actualizar una fila en la tabla

correspondiente

return true

Clase privada auxiliar para ayudar en la creacioacuten yactualizacioacuten

de la base de datosprivate static class miHelperBD extends SQLiteOpenHelper

public miHelperBD(Context contexto String nombreCursorFactory factory int version)

super(contexto nombre factory version)

Meacutetodo llamado cuando la base de datos no existe en eldisco

y la clase Helper necesita crearla desde ceroOverridepublic void onCreate(SQLiteDatabase _db)

_dbexecSQL(CREAR_BASE_DATOS)

Meacutetodo llamado cuando la versioacuten de la base de datos en disco es diferente a la indicada en este caso la base de datos en disco necesita ser actualizadaOverridepublic void onUpgrade(SQLiteDatabase _db int

_versionAnterior

Persistencia

9Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

int _versionNueva) Mostramos en LogCat la operacioacutenLogw(AdaptadorBD Actualizando desde la

versioacuten +_versionAnterior + a la versioacuten +_versionNueva + lo cual borraraacute los

datospreviamente almacenados)

Actualizamos la base de datos para que seadapte a la nueva versioacuten

La forma maacutes sencilla de llevar a cabo dichaactualizacioacuten es

eliminar la tabla antigua y crear una nueva_dbexecSQL(DROP TABLE IF EXISTS +

TABLA_BASE_DATOS)onCreate(_db)

134 La clase SQLiteOpenHelper

La clase SQLiteOpenHelper es una clase abstracta utilizada como medio paraimplementar un patroacuten de creacioacuten apertura y actualizacioacuten de una determinada base dedatos Al implementar una subclase de SQLiteOpenHelper podemos abstraernos de laloacutegica subyacente a la decisioacuten de crear o actualizar una base de datos de manera previa aque eacutesta deba ser abierta

En el ejemplo de coacutedigo anterior se vio coacutemo crear una subclase de SQLiteOpenHelper

por medio de la sobrecarga de sus meacutetodos onCreate y onUpgrade los cuales permitencrear una nueva base de datos o actualizar a una nueva versioacuten respectivamente

NotaEn el ejemplo anterior el meacutetodo onUpgrade simplemente eliminaba la tabla existente y lareemplazaba con la nueva definicioacuten de la misma En la praacutectica la mejor solucioacuten seriacutea migrarlos datos ya existentes a la nueva tabla

Mediante los meacutetodos getReadableDatabase y getWritableDatabase podemos abrir yobtener una instancia de la base de datos de soacutelo lectura o de lectura y escriturarespectivamente La llamada a getWritableDatabase podriacutea fallar debido a falta deespacio en disco o cuestiones relativas a permisos por lo que es una buena praacutectica teneren cuenta esta posibilidad por medio del mecanismo de manejo de excepciones de JavaEn caso de fallo la solucioacuten podriacutea pasar por al menos proporcionar una copia de soacutelolectura tal como se muestra en el siguiente ejemplo

dbHelper = new miHelperBD(contexto NOMBRE_BASE_DATOS nullVERSION_BASE_DATOS)

SQLiteDatabase dbtry

db = dbHelpergetWritableDatabase() catch (SQLiteException ex)

db = dbHelpergetReadableDatabase()

Persistencia

10Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Al hacer una llamada a cualquiera de los dos meacutetodos anteriores se invocaraacuten losmanejadores de evento adecuados Asiacute pues si por ejemplo la base de datos no existieraen disco se ejecutariacutea el coacutedigo de onCreate Si por otra parte la versioacuten de la base dedatos se hubiera modificado se disparariacutea el manejador onUpgrade Lo que sucedeentonces es que al hacer una llamada a getWritableDatabase o getReadableDatabase

se devolveraacute una base de datos nueva actualizada o ya existente seguacuten el caso

135 Crear una base de datos sin SQLiteHelper

Tambieacuten es posible crear y abrir bases de datos sin necesidad de utilizar una subclase deSQLiteHelper Para ello haremos uso del meacutetodo openOrCreateDatabase En este casoseraacute necesario tambieacuten hacer uso del meacutetodo execSQL sobre la instancia de la base dedatos devuelta por el meacutetodo anterior para ejecutar los comandos SQL que permitiraacutencrear las tablas de la base de datos y establecer las relaciones entre ellas El siguientecoacutedigo muestra un ejemplo

private static final String NOMBRE_BASE_DATOS = mibasededatosdbprivate static final String TABLA_BASE_DATOS = tablaPrincipal

private static final String CREAR_BASE_DATOS =create table + TABLA_BASE_DATOS + _id integer primare key

autoincrement +columna_uno text not null)

SQLiteDatabase db

private void crearBaseDatos() db = openOrCreateDatabase(NOMBRE_BASE_DATOS ContextMODE_PRIVATE

null)dbexecSQL(CREAR_BASE_DATOS)

NotaAunque no es necesariamente obligatorio es recomendable que cada tabla incluya un campo detipo clave primaria con autoincremento a modo de iacutendice para cada fila En el caso en el que sedesee compartir la base de datos mediante un proveedor de contenidos un campo uacutenico de estetipo es obligatorio

136 Realizar una consulta

El resultado de cualquier consulta a una base de datos seraacute un objeto de la clase CursorEsto permite a Android manejar los recursos de manera maacutes eficiente de tal forma que enlugar de devolver todos los resultados eacutestos se van proporcionando conforme senecesitan

Para realizar una consulta a una base de datos haremos uso del meacutetodo query de lainstancia correspondiente de la clase SQLiteDatabase Los paraacutemetros que requiere este

Persistencia

11Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

meacutetodo son los siguientes

bull Un booleano opcional que permite indicar si el resultado deberiacutea contener tan soacutelovalores uacutenicos

bull El nombre de la tabla sobre la que realizar la consultabull Un array de cadenas que contenta un listado de las columnas que deben incluirse en el

resultadobull Una claacuteusula where que defina las filas que se deben obtener de la tabla Se puede

utilizar el comodiacuten el cual seraacute reemplazado por los valores que se pasen a traveacutesdel siguiente paraacutemetro

bull Un array de cadenas correspondientes a argumentos de seleccioacuten que reemplazaraacutenlos siacutembolos en la claacuteusula where

bull Una claacuteusula group by que define coacutemo se agruparaacuten las filas obtenidas comoresultado

bull Un filtro having que indique que grupos incluir en el resultado en el caso en el que sehaya hecho uso del paraacutemetro anterior

bull Una cadena que describa la ordenacioacuten que se llevaraacute a cabo sobre las filas obtenidascomo resultado

bull Una cadena opcional para definir un liacutemite en el nuacutemero de resultados a devolver

NotaComo se puede observar la mayoriacutea de estos paraacutemetros hacen referencia al lenguaje SQLTratar este tema queda fuera de los objetivos de este curso por lo que en nuestros ejemplos ledaremos en la mayoriacutea de las ocasiones un valor null a estos paraacutemetros

El siguiente coacutedigo muestra un ejemplo de consultas utilizando diversos paraacutemetros

Devolver los valores de las columnas uno y tres para todas las filas sin duplicadosString[] columnas = new String[] CLAVE_ID CLAVE_COL1 CLAVE_COL3

Cursor todasFilas = dbquery(true TABLA_BASE_DATOS columnas null nullnull null

null null)

Devolvemos los valores de todas las columnas para aquellas filas cuyovalor de la columna 3 sea igual al de una determinada variable Ordenamos las filasseguacuten el valor de la columna 5 Como queremos los valores de todas las columnas le damos al paraacutemetro correspondiente a las columnas a devolver el valor null En este caso no se ha hecho uso del primer paraacutemetro booleano que esoptativoString where = CLAVE_COL3 + = + valorString orden = CLAVE_COL5Cursor miResultado = dbquery(TABLA_BASE_DATOS null where null nullnull orden)

137 Extraer resultados de un cursor

Persistencia

12Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El primer paso para obtener resultados de un cursor seraacute desplazarnos a la posicioacuten apartir de la cual queremos obtener los datos usando cualquiera de los meacutetodosespecificados anteriormente (como por ejemplo moveToFirst o moveToPosition) Unavez hecho esto hacemos uso de meacutetodos get[TIPO] pasando como paraacutemetro el iacutendice dela columna Esto tendraacute como resultado la devolucioacuten del valor para dicha columna de lafila actualmente apuntada por el cursor

String valor = miResultadogetString(indiceColumna)

A continuacioacuten podemos ver un ejemplo maacutes completo en el que se va iterando a lo largode los resultados apuntados por un cursor extrayendo y sumando los valores de unacolumna de flotantes

int COLUMNA_VALORES_FLOTANTES = 2Cursor miResultado = dbquery(Tabla null null null null nullnull)float total = 0

Nos aseguramos de que se haya devuelto al menos una filaif (miResultadomoveToFirst())

Iteramos sobre el cursordo

float valor =miResultadogetFloat(COLUMNA_VALORES_FLOTANTES)

total += valor while (miResultadomoveToNext())

float media = total miResultadogetCount()

NotaDebido a que las columnas de las bases de datos en SQLite son debilmente tipadas podemosrealizar castings cuando sea necesario Por ejemplo los valores guardados como flotantes en labase de datos podriacutean leerse maacutes adelante como cadenas

138 Antildeadir actualizar y borrar filas

La clase SQLiteDatabase proporciona los meacutetodos insert delete y update queencapsulan las instrucciones SQL requeridas para llevar a cabo estas acciones Ademaacutesdisponemos de la posibilidad de utilizar el meacutetodo execSQL el cual nos permite ejecutarcualquier sentencia SQL vaacutelida en nuestras tablas de la base de datos en el caso en el quequeramos llevar a cabo estas (u otras) operaciones manualmente

NotaCada vez que modifiques los valores de la base de datos puedes actualizar un cursor que se puedaver afectado por medio del meacutetodo refreshQuery de la clase Cursor

Para insertar una nueva fila es necesario construir un objeto de la clase ContentValues

y usar su meacutetodo put para proporcionar valor a cada una de sus columnas Para insertar la

Persistencia

13Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

nueva fila pasaremos como paraacutemetro este objeto del tipo ContentValues al meacutetodoinsert de la instancia de la base de datos junto por supuesto con el nombre de la tablaA continuacioacuten se muestra un ejemplo

Crear la nueva filaContentValues nuevaFila = new ContentValues()

Asignamos valores a cada columnanuevaFilaput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas ]

Insertar la nueva fila en la tabladbinsert(TABLA_BASE_DATOS null nuevaFila)

NotaLos ficheros como imaacutegenes o ficheros de audio no se suelen almacenar dentro de tablas en unabase de datos En estos casos se suele optar por almacenar en la base de datos una cadena con laruta al fichero o una URI

La operacioacuten de actualizar una fila tambieacuten puede ser llevada a cabo por medio del usode la clase ContentValues En este caso deberemos llamar al meacutetodo update de lainstancia de la base de datos pasando como paraacutemetro el nombre de la tabla el objetoContentValues y una claacuteusula where que especifique la fila o filas a actualizar tal comose puede ver en el siguiente ejemplo

Creamos el objeto utilizado para definir el contenido a actualizarContentValues valoresActualizar = new ContentValues()

Asignamos valores a las columnas correspondientesvaloresActualizarput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas a actualizar ]

String where = CLAVE_ID + = + idFila

Actualizamos la fila con el iacutendice especificado en la instruccioacutenanteriordbupdate(TABLA_BASE_DATOS valoresActualizar where null)

Por uacuteltimo para borrar una fila simplemente llamaremos al meacutetodo delete de lainstancia de la base de datos especificando en este caso el nombre de la tabla que se veraacuteafectada por la operacioacuten y una claacuteusula where que utilizaremos para especificar las filasque queremos borrar Un ejemplo podriacutea ser el siguiente

dbdelete(TABLA_BASE_DATOS CLAVE_ID + = + idFila null)

Persistencia

14Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

2 Ejercicios - Persistencia en Android ficheros y SQLite

21 Uso de ficheros (05 puntos)

En este ejercicio vamos a crear una aplicacioacuten que muestre un listado de cadenas porpantalla Estas cadenas se almacenaraacuten en un fichero de texto privado para la aplicacioacutenPodremos antildeadir nuevas cadenas a partir de un cuadro de edicioacuten presente en la interfazPartiremos del proyecto Ficheros proporcionado en las plantillas de la aplicacioacuten Debesseguir los siguientes pasos

bull Antildeadir un manejador al botoacuten de la actividad principal para que cada vez que seapulsado se guarde en un fichero de texto El fichero se llamaraacute ficherotxt Al abrirel fichero para escritura se utilizaraacute el paraacutemetro ContextMODE_APPEND con lo quecada nueva cadena se antildeadiraacute al final del fichero en el caso en el que eacuteste ya existieraPara escribir en el fichero utilizaremos el meacutetodo writeBytes del objetoDataOutputStream correspondiente

bull Al pulsar el botoacuten tambieacuten se deberaacute borrar el contenido del elemento EditText (leasignamos a la vista una cadena vaciacutea con el meacutetodo setText)

bull Ejecutamos la aplicacioacuten e introducimos algunas liacuteneas en el fichero Vamos acomprobar ahora que todo ha funcionado correctamente Para ello accedemos alsistema de ficheros de nuestro dispositivo ejecutando el comando adb shell

dentro de la carpeta platform-tools de nuestra carpeta de instalacioacuten del SDK deAndroid

bull Comprueba que se ha creado el fichero ficherotxt en la carpetadatadataesuajtechandroidficherosfiles Examina su contenido ejecutando cat

ficherotxt Si algo no ha salido bien siempre podraacutes eliminar el fichero con rm

ficherotxtbull En la interfaz de la actividad se ha incluido una vista de tipo TextView debajo del

botoacuten Antildeade un manejador para dicha vista de tal forma que cada vez que se hagaclick sobre ella se muestre el contenido del fichero ficherotxt Para ello leeremos elfichero liacutenea a liacutenea antildeadiendo cada una al TextView por medio del meacutetodo append

Persistencia

15Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Interfaz de la aplicacioacuten Ficheros

22 Persistencia con ficheros (05 puntos)

Seguramente habraacutes observado que si abandonas la aplicacioacuten anterior (pulsando el botoacutenBACK del dispositivo) al volver a ejecutarla el contenido del TextView ha desaparecidoDebes volver a pulsar sobre la vista para que se vuelva a mostrar el contenido del ficheroHaz las modificaciones pertinentes para que esto no sea asiacute es decir para que cuandovuelva a mostrarse la actividad tras haber sido eliminada de la memoria se muestre elTextView tal cual se veiacutea antes de abandonar la aplicacioacuten

AvisoEn este ejercicio no se estaacute pidiendo que cargues el contenido del fichero en el TextView nadamaacutes arrancar la aplicacioacuten Puede darse el caso de que lo que esteacute mostrando el TextViewcuando la actividad sea eliminada de memoria no se corresponda con el contenido actualizado delfichero

NotaPara poder completar el ejercicio deberaacutes repasar los manejadores de evento de la primera sesioacutendel moacutedulo de Android relacionados con el ciclo de vida de ejecucioacuten de actividades

23 Base de datos SQLiteOpenHelper (05 puntos)

En las plantillas de la aplicacioacuten se incluye la aplicacioacuten BaseDatos En el proyecto de laaplicacioacuten se incluye el esqueleto de una clase MiAdaptadorBD que tendremos quecompletar Se trata de un patroacuten que nos va a permitir acceder a una base de datos de

Persistencia

16Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

usuarios dentro de la aplicacioacuten sin necesidad de hacer uso de coacutedigo SQL

La clase MiAdaptadorBD incluye a su vez otro patroacuten en este caso implementado comouna subclase de SQLiteOpenHelper Eacuteste nos obliga a definir queacute ocurre cuando la basede datos todaviacutea no existe y debe ser creada y queacute ocurre si ya existe pero debe seractualizada porque ha cambiado de versioacuten Asiacute el SQLiteOpenHelper queimplementemos en este caso MiOpenHelper nos devolveraacute siempre una base de datosseparaacutendonos de la loacutegica encargada de comprobar si la base de datos existe o no

En este primer ejercicio se pide hacer lo siguiente

bull Ejecutar la sentencia de creacioacuten de bases de datos (la tenemos declarada comoconstante de la clase) en el meacutetodo MiOpenHelperonCreate

bull Implementar tambieacuten el meacutetodo onUpgrade Idealmente eacuteste deberiacutea portar las tablasde la versioacuten antigua a la versioacuten nueva copiando todos los datos Nosotros vamos aeliminar directamente la tabla que tenemos con la sentencia SQL DROP TABLE IF

EXISTS + NOMBRE_TABLA y volveremos a crearlabull En el constructor de MiAdaptadorBD debemos obtener en el campo db la base de

datos utilizando MiOpenHelper

24 Base de datos insercioacuten y borrado (05 puntos)

Continuamos trabajando con el proyecto anterior En este ejercicio completaremos elcoacutedigo relacionado con las sentencias de insercioacuten y borrado Para realizar la insercioacutenvamos a hacer uso de un mecanismo que no hemos visto en la sesioacuten de teoriacutea y queconsiste en utilizar una sentencia de insercioacuten SQL precompilada que se completaraacute conlos valores concretos a insertar en la base de datos justo antes de realizar dicha insercioacutenLa ventaja de las sentencias compiladas es que evitan que se produzcan ataques deinyeccioacuten de coacutedigo

Como se puede observar se ha incluido un atributo a la clase que representa la insercioacutenSQL

private static final String INSERT = insert into + NOMBRE_TABLA +(+COLUMNAS[1]+) values ()

En esta sentencia no se indica ninguacuten valor concreto para la columna nombre En el lugaren el que deberiacutean aparecer los valores de dicho campo se ha escrito simplemente unsiacutembolo de interrogacioacuten Tambieacuten se ha antildeadido una instancia de la claseSQLiteStatement como parte de los atributos de la clase

private SQLiteStatement insertStatement

Realizamos los siguientes pasos

bull En el constructor de la clase MiAdaptadorBD llevamos a cabo la compilacioacuten de lasentencia

thisinsertStatement = thisdbcompileStatement(INSERT)

Persistencia

17Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Una vez hecho esto cada vez que deseemos insertar un nuevo usuario en la base dedatos deberemos dar valores concretos a la columna nombre y ejecutar la sentenciaPara ello debemos antildeadir el siguiente coacutedigo dentro del meacutetodo insert de la claseMiAdaptadorBD

thisinsertStatementbindString(1 nombreUsuario)return thisinsertStatementexecuteInsert()

bull Por uacuteltimo completamos el coacutedigo del meacutetodo deleteAll cuyo contenido es eliminara todos los usuarios de la base de datos Para que ademaacutes se devuelva el nuacutemero defilas afectadas podemos insertar la siguiente liacutenea en dicho meacutetodo

return dbdelete(NOMBRE_TABLA null null)

Una vez hechos todos estos cambios podremos utilizar la clase MiAdaptadorBD parahacer operaciones con la base de datos de usuarios de manera transparente

25 Base de datos probar nuestro adaptador (05 puntos)

Antildeade coacutedigo en la actividad Main para eliminar todos los usuarios de la base de datosantildeadir dos cualesquiera y listarlos por medio de la vista de tipo TextView que seencuentra en el layout de dicha actividad

Podemos comprobar mediante la liacutenea de comandos (comando adb shell) que la basede datos ha sido creada Para ello puede ser uacutetil hacer uso de los siguientes comandos

cd datadataesuajtechandroidbasedatosdatabasessqlite3 misusuariosdbsqlitegt schemasqlitegt tablessqlitegt select from usuarios

26 Base de datos cambios en la base de datos (05 puntos)

Ahora vamos a cambiar en la clase MiAdaptadorBD el nombre de la segunda columnaque en lugar de nombre se va a llamar nombres Ejecutamos la aplicacioacuten y comprobamosque sale con una excepcioacuten Comprueba por medio de LogCat cuaacutel ha sido el erroriquestCoacutemo lo podemos solucionar

NotaPista conforme hemos programado la clase MiAdaptadorBD y siguiendo el patroacuten de disentildeode SQLiteOpenHelper es posible arreglar el problema simplemente modificando el valor deun campo

Persistencia

18Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

3 Persistencia en Android proveedores de contenidos ySharedPreferences

31 Shared Preferences

Comenzamos esta sesioacuten hablando de una de las teacutecnicas maacutes simples de persistencia enAndroid junto al uso de ficheros Shared Preferences Se trata de un mecanismo ligeropara almacenar datos basado en pares clave-valor utilizado normalmente para guardarpreferencias u opciones de la aplicacioacuten Tambieacuten puede ser utilizado para almacenar elestado de la interfaz graacutefica para contemplar el caso de que nuestra actividad debafinalizar abruptamente su ejecucioacuten Otro posible uso es el de compartir informacioacutenentre componentes de una misma aplicacioacuten

NotaLos Shared Preferences nunca son compartidos entre diferentes aplicaciones

Este meacutetodo se basa en el uso de la clase SharedPreferences Es posible utilizarla paraguardar datos en muy diversos formatos booleanos cadenas flotantes y enteros

311 Guardar Shared Preferences

Para crear o modificar un Shared Preference llamamos al meacutetodogetSharedPreferences del contexto de la aplicacioacuten pasando como paraacutemetro elidentificador del conjunto de valores de preferencias con el que queremos trabajar Pararealizar la modificacioacuten del valor de un Shared Preference hacemos uso de la claseSharedPreferencesEditor Para obtener el objeto editor invocamos el meacutetodo edit

del objeto Shared Preferences que queremos modificar Los cambios se guardan medianteel meacutetodo commit Veamos un ejemplo

int modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences = getSharedPreferences(Mispreferencias modo)

Obtenemos un editor para modificar las preferenciasSharedPreferencesEditor editor = misSharedPreferencesedit()

Guardamos nuevos valores en el objeto SharedPreferenceseditorputBoolean(isTruetrue)editorputFloat(unFloat10)editorputInt(numeroEntero12)editorputLong(otroNumero2)editorputString(unaCadenavalor)

Guardamos los cambioseditorcommit()

Persistencia

19Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

312 Leer Shared Preferences

Para leer Shared Preferences utilizamos el meacutetodo getSharedPreferences tal como seha comentado anteriormente Pasamos como paraacutemetro el identificador del conjunto deShared Preferences al que deseamos acceder Una vez hecho esto ya podemos hacer usode meacutetodos de tipo get para acceder a preferencias individuales Hay diferentes meacutetodosget para diferentes tipos de datos Cada uno de ellos requiere como paraacutemetro la claveque identifica la preferencia cuyo valor deseamos obtener y un valor por defecto el cualse usaraacute en el caso concreto en el que no existiera la preferencia con la clave pasada comoprimer paraacutemetro Podemos ver un ejemplo a continuacioacuten

public static String MIS_PREFS = MIS_PREFS

public void cargarPreferences() Obtener las preferencias almacenadasint modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences =

getSharedPreferences(MIS_PREFS modo)

boolean isTrue = misSharedPreferencesgetBoolean(isTruefalse)float unFloat = misSharedPreferencesgetFloat(unFloat0)int numeroEntero = misSharedPreferencesgetInt(numeroEntero0)long otroNumero = misSharedPreferencesgetLong(otroNumero0)String unaCadena = misSharedPreferencesgetString(unaCadena)

313 Interfaces para Shared Preferences

Android proporciona una plataforma basada en XML para la creacioacuten de interfacesgraacuteficas para el manejo de preferencias Estas interfaces tendraacuten un aspecto similar al delas del resto de aplicaciones del sistema Al utilizarla nos estaremos asegurando de quenuestras actividades para el manejo de preferencias seraacuten consistentes con las del resto deaplicaciones del sistema De esta forma los usuarios estaraacuten familiarizados con el manejode esta parte de nuestra aplicacioacuten

La plataforma consiste en tres elementos

bull Layout de la actividad de preferencias se trata de un archivo XML que definiraacute lainterfaz graacutefica de la actividad que muestre los controles para modificar los valores delas preferencias Permite especificar las vistas a mostrar los posibles valores que sepueden introducir y a queacute clave de Shared Preferences se corresponde cada vista

bull Actividad de preferencias una subclase de PreferenceActivity que secorresponderaacute con la pantalla de preferencias de nuestra aplicacioacuten

bull Shared Preference Change Listener una implementacioacuten de la claseonSharedPreferenceChangeListener que actuaraacute en el caso en el que cambie elvalor de alguna Shared Preference

314 Definiendo una pantalla de preferencias con un layout en XML

Persistencia

20Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esta es sin duda la parte maacutes importante de una actividad de preferencias Se trata de unarchivo XML utilizado para definir varios aspectos de dicha actividad Al contrario queen el caso de los archivos de layout para interfaces graacuteficas que vimos en la sesioacutencorrespondiente del moacutedulo de Android estos archivos de layout se guardan en la carpetaresxml de los recursos de la aplicacioacuten

Aunque conceptualmente estos layouts son similares a los utilizados para definirinterfaces graacuteficas los layouts de actividades de preferencias utilizan un conjuntoespecializado de componentes especiacuteficos para este tipo de actividades Estaacuten pensadospara proporcionar interfaces con un aspecto similar al del resto de actividades depreferencias del sistema Estos controles se describen en maacutes detalle en la siguienteseccioacuten

Los layout de preferencias contendraacuten un elemento PreferenceScreen

ltxml version=10 encoding=utf-8gtltPreferenceScreen

xmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceScreengt

Se pueden antildeadir maacutes elementos de este tipo si se desea Al hacerlo eacutestos serepresentaraacuten como un elemento seleccionable en un listado que mostraraacute una nuevaventana de preferencias en caso de que sea seleccionado

Dentro de cada elemento PreferenceScreen podemos incluir tantos elementos de tipoPreferenceCategory y elementos especiacuteficos para antildeadir controles como se desee Loselementos PreferenceCategory son utilizados para dividir cada pantalla de preferenciasen subcategoriacuteas mediante una barra de tiacutetulo Su sintaxis es la siguiente

ltPreferenceCategoryandroidtitle=My Preference Categorygt

ltPreferenceCategorygt

Una vez dividida la pantalla en subcategoriacuteas tan soacutelo quedariacutea antildeadir los elementoscorrespondientes a los controles especiacuteficos que veremos en la siguiente seccioacuten Losatributos de los elementos XML para estos controles pueden variar aunque existe unconjunto de atributos comunes

bull androidkey la clave de Shared Preference que se usaraacute para guardar el valor delcontrol

bull androidtitle texto a mostrar para describir la preferenciabull androidsummary una descripcioacuten maacutes larga a mostrar debajo del texto definido con

el campo anteriorbull androiddefaultValue el valor por defecto que se mostraraacute inicialmente y tambieacuten

el que finalmente se guardaraacute si ninguacuten otro valor fue asignado a este campo

El siguiente listado muestra una pantalla de preferencias muy simple con una uacutenicacategoriacutea y un uacutenico control (un check box)

ltxml version=10 encoding=utf-8gt

Persistencia

21Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltPreferenceScreenxmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceCategory

androidtitle=Mi categoriacuteagtltCheckBoxPreference

androidkey=MI_CHECK_BOXandroidtitle=Preferencia Check Boxandroidsummary=Descripcioacuten de la preferencia

Check BoxandroiddefaultValue=true

gtltPreferenceCategorygt

ltPreferenceScreengt

Esta pantalla de preferencias se mostrariacutea de la siguiente forma

Ejemplo sencillo de ventana de preferencias

315 Controles nativos para preferencias

Android incluye diferentes controles que podemos antildeadir a nuestras ventanas depreferencias por medio de los elementos XML que se indican a continuacioacuten

bull CheckBoxPreference un check box estaacutendar que puede ser utilizado parapreferencias de tipo booleano

bull EditTextPreference permite al usuario introducir una cadena como valor para unapreferencia Al seleccionar el texto se mostraraacute un diaacutelogo con el que introducir elnuevo valor

bull ListPreference el equivalente a un Spinner Seleccionar este elemento de lainterfaz mostraraacute un diaacutelogo con las diferentes opciones que es posible seleccionar Seutilizan arrays para asociar valores y textos a las opciones de la lista

bull RingtonePreference un tipo especiacutefico de preferencia de tipo listado que permiteseleccionar entre diferentes tonos de teleacutefono Esta opcioacuten es particularmente uacutetil en

Persistencia

22Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

el caso en el que se esteacute creando una pantalla de preferencias para configurar lasopciones de notificacioacuten de una aplicacioacuten o componente de la misma

Tambieacuten es posible crear nuestros propios controles personalizados definiendo unasubclase de Preference (o cualquiera de sus subclases) Se puede encontrar maacutesinformacioacuten en la documentacioacuten de AndroidhttpdeveloperandroidcomreferenceandroidpreferencePreferencehtml

316 Actividades de preferencias

Para mostrar una pantalla de preferencias debemos definir una subclase dePreferenceActivity

public class MisPreferencias extends PreferenceActivity

La interfaz graacutefica de la interfaz se puede crear a partir del layout en el meacutetodo onCreatemediante una llamada a addPreferencesFromResource

Overridepublic void onCreate(Bundle savedInstaceState)

superonCreate(savedInstanceState)addPreferencesFromResource(Rxmlmilayout)

Como en el caso de cualquier otra actividad la actividad de preferencias que hayamosdefinido debe ser incluida en el Manifest de la aplicacioacuten

ltactivity androidname=MisPreferenciasandroidlabel=Mis preferenciasgt

ltactivitygt

Eso es todo lo que necesitamos para crear este tipo de actividades y mostrar una ventanacon las preferencias definidas en el layout Esta actividad puede ser iniciada comocualquier otra mediante un Intent ya sea a traveacutes de una llamada a startActivity obien a startActivityForResult

Intent i = new Intent(this MisPreferenciasclass)startActivityForResult(i MOSTRAR_PREFERENCIAS)

Los valores para las diferentes preferencias de la actividad son almacenadas en elcontexto de la aplicacioacuten Esto permite a cualquier componente de dicha aplicacioacutenincluyendo a cualquier actividad y servicio de la misma acceder a los valores tal como semuestra en el siguiente coacutedigo de ejemplo

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto) PENDIENTE hacer uso de meacutetodos get para obtener los valores

317 Shared Preference Change Listeners

Persistencia

23Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El uacuteltimo elemento que nos queda por examinar en esta plataforma de Shared Preferenceses la interfaz onSharedPreferenceChangeListener que es utilizada para invocar unevento cada vez que una preferencia es antildeadida eliminada o modificada Para registrarListeners usamos un coacutedigo como el que se muestra a continuacioacuten en el que la parte maacutesimportante es sin duda el meacutetodo onSharedPreferenceChanged dentro del cualdeberemos determinar queacute preferencia ha cambiado de estado a partir de sus paraacutemetrospara llevar a cabo las acciones oportunas

public class MiActividad extends Activity implementsOnSharedPreferenceChangeListener

Overridepublic void onCreate(Bundle SavedInstanceState)

Registramos este objetoOnSharedPreferenceChangeListener

Context contexto = getApplicationContext()SharedPreferences prefs =

PreferenceManagergetDefaultSharedPreferences(contexto)prefsregisterOnSharedPreferenceChangeListener(this)

public void onSharedPreferenceChanged(SharedPreferences prefsString clave)

PENDIENTE comprobar queacute clave concreta ha cambiado ysu nuevo valor

para modificar la interfaz de una actividad o elcomportamiento de

alguacuten componente

Otra forma maacutes sencilla de controlar los cambios en las preferencias es registrar unlistener en el constructor de la actividad principal o aquella que haga uso de los valores depreferencias de la forma

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto)

mListener = new OnSharedPreferenceChangeListener() public void onSharedPreferenceChanged(SharedPreferences

sharedPreferences String key)

if( keyequals(MICLAVE) )boolean valor =

sharedPreferencesgetBoolean(MICLAVE false)

prefsregisterOnSharedPreferenceChangeListener(mListener)

NotaEn este segundo ejemplo mListener es una variable miembro de la clase Para este caso esnecesario registrar el listener de esta forma porque sino el garbage collector lo eliminariacutea (debidoa particularidades en la programacioacuten de los listeners asociados a las SharedPreferences)

Persistencia

24Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

32 Proveedores de contenidos

Los proveedores de contenidos o ContentProvider proporcionan una interfaz parapublicar y consumir datos identificando la fuente de datos con una direccioacuten URI queempieza por content Son una forma maacutes estaacutendar que los adaptadores (como el quevimos en la sesioacuten anterior en la seccioacuten dedicada a SQLite) de desacoplar la capa deaplicacioacuten de la capa de datos

Podemos encontrar dos tipos principales de proveedores de contenidos en Android Losproveedores nativos son proveedores que el propio sistema nos proporciona para poderacceder a datos del dispostivo incluyendo audio viacutedeo imaacutegenes informacioacuten personaletc Por otra parte tambieacuten es posible que creemos nuestros propios proveedores con talde permitir a nuestra aplicacioacuten compartir datos con el resto del sistema En esta sesioacutenhablaremos de ambos tipos

321 Proveedores nativos

Android incluye una serie de proveedores de contenidos que nos pueden proporcionarinformacioacuten de diferentes tipos informacioacuten sobre nuestros contactos informacioacuten delcalendario e incluso ficheros multimedia Ejemplos de estos proveedores de contenidosnativos son el Browser CallLog ContactsContract MediaStore Settings y elUserDictionary Para poder hacer uso de estos proveedores de contenidos y acceder alos datos que nos proporcionan debemos antildeadir los permisos adecuados al ficheroAndroidManifestxml Por ejemplo para acceder al listiacuten telefoacutenico podemos antildeadir elpermiso correspondiente de la siguiente forma

ltuses-sdk androidminSdkVersion=8 gtltuses-permission androidname=androidpermissionREAD_CONTACTSgt

ltmanifestgt

Para obtener informacioacuten de un ContentProvider debemos hacer uso del meacutetodo query

de la clase ContentResolver El primero de los paraacutemetros de dicho meacutetodo seraacute la URIdel proveedor de contenidos al que deseamos acceder Este meacutetodo devuelve un cursorque nos permitiraacute iterar entre los resultados tal como se vio en la sesioacuten anterior en elapartado de cursores

ContentResolverquery(Uri uriString[] projectionString selectionString[] selectionArgsString sortOrder)

Por ejemplo la siguiente llamada nos proporcionaraacute un cursor que nos permitiraacute acceder atodos los contactos de nuestro teleacutefono moacutevil Para ello hemos hecho uso de la constante

Persistencia

25Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ContactsContractContactsCONTENT_URI que almacena la URI del proveedor decontenidos correspondiente Al resto de paraacutemetros se le ha asignado el valor null

ContentResolver cr = getContentResolver()Cursor cursor = crquery(ContactsContractContactsCONTENT_URI

null null null null)

NotaEn versiones anteriores a Android 20 las estructuras de datos de los contactos son diferentes y nose accede a esta URI

Una vez obtenido el cursor es posible mapear sus campos con alguacuten componente de lainterfaz graacutefica de la actividad De esta forma cualquier cambio que se produzca en elcursor se reflejaraacute automaacuteticamente en el componente graacutefico sin que sea necesario queprogramemos el coacutedigo que lo refresque Para esto tenemos que llamar al meacutetodosetNotificationUri del cursor con lo cual nos aseguraremos de que los listeners seraacutennotificados cuando se produzca alguacuten cambio en los datos referenciados por dicho cursor

cursorsetNotificationUri(crContactsContractContactsCONTENT_URI)

Para asignar un adaptador a una lista de la interfaz graacutefica de la actividad maacutesconcretamente una ListView utilizamos el meacutetodo setAdapter(Adapter)

ListView lv = (ListView)findViewById(RidListView01)SimpleCursorAdapter adapter = new SimpleCursorAdapter(

getApplicationContext()Rlayouttextviewlayoutcursornew String[]

ContactsContractContacts_IDContactsContractContactsDISPLAY_NAME

new int[]RidTextView1RidTextView2)

lvsetAdapter(adapter)

En este ejemplo los identificadores RidTextView1 y RidTextView2 se correspondencon views del layout que define cada fila de la ListView como se puede ver en elarchivo textviewlayoutxml que tendriacuteamos que crear

ltxml version=10 encoding=utf-8gt

ltLinearLayout xmlnsandroid=httpschemasandroidcomapkresandroidandroidorientation=horizontalandroidlayout_width=fill_parentandroidlayout_height=wrap_contentgt

ltTextView androidid=+idTextView1androidtextStyle=bold

Persistencia

26Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

androidems=2androidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltTextView androidid=+idTextView2androidtextStyle=boldandroidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltLinearLayoutgt

La lista en siacute (identificada por RidListView01 en el presente ejemplo) estariacutea en otroXML layout por ejemplo en mainxml

Se debe tener en cuenta que en el caso de este ejemplo si quisieacuteramos obtener losnuacutemeros de teleacutefono de cada uno de nuestros contactos tendriacuteamos que recorrer el cursorobtenido a partir del proveedor de contenidos y por cada persona en nuestra agenda crearun nuevo cursor que recorriera los teleacutefonos ya que una persona puede tener asignadosvarios teleacutefonos

Puedes acceder a un listado completo de los proveedores de contenidos nativos existentesen Android consultando su manual de desarrollo Concretamente leyendo la seccioacutendedicada al paquete androidprovider enhttpdeveloperandroidcomreferenceandroidproviderpackage-summaryhtml

322 Proveedores propios crear un nuevo proveedor de contenidos

Para acceder a nuestras fuentes de datos propias de manera estaacutendar nos interesaimplementar nuestros propios ContentProvider Gracias a que proporcionamos nuestrosdatos a partir de una URI quien use nuestro proveedor de contenidos no deberaacutepreocuparse de doacutende provienen los datos indicados por dicha URI podriacutean provenir deficheros locales en la tarjeta de memoria de un servidor en Internet o de una base dedatos SQLite Dada una URI nuestro proveedor de contenidos deberaacute proporcionar en suinterfaz los meacutetodos necesarios para realizar las operaciones baacutesicas en una base de datosinsercioacuten lectura actualizacioacuten y borrado

Para crear un nuevo proveedor de contenidos definimos una subclase deContentProvider Utilizaremos una sobrecarga del meacutetodo onCreate para crear einicializar la fuente de datos que queramos hacer puacuteblica a traveacutes de dicho proveedor Unesqueleto de subclase de ContentProvide podriacutea ser el siguiente

public class MiProveedor extends ContentProvider

Overridepublic boolean onCreate()

Inicializar la base de datosreturn true

Dentro de la clase deberemos crear un atributo puacuteblico y estaacutetico de nombreCONTENT_URI en el cual almacenaremos el URI completo del proveedor que estamos

Persistencia

27Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creando Este URI debe ser uacutenico por lo que una buena idea puede ser tomar como baseel nombre de paquete de nuestra aplicacioacuten La forma general de un URI es la siguiente

content[NOMBRE_PAQUETE]provider[NOMBRE_APLICACION][RUTA_DATOS]

Por ejemplo

contentesuajtechandroidprovidermiaplicacionelementos

En general (y por simplicidad) no es necesario antildeadir la cadena provider al nombre delpaquete por lo que podemos utilizar directamente el nombre del paquete seguido de laruta de datos de la forma

contentesuajtechandroidmiaplicacionelementos

Estos URIs se pueden presentar en dos formas La URI anterior representa una consultadirigida a obtener todos los valores de ese tipo (en este caso todos los elementos) Si a laURI anterior antildeadimos un sufijo [NUMERO_FILA] estamos representando una consultadestinada a obtener un uacutenico elemento (por ejemplo el quinto elemento en el siguienteejemplo)

contentesuajtechandroidprovidermiaplicacionelementos5

NotaSe considera una buena praacutectica de programacioacuten el proporcionar acceso a nuestro proveedor decontenidos utilizando cualquiera de estas dos formas

La forma maacutes simple de determinar cuaacutel de las dos formas anteriores de URI se utilizoacutepara hacer una peticioacuten a nuestro proveedor de contenidos es mediante un Uri MatcherCrearemos y configuramos un elemento Uri Matcher para analizar una URI y determinara cuaacutel de las dos formas se corresponde En el siguiente coacutedigo se muestra el coacutedigobaacutesico que deberemos emplear para implementar este patroacuten

public class MiProveedor extends ContentProvider

private static final String miURI =contentesuajtechandroidprovidermiaplicacionelementos

public static final Uri CONTENT_URI = Uriparse(miUri)

Creamos las constantes que nos van a permitir diferenciar entre los dos tipos distintos de peticiones a partir de la URIprivate static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

Inicializamos el objeto UriMatcher Una URI que termine en elementos se corresponderaacute con una consulta para todas las filas mientras que una URI con el sufijo elementos[FILA] representaraacute una uacutenica filastatic

uriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidprovidermiaplicacion

elementos TODAS_FILAS)

Persistencia

28Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

uriMatcheraddURI(esuajtechandroidprovidermiaplicacionelementos UNA_FILA)

Overridepublic boolean onCreate()

PENDIENTE inicializar la base de datosreturn true

Esta misma teacutecnica se puede emplear para proporcionar diferentes alternativas de URIpara diferentes subconjuntos de datos como por ejemplo diferentes tablas en una base dedatos a traveacutes del mismo proveedor de contenidos

323 Proveedores propios crear la interfaz de consultas

Para permitir realizar operaciones con los datos publicados a traveacutes de nuestro proveedorde contenidos deberemos implementar los meacutetodos delete insert update y query

para el borrado insercioacuten actualizacioacuten y realizacioacuten de consultas respectivamenteEstos meacutetodos son la interfaz estaacutendar utilizada con proveedores de contenidos de formaque para compartir datos entre aplicaciones no se tendraacuten interfaces distintos paradiferentes fuentes de datos

Lo maacutes habitual suele ser implementar un proveedor de contenidos como un mecanismopara permitir el acceso a una base de datos SQLite privada a una aplicacioacuten aunque atraveacutes de estos meacutetodos indicados se podriacutea en principio acceder a cualquier fuente dedatos

El siguiente coacutedigo extiende el ejemplo anterior (MiProveedor) y muestra el esqueleto deestos meacutetodos dentro de una subclase de ContentProvider Obseacutervese que se hace usodel objeto UriMatcher para determinar que tipo de consulta se estaacute realizando

Overridepublic Cursor query(Uri uri

String[] projectionString selectionString[] selectionArgsString sort)

Si se trata de una consulta para obtener una uacutenica filalimitamos

los resultados a obtener de la fuente de datos por medio de una claacuteusula whereswitch(UriMatchermatch(uri))

case UNA_FILA PENDIENTE modifica la sentencia SELECT

mediante una claacuteusula where obteniendo el identificador de fila

como numeroFila = urigetPathSegments()get(1))

return null

Override

Persistencia

29Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public Uri insert(Uri _uri ContentValues _valores) long idFila = [ Antildeadir un nuevo elemento ]

Devolvemos la URI del elemento recieacuten antildeadidoif (idFila gt 0)

return ContentUriswithAppendendId(CONTENT_URI idFila)

throw new SQLException(No se pudo antildeadir un nuevo elemento a +_uri)

Overridepublic int delete(Uri uri String where String[] whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

Overridepublic int update(Uri uri ContentValues valores String where String[]whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

324 Proveedores propios tipo MIME

El uacuteltimo paso para crear un proveedor de contenidos es definir el tipo MIME queidentifica el tipo de datos que devuelve el proveedor Debemos sobrecargar el meacutetodogetType para que devuelva una cadena que identifique nuestro tipo de datos de manerauacutenica Se deberiacutean definir dos tipos posibles uno para el caso de una uacutenica fila y otro parael caso de devolver todos los resultados Se debe utilizar una sintaxis similar a la delsiguiente ejemplo

bull Un uacutenico elementovndesuajtechandroidmiaplicacioncursoritemmiproveedorcontent

bull Todos los elementosvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

Como se puede observar la cadena comienza siempre por vnd A continuacioacutentendriacuteamos el nombre del paquete de nuestra aplicacioacuten Lo siguiente es cursor ya quenuestro proveedor de contenidos estaacute devolviendo datos de tipo Cursor Justo antes de labarra pondremos item en el caso del tipo MIME para un uacutenico elemento y dir para elcaso del tipo MIME para todos los elementos Finalmente tras la barra pondremos elnombre de nuestra clase (en minuacutesculas) seguido de content En el siguiente coacutedigo deejemplo se muestra coacutemo sobrecargar getType seguacuten la URI

Persistencia

30Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Overridepublic String getType(Uri _uri)

switch (uriMatchermatch(_uri)) case TODAS_FILAS

returnvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

case UNA_FILAreturn

vndesuajtechandroidmiaplicacioncursoritemmiproveedorcontentdefault

throw new IllegalArgumentException(URI nosoportada + _uri)

325 Proveedores propios registrar el proveedor

No debemos olvidar incorporar nuestro proveedor de contenidos al Manifest de laaplicacioacuten una vez que lo hemos completado Esto se haraacute por medio del elementoprovider cuyo atributo authorities podremos utilizar para especificar la URI base talcomo se puede ver en el siguiente ejemplo

ltprovider androidname=MiProveedorandroidauthorities=esuajtechandroidmiaplicaciongt

326 Content Resolvers

Aunque en la seccioacuten de proveedores nativos ya se ha dado alguacuten ejemplo de coacutemo haceruso de un Content Resolver para realizar una consulta ahora que hemos podido observaren detalle la estructura de un proveedor de contenidos propio es un buen momento paraver en detalle coacutemo realizar cada una de las operaciones que eacutestos permiten

El contexto de una aplicacioacuten incluye siempre una instancia de la claseContentResolver a la cual se puede acceder mediante el meacutetodo getContentResolver

ContentResolver cr = getContentResolver()

Esta clase incorpora diversos meacutetodos para realizar consultas a proveedores decontenidos Cada uno de estos meacutetodos acepta como paraacutemetro una URI que indica conqueacute proveedor de contenidos se desea interactuar

Las consultas realizadas sobre un proveedor de contenidos recuerdan a las consultasrealizadas sobre bases de datos Los resultados de las consultas se devuelven comoobjetos de la clase Cursor Se pueden extraer valores del cursor de la misma forma en laque se explicoacute en el apartado de bases de datos de la sesioacuten anterior El meacutetodo query dela clase ContentResolver acepta los siguientes paraacutemetros

bull La URI del proveedor del contenidos al que se desea realizar la consultabull Una proyeccioacuten que enumera las columnas que se quieren incluir en los resultados

obtenidosbull Una claacuteusula where que define las filas a obtener Se pueden utilizar comodines que

Persistencia

31Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

se reemplazaraacuten por valores incluidos en el siguiente paraacutemetrobull Una matriz de cadenas que se pueden utilizar para reemplazar los comodines en la

claacuteusula where anteriorbull Una cadena que describa la ordenacioacuten de los elementos devueltos

El siguiente coacutedigo muestra un ejemplo

ContentResolver cr = getContentResolver() Devolver todas las filasCursor todasFilas = crquery(MiProveedorCONTENT_URI null null nullnull) Devolver todas columnas de las filas para las que la columna 3 tiene un valor determinado ordenadas seguacuten el valor de la columna 5String where = COL3 + = + valorString orden = COL5Cursor algunasFilas = crquery(MiProveedorCONTENT_URI null where nullorden)

327 Otras operaciones con Content Resolvers

Para realizar otro tipo de operaciones con los datos de un proveedor de contenidosharemos uso de los meacutetodos delete update e insert de un objeto ContentResolver

Con respecto a la insercioacuten la clase ContentResolver proporciona en su interfaz dosmeacutetodos diferentes insert y bulkInsert Ambos aceptan como paraacutemetro la URI delproveedor de contenidos pero mientras que el primer meacutetodo tan solo toma comoparaacutemetro un objeto de la clase ContentValues (ya vistos en la seccioacuten de bases dedatos) el segundo toma como paraacutemetro una matriz de elementos de este tipo El meacutetodoinsert devolveraacute la URI del elemento recieacuten antildeadido mientras que bulkInsert

devolveraacute el nuacutemero de filas correctamente insertadas

Obtener el Content ResolverContentResolver cr = getContentResolver()

Crear una nueva fila de valores a insertarContentValues nuevosValores = new ContentValues()

Asignar valores a cada columnanuevosValoresput(NOMBRE_COLUMNA nuevoValor)[ Repetir para cada columna ]

Uri miFilaUri = crinsert(MiProveedorCONTENT_URI nuevosValores)

Insertamos ahora una matriz de nuevas filasContentValues[] arrayValores = new ContentValues[5] PENDIENTE rellenar el array con los valores correspondientesint count = crbulkInsert(MiProveedorCONTENT_URI arrayValores)

Para el caso del borrado hacemos uso del meacutetodo delete del Content Resolver pasandocomo paraacutemetro la URI de la fila que deseamos eliminar de la fuente de datos Otraposibilidad es incluir una claacuteusula where para eliminar muacuteltiples filas Ambas teacutecnicas semuestran en el siguiente ejemplo

ContentResolver cr = getContentResolver()

Persistencia

32Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Eliminar una fila especiacuteficacrdelete(miFilaUri null null) Eliminar las primeras cinco filasString where = _id lt 5crdelete(MiProveedorCONTENT_URI where null)

Una actualizacioacuten se lleva a cabo mediante el meacutetodo update del objeto ContentResolver Este meacutetodo toma como paraacutemetro la URI del proveedor de contenidosobjetivo un objeto de la clase ContentValues que empareja nombres de columna convalores actualizados y una claacuteusula where que indica queacute filas deben ser actualizadas Elresultado de la actualizacioacuten seraacute que en cada fila en la que se cumpla la condicioacuten de laclaacuteusula where se cambiaraacuten los valores de las columnas especificados por el objeto de laclase ContentValues tambieacuten se devolveraacute el nuacutemero de filas correctamenteactualizadas A continuacioacuten se muestra un ejemplo

ContentValues nuevosValores = new ContentValues()

Utilizamos el objeto ContentValues para especificar en queacute columnas queremos que se produzca un cambio y cuaacutel debe ser el nuevo valor de dichas columnasnuevosValuesput(NOMBRE_COLUMNA nuevoValor)

Aplicamos los cambios a las primeras cinco filasString where = _id lt 5

getContentResolver()update(MiProveedorCONTENT_URI nuevosValores wherenull)

Persistencia

33Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

4 Ejercicios - Persistencia en Android proveedores de contenidos ySharedPreferences

41 Compartir datos entre actividades con Shared Preferences (075 puntos)

Descarga de las plantillas el proyecto Dni Dicho proyecto estaacute compuesto de dosactividades Formulario y Resumen El objetivo del ejercicio es conseguir que al pulsar elbotoacuten Siguiente en la actividad Formulario se muestre en la actividad Resumen cuaacutelesfueron los datos introducidos

En este ejercicio vamos a hacer uso de SharedPreferences para pasar los datos de unaactividad a la siguiente Al pulsar en Siguiente en Formulario deberemos guardar el valorde todos los campos mediante esta plataforma Al mostrarse la actividad Resumendeberemos leer estos datos tambieacuten a partir de SharedPreferences para mostrarlos porpantalla

Por uacuteltimo crea un meacutetodo que valide el DNI (debe tratarse de una secuencia de ochodiacutegitos entre 0 y 9 y una letra al final) Si se pulsa Siguiente y el DNI no tiene el formatocorrecto no se deberaacute pasar a la actividad Resumen En lugar de esto se mostraraacute unToast en pantalla con un mensaje de error

42 Actividad de preferencias (075 puntos)

En este ejercicio seguimos trabajando con el proyecto del ejercicio anterior Vamos aantildeadir un menuacute de opciones que permita escoger en queacute momento se comprobaraacute lavalidez del DNI Para ello antildeade en primer lugar un menuacute a la actividad Formulario conuna uacutenica opcioacuten cuyo nombre seraacute Opciones

Al seleccionar esta opcioacuten se deberaacute mostrar una actividad de preferencias cuyo aspectoseraacute el siguiente

Persistencia

34Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Preferencias de la aplicacioacuten Dni

El significado de las opciones seraacute el siguiente

bull En el campo con esta opcioacuten seleccionada al pulsar sobre el campo de DNI en elformulario deberaacute mostrarse un Toast indicando si la sintaxis del DNI es correcta ono

bull Al pulsar si se desactiva esta opcioacuten (que deberaacute estar activada por defecto) no seharaacute la comprobacioacuten del DNI al pulsar el botoacuten Siguiente por lo que siempre seraacuteposible pasar de la actividad Formulario a la actividad Resumen sea cual sea elformato del DNI introducido

bull El uacuteltimo check box se encontraraacute siempre deshabilitado iquestQueacute atributo debemosutilizar para conseguir esto

Crea dos variables booleanas llamadas enElCampo y alPulsar Su valor dependeraacute delestado de los checkboxes anteriores y se actualizaraacute por medio del eventoonSharedPreferenceChange Utiliza el valor de estas variables en el coacutedigo de tuactividad para que esta tenga el comportamiento indicado

43 Proveedor de contenidos propio (075 puntos)

Vamos a implementar otra forma de acceder a la base de datos de usuarios de laaplicacioacuten BaseDatos desarrollada durante los ejercicios de la sesioacuten anterior siguiendoesta vez el patroacuten de disentildeo ContentProvider de Android Seguiremos trabajando puescon dicho proyecto

bull Creamos una nueva clase llamada UsuariosProvider que herede deContentProvider Esto nos obligaraacute a sobrecargar una serie de meacutetodos abstractosAntes de implementar la query vamos a configurar el provider

bull Antildeadimos algunos campos tiacutepicos de los content provider

Persistencia

35Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public static final Uri CONTENT_URI =Uriparse(contentesuajtechandroidbasedatosusuarios)

private static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

staticuriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

TODAS_FILAS)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

UNA_FILA)

bull Vamos a acceder a la misma base de datos de usuarios utilizada en los ejercicios de lasesioacuten anterior pero no vamos a hacerlo a traveacutes del adaptador que tuvimos queimplementar sino que vamos a copiar de eacutel el coacutedigo que nos haga falta Copia loscampos que definen el nombre de la base de datos de la tabla de las columnas laversioacuten asiacute como la referencia al contexto y a la base de datos La sentenciacompilada del insert ya no va a hacer falta Inicializa los valores necesarios en elconstructor Para inicializar la referencia a la base de datos vamos a utilizar una vezmaacutes MiOpenHelper Podemos copiarlo del adaptador que ya implementamos en losejercicios de la sesioacuten anterior

bull Implementa de forma apropiada el getType para devolver un tipo MIME diferenteseguacuten si se trata de una URI de una fila o de todas las filas Para ello ayuacutedate deluriMatcher

bull Implementa el meacutetodo query Simplemente se trata de devolver el cursor queobtenemos al hacer una consulta a la base de datos SQLite Algunos de los paraacutemetrosque le pasaremos los recibimos como paraacutemetros del meacutetodo del provider Los que notengamos iraacuten con valor null

bull Aunque no tenemos todos los meacutetodos del UsuariosProvider implementadospodemos probarlo Para ello debemos registarlo en el AndroidManifestxml

ltprovider androidname=UsuariosProvider

androidauthorities=esuajtechandroidbasedatosgtltapplicationgt

ltmanifestgt

bull En la clase Main se antildeadioacute codigo para insertar una serie de valores en la base dedatos y mostrarlos en el campo de texto Manteniendo este coacutedigo vamos a antildeadir alcampo de texto el resultado obtenido con la consulta del UsuariosProvider paracomprobar que tanto el adaptador de la base de datos como el proveedor nosdevuelven el mismo resultado

44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)

Porque es la forma estaacutendar que establece Android de acceder a contenidos Ademaacutes elproveedor de contenidos nos permitiraacute notificar al ContentResolver de los cambiosocurridos Asiacute componentes en la pantalla podraacuten refrescarse de forma automaacutetica

Persistencia

36Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Utiliza el proyecto ProveedorContenidos de las plantillas Implementa la insercioacuten enel proveedor de contenidos Prueacutebala insertando algunos usuarios de ejemplo en laclase Main Implementa tambieacuten el OnClickListener del botoacuten que inserta nuevosusuarios El nombre del nuevo usuario iraacute indicado en el EditText

bull Comprueba que la insercioacuten funciona y que gracias a la siguiente liacutenea y al estarusando un proveedor de contenidos la lista se actualiza automaacuteticamente cuandoocurre alguacuten cambio sin necesidad de pedir expliacutecitamente la actualizacioacuten al pulsarel botoacuten

cursorsetNotificationUri(cr UsuariosProviderCONTENT_URI)

NotaEsto no va a funcionar si se notifica que se ha producido un cambio al insertar o borrar unelemento Para ello usamosgetContext()getContentResolver()notifyChange(UsuariosProviderCONTENT_URI null)

bull Implementa el meacutetodo delete del proveedor de contenidos Prueacutebalo en la claseMain Termina de implementar el onCreateContextMenuListener que se ejecutaraacutecada vez que se haga una pulsacioacuten larga sobre alguna entrada de la lista Compruebaque funciona (eliminando el usuario correspondiente y no otro)

Persistencia

37Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

5 Persistencia de datos en iOS Ficheros y SQLite

51 Introduccioacuten

La persistencia de datos en iOS se realiza en una pequentildea memoria flash que tienenuestro dispositivo la cual es equivalente a un disco duro de tamantildeo limitado Los datosque ahiacute se almacenen se conservaraacuten aunque el dispositivo se apague Por motivos deseguridad el SO de iOS no permite que las aplicaciones accedan directamente a lamemoria interna pero a cambio existe una API completa de acceso a la porcioacuten dememoria que le corresponde a la app que desarrollemos

En una aplicacioacuten iOS encontraremos baacutesicamente 4 modos de almacenamiento deinformacioacuten de forma persistente

bull Ficheros de propiedades (plist)bull Bases de datos embebidas SQLitebull Datos de usuario (User Defaults)bull Core Data

En esta primera sesioacuten veremos coacutemo usar los ficheros de texto (Property Lists) y elalmacenamiento en base de datos de tipo SQLite

52 Ficheros plist (Property Lists)

El almacenamiento de datos en ficheros de propiedades es uno de los maacutes usados en eldesarrollo de las aplicaciones para iOS Se usa principalmente para guardar datos deconfiguracioacuten de la aplicacioacuten u otra informacioacuten poco compleja y acceder a ella deforma sencilla Si estamos desarrollando un juego podemos usarlos para la definicioacuten delos niveles almacenar las puntuaciones del jugador etc

521 Leyendo datos desde ficheros plist

Los ficheros plist (Property Lists) tienen un formato XML aunque XCode nosproporciona una interfaz que nos permite acceder a ellos de forma muy sencilla Paraentender el funcionamiento de este tipo de ficheros vamos a realizar un ejemplo en el queprimero crearemos un fichero plist lo completaremos con datos de ejemplo y despueacutes loleeremos para mostrarlo por pantalla

Un ejemplo de un fichero plist podriacutea ser el siguiente

Persistencia

38Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Fichero plist

Antes de nada creamos un proyecto nuevo en XCode de tipo Window-based

Application al que llamaremos sesion03-ejemplo1 Para crear un fichero plistdebemos hacer click derecho sobre el directorio Supporting Files de nuestro proyectoy seleccionamos iOS gt Resource gt Property List Lo guardaremos con el nombreconfigUsuario Seguidamente ya podemos acceder al fichero recieacuten creado haciendoclick sobre el en este momento XCode nos mostraraacute un pequentildeo editor totalmente vacioen el que iremos rellenando con datos Podemos verlo tambieacuten en formato XML sihacemos click derecho sobre el y seleccionamos Open As gt Preview

Para empezar a rellenar el fichero que acabamos de crear hacemos click derecho dentrode su contenido (ahora vacio) y seleccionamos Add row Entonces nos apareceraacute unafila vaciacutea Dentro del campo Key escribimos la clave un nombre descriptivo quedeberemos utilizar maacutes adelante para acceder a los datos En nuestro caso vamos aescribir config como clave Dentro de la columna Type seleccionamosDictionary Veremos que aparece una flecha en el lado izquierdo que indica quepueden haber subniveles dentro de nuestro diccionario

Ahora antildeadimos un nuevo item que cuelgue del diccionario que acabamos de crear paraello hacemos click sobre la flecha de la izquierda esta se giraraacute hacia abajo entonceshacemos click ahora sobre el siacutembolo + (maacutes) Nos apareceraacute una nueva liacutenea que cuelgadel diccionario

En esta nueva liacutenea dentro del campo key escribimos nombre el campo type lodejamos como de tipo String ya que va a ser una cadena de texto y en la columna deValue escribimos nuestro nombre por ejemplo Javi

Ahora vamos a antildeadir otro campo nuevo en nuestra configuracioacuten para ello hacemosclick sobre el siacutembolo + y nos apareceraacute una nueva liacutenea justo al mismo nivel que laanterior y colgando del diccionario Dentro del campo de Key escribimos ciudad yen Value escribimos Alicante El campo de Type lo dejamos como String

Persistencia

39Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora dentro de nuestro fichero plist vamos a indicar los dispositivos que dispone elusuario para ello creamos una nueva linea dentro del diccionario y le pondremos comoclave dispositivos Seleccionamos el tipo Array y seguimos los mismos pasosque al crear el diccionario para ello hacemos click sobre la flecha de la izquierda ydespueacutes sobre el siacutembolo + (maacutes) Ahora nos apareceraacute una nueva fila con clave Item 0Seleccionamos el tipo String y en el campo Value iPhone

Ahora repetimos el paso 3 veces indicando como values iPad Android y

Blackberry

Una vez seguidos todos estos pasos tendremos nuestro fichero de propiedades listo Debede quedar como se muestra a continuacioacuten

Fichero plist terminado

Este fichero se almacenaraacute dentro del directorio de bundle cuando compilemos laaplicacioacuten En el caso de que queramos escribir en el debermos almacenar el ficherodentro del directorio de Documents del binario

Ahora desde nuestro coacutedigo vamos a acceder a eacutel y a mostrarlo por pantalla para elloescribimos el siguiente coacutedigo dentro del meacutetodo didFinishLaunchingWithOptions dela clase delegada

Cargamos el fichero PLISTNSString mainBundlePath = [[NSBundle mainBundle] bundlePath]NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]NSDictionary diccionario = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionario delraiz

NSString nombre = [dicConfig objectForKeynombre]

Persistencia

40Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

[selfwindow makeKeyAndVisible]return YES

Si todo ha ido correctamente deberemos obtener la siguiente salida por la consola

Lista de dispositivos

522 Escribiendo datos en ficheros plist

Partiendo del ejemplo anterior vamos a escribir ahora en el fichero para ello primerodebemos comprobar que el fichero plist se encuentre dentro del directorio de Documents

del dispositivo en el caso de que no lo esteacute (por ejemplo si es la primera vez que arrancala aplicacioacuten) debemos copiarlo a ese directorio Una vez que tenemos el fichero dentrodel directorio de documentos ya podemos escribir en el

Para realizar todo el proceso de comprobacioacuten de la existencia del fichero dentro deldirectorio de documentos copiarlo en ese directorio y leer los datos debemos de sustituirel coacutedigo que hay dentro del meacutetodo didFinishLaunchingWithOptions de la clasedelegada por este otro

AtencioacutenPara que funcione la escritura en un fichero plist este debe de estar dentro del directorio deDocuments de nuestro dispositivo iOS no en el boundle

NSDictionary diccionarioRaiz

Ruta del directorio de documentos de nuestro dispositivoNSArray paths =

NSSearchPathForDirectoriesInDomains(NSDocumentDirectoryNSUserDomainMask YES)

if ([paths count] gt 0)

Persistencia

41Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

Primero comprobamos si existe el fichero en eldirectorio de documentos

BOOL fileExists = [[NSFileManager defaultManager]fileExistsAtPathdocumentsFilename]if (fileExists)

NSLog(Fichero encontrado en directorio de documentosOK

documentsFilename)

Si existe ya cargamos los datos desde aquidirectamente

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

else

NSLog(No se encuentra el fichero configUsuarioplisten

el directorio de documentos-gtLo creamos)

Si no existe primero cargamos el fichero PLISTdesde el Boundle

NSString mainBundlePath = [[NSBundle mainBundle]bundlePath]

NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Y despues escribimos los datos cargados (eldiccionario completo)

a un fichero nuevo de la carpeta de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

NSLog(plist de configUsuario creado)

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionariodel raiz

NSString nombre = [dicConfig objectForKeynombre]NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

Persistencia

42Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Por ultimo liberamos la memoria del diccionario raiz[diccionarioRaiz release]

[selfwindow makeKeyAndVisible]return YES

Para comprobar el funcionamiento arrancamos la aplicacioacuten y veremos que la primera vezse crearaacute el fichero dentro de la carpeta de Documents de nuestro dispositivo y en lassiguientes ya no se crearaacute y simplemente lo leeraacute desde la carpeta de Documents

Ahora que ya tenemos la gestioacuten de ficheros baacutesica completada vamos a escribir sobre elalguacuten dato Para ello simplemente escribimos el siguiente fragmento de coacutedigo justo antesde la liacutenea [selfwindow makeKeyAndVisible]

Cambiamos el nombre y lo guardamos en el fichero Ruta del directorio de documentos de nuestro dispositivo

if ([paths count] gt 0)

Cargamos el fichero desde el directorio dedocumentos del dispositivo

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cambiamos el valor del nombre en el diccionario[dicConfig setValueOtro nombre forKeynombre]

Escribimos todo el diccionario de nuevo al fichero del directorio de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

Por ultimo liberamos el diccionario de lamemoria

[diccionarioRaiz release]

Lo que hemos hecho en el coacutedigo anterior es cargar todo el diccionario de nuevo desde eldirectorio de documentos modificar los datos que queremos cambiar o incluso antildeadir ydespueacutes guardar el diccionario en el fichero

Volvemos a arrancar la aplicacioacuten y veremos que ha cambiado el campo de nombre

Nota

Persistencia

43Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

La lectura y escritura de datos en un fichero plist se debe de hacer de una soacutela vez nunca porpartes Siempre se debe de cargar o escribir un diccionario NSDictionary como elementoprincipal del fichero

53 SQLite

SQLite es una base de datos relacional ligera con la que se puede trabajar de formasencilla mediante sentencias SQL La base de datos se almacenaraacute en un fichero dentrode nuestra aplicacioacuten Este meacutetodo de persistencia puede ser el adecuado cuando los datosa almacenar esteacuten en un formato de tablas filas y columnas y se necesite que los datossean accesibles de forma raacutepida

531 Herramientas disponibles

Para explicar el funcionamiento de las librerias de SQLite de Cocoa Touch vamos a seguirun ejemplo completo Comenzamos creando la base de datos para ello podemos utilizaruno de los frontends que facilitan la gestioacuten del SQLite uno bastante sencillo yrecomendado es el SQLite Database Browser y otro es fmdb

Nosotros utilizaremos el primero (SQLite Database Browser) Abrimos la aplicacioacuten yhacemos click sobre el botoacuten de crear nueva base de datos que llamaremospersonasDBsqlite Seguidamente creamos las tablas para simplificar en nuestroejemplo crearemos una soacutela tabla llamada personas Esta tabla tendraacute las siguientescolumnas id (NUMERIC) nombre (TEXT) apellidos (TEXT) dni (TEXT) edad

(NUMERIC) y localidad (TEXT)

Persistencia

44Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Estructura de la Base de Datos

Una vez creada la tabla pasamos a insertar datos dentro de ella para ello abrimos lapestantildea de Browse Data y hacemos click en New Record Cuando tengamos al menos 3registros metidos dentro de la tabla ya podemos guardar la base de datos Hacemos clicksobre guardar le ponemos el nombre que queramos y elegimos cualquier directorio delMAC

Datos de la Base de Datos

532 Lectura de datos

Una vez que tenemos la base de datos SQLite creada ya podemos empezar a escribir elcoacutedigo para leer y mostrar los datos dentro de nuestra aplicacioacuten

Primero empezamos creando un proyecto nuevo en XCode usando la plantilla basada envistas View-based application y lo llamaremos sesion03-ejemplo2 Ahora antildeadimosel framework de sqlite a nuestro proyecto para ello hacemos click sobre el raiz delproyecto y seleccionamos la pestantildea Build Phases Desplegamos el listado de Link

Binary With Libraries y hacemos click sobre el botoacuten (+) El el listadoseleccionamos el framework libsqlite3dylib y hacemos click en Add

Persistencia

45Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Antildeadiendo el framework libsqlite3dylib

Ahora antildeadimos a nuestro proyecto la base de datos que hemos creado anteriormentepara ello arrastramos el fichero personasDBsqlite a nuestro directorio Supporting

Files Asegurate de seleccionar la opcioacuten de Copy items to destination groups

folder (if needed) para que se copie el fichero dentro de la carpeta del proyecto

Para acelerar el proceso de lectura de la base de datos y ahorrar memoria vamos a cargarprimero todos los datos en objetos para ello vamos a crear una clase que se encargue dealmacenar los datos Hacemos click derecho sobre el raiz del proyecto New file gtObjective-C class seleccionamos NSObject como clase principal y le damos a NextEscribimos como nombre de la clase Persona y hacemos click en save

Personah

imports iniciales

interface Persona NSObject int _uIdNSString _nombreNSString _apellidos

Persistencia

46Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSInteger _edadNSString _localidad

property (nonatomic assign) int uIdproperty (nonatomic copy) NSString nombreproperty (nonatomic copy) NSString apellidosproperty (nonatomic) NSInteger edadproperty (nonatomic copy) NSString localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidos edad(NSInteger)edadlocalidad(NSString )localidad

end

Personam

import Personah

implementation Persona

synthesize uId = _uIdsynthesize nombre = _nombresynthesize apellidos = _apellidossynthesize edad = _edadsynthesize localidad = _localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidosedad(NSInteger)edad localidad(NSString )localidad

if ((self = [super init])) selfuId = uIdselfnombre = nombreselfapellidos = apellidosselfedad = edadselflocalidad = localidad

return self

- (void) dealloc selfnombre = nilselfapellidos = nilselflocalidad = nil[super dealloc]

end

Una vez creada la clase Persona vamos a crear la clase encargada de manejar la basede datos sqlite3 Otra opcioacuten seriacutea cargar todos los datos directamente desde la clasedelegada al arrancar la aplicacioacuten pero no es recomendable ya que si en alguacuten momentotenemos que cambiar de motor de base de datos lo tendremos maacutes complicado

Persistencia

47Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creamos entonces una clase dentro de nuestro proyecto llamada BDPersistencia queherede de NSObject y escribimos el siguiente coacutedigo

BDPersistenciah

imports iniciales

interface BDPersistencia NSObject sqlite3 _database

+ (BDPersistencia)database- (NSArray )arrayPersonas

end

BDPersistenciam

import BDPersistenciahimport Personah

implementation BDPersistencia

static BDPersistencia _database

+ (BDPersistencia)database if (_database == nil)

_database = [[BDPersistencia alloc] init]return _database

- (id)init if ((self = [super init]))

NSString sqLiteDb = [[NSBundle mainBundle]pathForResourcepersonasDB

ofTypesqlite]

if (sqlite3_open([sqLiteDb UTF8String]amp_database) = SQLITE_OK)

NSLog(Fallo al abrir la BD)

return self

- (void)dealloc sqlite3_close(_database)[super dealloc]

- (NSArray )arrayPersonas

NSMutableArray arraySalida = [[[NSMutableArray alloc]init]

autorelease]

Persistencia

48Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString query = SELECT id nombre apellidoslocalidad edad

FROM personas ORDER BY apellidos DESC

sqlite3_stmt statementif (sqlite3_prepare_v2(_database [query UTF8String]

-1ampstatement nil)

== SQLITE_OK) while (sqlite3_step(statement) == SQLITE_ROW)

int uniqueId = sqlite3_column_int(statement0)

char nombreChar = (char )sqlite3_column_text(statement 1)

char apellidosChar = (char )sqlite3_column_text(statement 2)

char localidadChar = (char )sqlite3_column_text(statement 3)

int edad = sqlite3_column_int(statement 4)

NSString nombre = [[NSString alloc]initWithUTF8StringnombreChar]NSString apellidos = [[NSString alloc]initWithUTF8StringapellidosChar]NSString localidad = [[NSString alloc]initWithUTF8StringlocalidadChar]Persona persona = [[Persona alloc]initWithUiduniqueId nombrenombreapellidosapellidos edadedad

localidadlocalidad]

[arraySalida addObjectpersona][nombre release][apellidos release][localidad release][persona release]

sqlite3_finalize(statement)

return arraySalida

end

Con esto ya podemos leer de nuestra base de datos las personas Para ver que funcionatodo a la perfeccioacuten escribimos el siguiente coacutedigo dentro de la clase delegada en elmeacutetodo applicationDidFinishLaunching Antes debemos incluir en la parte superiorlas clases creadas

import BDPersistenciahimport Personah

NSArray personas = [BDPersistenciadatabase]arrayPersonas

for (Persona persona in personas) NSLog(d d personauId

personanombrepersonaapellidos personalocalidad personaedad)

Persistencia

49Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Si ejecutamos el proyecto obtendremos la siguiente salida por consola

Listado de personas

533 Mostrando los datos en una tabla

Una vez que tenemos el coacutedigo para obtener todas las filas de la tabla de personas de labase de datos queda lo maacutes faacutecil mostrarlas dentro de una vista de la aplicacioacuten En estecaso utilizaremos la vista de tabla UITableView

Comenzamos creando una nueva vista en nuestro proyecto para ello hacemos clickderecho sobre el raiz y seleccionamos New File gt UIVIewController SubclassAsegurate de marcar Subclass of UITableViewController y la opcoacuten de With

XIB for user interface Hacemos click en Next y guardamos el fichero comoPersonasViewController por ejemplo

Ahora abrimos el fichero PersonasViewControllerh y antildeadimos un NSArray queseraacute el que almacene las personas de la base de datos

imports iniciales

interface PersonasViewController UITableViewController

NSArray _listadoPersonas

property (nonatomic retain) NSArray listadoPersonas

end

Ahora abrimos el fichero PersonasViewControllerm antildeadimos el syntetizeimportamos los ficheros de gestioacuten de la base de datos la clase Persona y todo el coacutedigobaacutesico para la gestioacuten de la vista de la tabla

import PersonasViewControllerhimport Personahimport BDPersistenciah

implementation PersonasViewController

synthesize listadoPersonas = _listadoPersonas

Persistencia

50Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

- (id)initWithStyle(UITableViewStyle)style

self = [super initWithStylestyle]if (self)

Custom initializationreturn self

- (void)dealloc

[super dealloc]

selflistadoPersonas = nil

- (void)didReceiveMemoryWarning

Releases the view if it doesnt have a superview[super didReceiveMemoryWarning]

Release any cached data images etc that arent inuse

pragma mark - View lifecycle

- (void)viewDidLoad

[super viewDidLoad]

selftitle = Listado de personas

selflistadoPersonas = [BDPersistenciadatabase]arrayPersonas

- (void)viewDidUnload

[super viewDidUnload] Release any retained subviews of the main view eg selfmyOutlet = nil

- (void)viewWillAppear(BOOL)animated

[super viewWillAppearanimated]

- (void)viewDidAppear(BOOL)animated

[super viewDidAppearanimated]

- (void)viewWillDisappear(BOOL)animated

[super viewWillDisappearanimated]

- (void)viewDidDisappear(BOOL)animated

[super viewDidDisappearanimated]

- (BOOL)shouldAutorotateToInterfaceOrientation

Persistencia

51Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

(UIInterfaceOrientation)interfaceOrientation

Return YES for supported orientationsreturn (interfaceOrientation ==

UIInterfaceOrientationPortrait)

pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView(UITableView)tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection

(NSInteger)section

Return the number of rows in the sectionreturn [selflistadoPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath

(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]if (cell == nil)

cell = [[[UITableViewCell alloc]initWithStyleUITableViewCellStyleSubtitle

reuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona persona = (Persona )[selflistadoPersonas

objectAtIndexindexPathrow]celltextLabeltext = [NSString stringWithFormat

(d) personanombrepersonaapellidos personaedad]celldetailTextLabeltext = personalocalidad

return cell

pragma mark - Table view delegate

- (void)tableView(UITableView )tableViewdidSelectRowAtIndexPath

(NSIndexPath )indexPath

Navigation logic may go here Create and pushanother view controller

end

Persistencia

52Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora soacutelo nos queda incorporar la vista que acabamos de crear dentro de nuestraaplicacioacuten Para ello vamos a llamarla desde el Delegate y a meterla dentro de unNavigation Controller Primero debemos de antildeadir un Outlet en la clasedelegada hacia un Navigation Controller

imports iniciales

interface sesion03_ejemplo1AppDelegate NSObjectltUIApplicationDelegategt

UIWindow _windowUINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet

UINavigationController navController

end

Ahora hacemos click sobre la vista MainWindowxib y arrastramos un Navigation

Controller desde el listado de la columna de la derecha hacia la columna de laizquierda donde pone Objects Nos apareceraacute el Navigation Controller dibujadoen pantalla Ahora desplegamos el Navigation Controller y hacemos click sobre elView Controller que hay dentro Nos vamos a la pestantildea de Identity Inspector

de la columna de la derecha y escribimos dentro de Class el nombre de la vista dellistado de personas PersonasViewController

Finalmente hacemos click sobre el objeto Delegate vamos a la pestantildea de Show

Connections Inspector y arrastramos desde navController hasta el objetoNavigation Controller de la columna de la izquierda Ya tenemos los controladoresy las vistas conectadas

Una vez hecho esto nos queda indicar dentro de nuestro coacutedigo que la aplicacioacuten arranquecon el Navigation Controller para ello escribimos lo siguiente al final del meacutetododidFinishLaunchingWithOptions de la implementacioacuten de la clase delegada

selfwindowrootViewController = selfnavController[selfwindow makeKeyAndVisible]return YES

Ya estaacute listo todo y preparado para compilar Si ejecutamos el proyecto veremos que nosaparece el listado de personas en la vista de tabla

Persistencia

53Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios

61 Creando y leyendo un fichero plist (15 puntos)

En este primer ejercicio vamos a crear nuestra primera aplicacioacuten usando el meacutetodo depersistencia de ficheros La aplicacioacuten mostraraacute en una vista de tabla TableView unlistado de series en la que si seleccionamos una veremos su informacioacuten maacutes importanteen otra vista Comenzaremos creando un fichero de propiedades (plist) siguiendo unaestructura determinada y posteriormente mostraremos en una vista de tabla todos susdatos iexclComenzamos

1) Crear un proyecto usando la plantilla Master-Detail application Lo llamaremosejercicio-plist-ios como identificador de empresa escribiremos esuajtech y como prefijode clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremos UseAutomatic Reference Counting para olvidarnos de la gestioacuten de memoria El resto deopciones las dejaremos sin marcar

2) Crear un archivo de propiedades (plist) con el nombre de series

3) Editar el archivo plist creando la siguiente estructura de datos (5 series miacutenimo)

Plist series

4) Rellenar el archivo plist con datos de series reales

5) Escribir el coacutedigo necesario en UAMasterViewControllerm que recorra el archivoplist que acabamos de crear y muestre por consola los titulos de las series

6) Mostrar en la tabla que hay implementada los tiacutetulos de la serie Para ello deberemosde completar todos los meacutetodos del protocolo del TableView

7) Ejecutar la aplicacioacuten y comprobar que funciona correctamente

Persistencia

54Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

62 Implementar vista de detalle de la serie (05 puntos)

Dentro del proyecto anterior (no hace falta crear uno nuevo) vamos a mostrar toda lainformacioacuten adicional de cada una de las series en una vista de detalle Cuandoseleccionemos una fila de la tabla nos deberaacute aparecer una nueva vista con el resto deinformacioacuten (antildeo director principal sinopsis y duracioacuten)

63 Uso baacutesico de una base de datos SQLite (1 punto)

En este ejercicio vamos a crear la misma aplicacioacuten que en el ejercicio anterior perousando el meacutetodo de persistencia de SQLite Para completar el ejercicio deberemos seguirlos siguientes pasos

1) Crear un proyecto usando la plantilla Master-Detail application lo llamaremosejercicio-sqlite-ios como identificador de empresa escribiremos esuajtech y comoprefijo de clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremosUse Automatic Reference Counting para olvidarnos de la gestioacuten de memoria El restode opciones las dejaremos sin marcar

2) Nos descargamos e instalamos el programa SQLite Database Browser desde estadireccioacuten

3) Disentildear la estructura de la base de datos usando SQLite Database Browser tal y comose muestra en la imagen siguiente

Sqlite series

4) Insertar al menos 5 series en la base de datos usando el programa SQLite DatabaseBrowser

5) Cargar todos los datos de la base de datos desde el meacutetododidFinishLaunchingWithOptions de la clase UAAppDelegate Mostrar por consola losdatos cargados

6) Mostrar los datos cargados en el paso anterior en la vista de tabla UITableView quehay creada en la clase UAMasterViewController

Persistencia

55Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7) Arrancar la aplicacioacuten y comprobar que se muestran las series en la tabla de formacorrecta

Persistencia

56Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7 Persistencia de datos en iOS User Defaults y Core Data

71 Introduccioacuten

En esta sesioacuten veremos como utilizar dos nuevos meacutetodos de persistencia en iOS ambosmuy interesantes y usados muy frecuentemente

bull User Defaults Muy uacutetil para almacenar datos simples y de uso repetido dentro denuestras aplicaciones iOS

bull Core Data Meacutetodo avanzado de almacenamiento de objetos enteros dentro de lamemoria del dispositivo iOS Muy uacutetil para almacenar datos complejos conrelaciones etc

72 User Defaults

Este meacutetodo es muy uacutetil cuando queremos almacenar pequentildeas cantidades de datos comopuntuaciones informacioacuten de usuario el estado de nuestra aplicacioacuten configuracionesetc

Este meacutetodo es el maacutes raacutepido de usar ya que no requiere de una base de datos ni ficherosni ninguna otra capa intermedia Los datos se almacenan directamente en la memoria deldispositivo dentro de un objeto de la clase NSUserDefaults

Para aprender a usar este meacutetodo de almacenamiento de datos vamos a crear unaaplicacioacuten muy simple que lea un nombre y una edad de pantalla y las guarde en unobjeto NSUserDefaults Para ello seguimos los siguientes pasos

bull 1) Comenzamos creando un nuevo proyecto llamado sesion04-ejemplo1 dentrode XCode

bull 2) Abrimos la vista sesion04_ejemplo1ViewController y arrastramos dos TextField un botoacuten y dos labels quedando la vista de la siguiente manera

Persistencia

57Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista de la aplicacioacuten

bull 3) Abrimos el fichero sesion04_ejemplo1ViewControllerh y definimos el Outletdel evento onclick del botoacuten El fichero quedaraacute de la siguiente manera

imports iniciales

interface sesion04_ejemplo1ViewController UIViewController UITextField tfNombreUITextField tfEdad

property(nonatomic retain) IBOutlet UITextField tfNombreproperty(nonatomic retain) IBOutlet UITextField tfEdad

-(IBAction) guardaDatos

end

bull 4) Ahora implementamos el coacutedigo para el meacutetodo del botoacuten dentro desesion04_ejemplo1ViewControllerm

synthesize tfEdadsynthesize tfNombre

-(IBAction) guardaDatos NSLog(Guardamos los datos)

Persistencia

58Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creamos el objeto NSUserDefaultsNSUserDefaults datos = [NSUserDefaults standardUserDefaults]

guardamos el nombre (NSString)[datos setObjecttfNombretext forKeynombre]

guardamos la edad (Integer)[datos setInteger[tfEdadtext integerValue] forKeyedad]

almacenamos los datos en la memoria[datos synchronize]

bull 5) Tenemos que enlazar el botoacuten con el evento que hemos programado en el pasoanterior para ello abrimos la vista sesion04_ejemplo1ViewControllerxib y conla tecla ctlr apretada arrastramos desde el botoacuten hasta Files Owner (columna de laizquierda parte superior) y seleccionamos el meacutetodo que hemos creado(guardaDatos) De esta forma ya tenemos conectado el botoacuten

bull 6) Ahora hacemos lo mismo con los Text Field Tenemos que conectarlos alcontrolador para ello seleccionamos el objeto Files Owner y abrimos la pestantildeade conexiones (la que estaacute maacutes a la derecha en la columna de la derecha) Hacemosclick en tfEdad y arrastramos hasta el Text Field de edad Hacemos lo mismo para eltext field de nombre Ya tenemos todos los elementos de la vista conectados

Conexioacuten de Outlets

bull 7) Arrancamos la aplicacioacuten y la probamos

Persistencia

59Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Aplicacioacuten

bull 8) Ahora vamos a hacer que la aplicacioacuten cargue los datos que se han guardado alarrancar la vista para ello simplemente tenemos que escribir el siguiente coacutedigo en elmeacutetodo (void)viewDidLoad de la clase sesion04_ejemplo1ViewControllerm

- (void)viewDidLoad

[super viewDidLoad]

NSUserDefaults datos = [NSUserDefaults standardUserDefaults]

obtenemos un NSStringNSString nombre = [datos stringForKeynombre]

obtenemos un NSIntegerNSInteger edad = [datos integerForKeyedad]

if (nombre=nil ampamp edad=0)NSLog(Nombre cargado edad dnombreedad)

else

NSLog(Sin datos)

bull 9) Ya podemos arrancar de nuevo la aplicacioacuten y veremos que si escribimos cualquier

Persistencia

60Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

texto en los Text Fields estos se almacenaraacuten dentro del objeto NSUserDefaults Alapagar la aplicacioacuten y volver a arrancarla estos datos se cargaraacuten (apareceraacuten enconsola)

Consola

NotaLos datos permaneceraacuten en nuestro dispositivo hasta que se elimine la aplicacioacuten en ese casotodos los datos guardados en NSUserDefaults se borraraacuten

bull 10) Probar eliminar la aplicacioacuten del dispositivosimulador y volver arrancarComprobamos que los datos se han borrado

Consola

73 Core Data

De todas las formas de persistencia de datos que existen en iOS Core Data es la maacutesapropiada para usar en caso de manejar datos no triviales con relaciones algo maacutescomplejas entre ellos El uso de Core Data dentro de nuestra aplicacioacuten optimiza almaacuteximo el consumo de memoria mejora el tiempo de respuesta y reduce la cantidad decoacutedigo redundante a escribir

Core Data utiliza de fondo el meacutetodo de almacenamiento SQLite visto en la sesioacuten 3 deeste moacutedulo por tanto para explicar el funcionamiento de este meacutetodo de persistenciavamos a utilizar el mismo modelo de datos que usamos en esa sesioacuten Persona -gtDetallePersona

El el siguiente ejemplo haremos la misma aplicacioacuten que hicimos con SQLite perousando este nuevo meacutetodo de persistencia

731 Creando el proyecto

Para empezar tenemos que crearnos un nuevo proyecto en XCode Para ello abrimosXCode y seleccionamos crear una nueva aplicacioacuten basada en ventanas (Window-basedapplication) Hacemos click en Next escribimos como nombre del proyectosesion04-ejemplo2 y seleccionamos Use Core Data

Persistencia

61Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creando un proyecto nuevo

Con esto ya tenemos la estructura baacutesica para empezar a programar Antes de nadahechamos un vistazo raacutepido a toda la estructura de directorios creada y vemos que dentrodel directorio principal tenemos un archivo que se llamasesion04_ejemplo2xcdatamodeld Al hacer click sobre el archivo se nos abre uneditor especial que en principio estaacute vacio y que maacutes adelante utilizaremos para disentildearnuestro modelo de datos

Ahora hechamos un vistazo a la clase delegada sesion04_ejemplo2AppDelegatemvemos que hay unos cuantos meacutetodos nuevos que serviraacuten para configurar el Core DataVamos a explicar los maacutes importantes

bull - (NSManagedObjectModel )managedObjectModel NSManagedObjectModel esla clase que contiene la definicioacuten de cada uno de los objetos o entidades quealmacenamos en la base de datos Normalmente este meacutetodo no lo utilizaremos ya quenosotros vamos a utilizar el editor que hemos visto antes con el que podremos crearnuevas entidades crear sus atributos y relaciones

bull - (NSPersistentStoreCoordinator )persistentStoreCoordinator Aquiacute esdonde configuramos los nombres y las ubicaciones de las bases de datos que se usaraacutenpara almacenar los objetos Cuando un managed object necesite guardar algo pasaraacutepor este meacutetodo

bull - (NSManagedObjectContext )managedObjectContext Esta claseNSManagedObjectContext seraacute la maacutes usada con diferencia de las tres y por tanto la

Persistencia

62Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

maacutes importante La utilizaremos baacutesicamente para obtener objetos insertarlos oborrarlos

732 Definiendo el modelo de datos

En el ejemplo que hicimos en la sesioacuten anterior de SQLite creamos dos tablas una paralos datos baacutesicos de la persona y otra para el detalle Ahora haremos lo mismo perousando Core Data la diferencia principal es que mientras que con SQLite obteniamos soacutelolos datos que necesitaacutebamos de la base de datos aquiacute obtenemos el objeto completo

Para definir nuestro modelo de datos basado en dos objetos (Persona y DetallePersona)abrimos el editor visual haciendo click sobre el ficherosesion04_ejemplo2xcdatamodeld Empezaremos creando una nueva entidad para ellohacemos click sobre el botoacuten Add Entity (+) que se encuentra en la parte inferior dela pantalla A la nueva entidad le pondremos como nombre Persona

Dentro de la seccioacuten de Attributes hacemos click sobre (+) y creamos un nuevoatributo para nuestra entidad le llamamos nombre y le asignamos como tipo StringRealizamos este mismo paso 2 veces maacutes para antildeadir apellidos y edad este uacutetlimode tipo Integer 16

Atributos Persona

Ahora creamos la entidad de DetallePersona de la misma manera que la anterior perocreando los atributos localidad pais direccion cp y telefono Todasde tipo String

Atributos DetallePersona

Finalmente nos queda relacionar de alguacuten modo las dos entidades para ello antildeadimos una

Persistencia

63Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

relacioacuten dentro de la entidad de Persona hacia DetallePersona simplementehaciendo click sobre el botoacuten (+) de la seccioacuten de Relationships A la relacioacuten leponemos como nombre detalle seleccionamos como destino la entidadDetallePersona y seraacute No inverse Este tipo de relacioacuten es uno a uno Por dentroCore Data realizaraacute crearaacute una clave ajena en la tabla de persona que apunte aDetallePersona pero eso a nosotros no nos interesa

Vista general modelo de datos (1)

Apple recomienda que siempre que hagamos una relacioacuten de una entidad a otra hagamostambieacuten la relacioacuten inversa por lo tanto hacemos justo lo anterior pero al reveacutes (deDetallePersona a Persona) con la diferencia que en la columna de Inverse

seleccionamos datosPersona

Ya tenemos las entidades y las relaciones creadas Si hacemos click en el botoacuten deEditor Style en la parte de abajo a la derecha del editor podemos ver de una formamaacutes clara la estructura que hemos disentildeado

Persistencia

64Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista general modelo de datos (2)

733 Probando el modelo

Una vez que tenemos el modelo creado pasamos a probarlo para comprobar que estaacute todocorrecto para ello abrimos la clase delegada (sesion04_ejemplo2AppDelegatem) yantildeadimos el siguiente fragmento de coacutedigo arriba del meacutetodoapplicationDidFinishLaunching

NSManagedObjectContext context = [self managedObjectContext]NSManagedObject persona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

[persona setValueJuan forKeynombre][persona setValueMartinez forKeyapellidos][persona setValue[NSNumber numberWithInt28] forKeyedad]

NSManagedObject detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersonainManagedObjectContextcontext]

[detallePersona setValueCalle Falsa 123 forKeydireccion][detallePersona setValueAlicante forKeylocalidad][detallePersona setValueEspantildea forKeypais][detallePersona setValue965656565 forKeytelefono]

[detallePersona setValuepersona forKeydatosPersona][persona setValuedetallePersona forKeydetalle]

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Persistencia

65Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Con este coacutedigo hemos creado un objeto Persona enlazado con otro objetoDetallePersona iexclComo puedes ver no hemos necesitado nada de coacutedigo SQLPrimero creamos un objeto del tipo NSManagedObjectContext que almacena el conextode Core Data Despueacutes creamos el objeto Persona en siacute con NSManagedObject A esteobjeto le pondremos los valores para cada una de las propiedades definidas en el modeloHacemos lo mismo con DetallePersona Finalmente relacionamos el objetoPersona con el objeto DetallePersona

Compilamos el proyecto comprobamos que todo funciona bien y no salen errores

Para comprobar que los datos se han almacenado correctamente en nuestro dispositivo losmostramos en la consola de la siguiente manera

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

intValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(------------------)

[fetchRequest release]

El coacutedigo de arriba lo escribiremos dentro del meacutetododidFinishLaunchingWithOptions de la clase delegada Lo que hace este coacutedigo esrecorrer todos los objetos almacenados en memoria y mostrarlos por consola Siarrancamos la aplicacioacuten veremos que se muestran todos los datos de los objetosalmacenados Cada vez que arranquemos la aplicacioacuten se mostraraacuten maacutes objetos ya quetambieacuten los creamos

Para listar los objetos usamos la clase NSFetchRequest Esta clase es equivalente aejecutar una sentencia Select de SQL Al objeto NSFetchRequest le asignamos laentidad que queremos listar (SELECT FROM ) en nuestro caso seraacute PersonaEl listado de los objetos se obtiene mediante el meacutetodo executeFetchRequest de laclase NSManagedObjectContext que hemos definido en el bloque anterior de coacutedigoEste meacutetodo devolveraacute un NSArray con todos los objetos NSManagedObject

Persistencia

66Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Salida de consola

NotaSi queremos ver queacute sentencias SQL estaacute ejecutando Core Data por debajo tenemos que activarel flag de debug para ello seleccionamos la opcioacuten Product gt Edit Scheme Enla columna de la izquierda seleccionamos el esquema que estamos utilizando para Debug y en laparte de la derecha en el cuadro de Arguments Passed on Launch antildeadimos 2paraacutemetros -comappleCoreDataSQLDebug y 1

Ahora arrancamos de nuevo la aplicacioacuten y veremos que en la consola apareceraacuten lassentencias SQL utilizadas por Core Data

()

INSERT INTO ZDETALLEPERSONA(Z_PK Z_ENT Z_OPT ZDATOSPERSONA ZTELEFONOZLOCALIDAD ZDIRECCION ZPAIS)VALUES( )CoreData sql SELECT 0 t0Z_PK t0Z_OPT t0ZTELEFONO t0ZLOCALIDADt0ZDIRECCION t0ZPAIS t0ZDATOSPERSONAFROM ZDETALLEPERSONA t0 WHERE t0Z_PK =

()

734 Generando los modelos automaacuteticamente

Generar los modelos a mano directamente desde el editor no es lo maacutes recomendado yaque podemos cometer errores de escritura errores de tipo etc La mejor manera dehacerlo es creando un archivo para cada entidad Esto se puede hacer desde 0 peroXCode tiene un generador de clases que es muy faacutecil de usar y ahorremos bastantetiempo iexclVamos a usarlo

Hacemos click sobre el raiz de nuestro proyecto y seleccionamos New File En lacolumna de la izquierda seleccionamos iOS gt Core Data y en el cuadro de laderecha NSManagedObject subclass Click en next En la siguiente pantallaseleccionamos nuestro modelo de datos sesion04_ejemplo2 y click en next Ahoranos aseguramos de seleccionar las dos entidades que hemos creado anteriormentePersona y DetallePersona click en next Seleccionamos el raiz de nuestroproyecto para guardar los ficheros que se generen y aceptamos

Persistencia

67Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ventana de seleccioacuten de entidad

Ahora veremos que xsource ha creado automaacuteticamente 4 ficheros nuevos dentro denuestro proyecto Personamh y DetallePersonamh con todos los atributosespecificados Estas nuevas clases que heredan de NSManagedObject seraacuten las queutilicemos a partir de ahora

Cambiamos el coacutedigo de la clase delegada por el siguiente

NSManagedObjectContext context = [self managedObjectContext]Persona datosPersona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

datosPersonanombre = JuandatosPersonaapellidos = MartinezdatosPersonaedad = [NSNumber numberWithInt28]

DetallePersona detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersona

inManagedObjectContextcontext]detallePersonadireccion = Calle Falsa 123

Persistencia

68Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

detallePersonalocalidad = AlicantedetallePersonapais = EspantildeadetallePersonatelefono = 965656565

detallePersonadatosPersona = datosPersonadatosPersonadetalle = detallePersona

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Listamos los datosNSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

integerValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(-----------------)[fetchRequest release]

Ejecutamos el proyecto y comprobamos que en la consola aparece lo mismo que antespero ahora tenemos el coacutedigo bastante maacutes claro

735 Creando la vista de tabla

Ahora vamos a mostrar los objetos que antes hemos listado por consola en una vista detabla UITableView Para ello creamos un UITableViewController

Click en New file gt UIViewController subclass En la siguiente pantallanos aseguramos de seleccionar Subclass of UITableViewController de esta formanos apareceraacuten ya todos los meacutetodos de la tabla creados por defecto Guardamos elarchivo con el nombre que queramos por ejemplo PersonasViewController

Abrimos ahora PersonasViewControllerh y antildeadimos 2 atributos a la clase

bull Un NSArray que se seraacute el listado de personas (objetos de la clase Persona)bull Una referencia a NSManagedObjectContext

El fichero PersonasViewControllerh quedaraacute de la siguiente manera

Persistencia

69Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

imports iniciales

interface PersonasViewController UITableViewController NSArray _listaPersonasNSManagedObjectContext _context

property (nonatomic retain) NSArray listaPersonasproperty (nonatomic retain) NSManagedObjectContext context

end

Ahora en el m importamos en el encabezado las clases Personah yDetallePersonah y en el meacutetodo viewDidLoad creamos el array listaPersonas contodos los objetos Persona de forma similar a cuando lo hicimos en el apartado anterior

import PersonasViewControllerh

import Personahimport DetallePersonah

implementation PersonasViewController

synthesize listaPersonas = _listaPersonassynthesize context = _context

En viewDidLoad

- (void)viewDidLoad

[super viewDidLoad]

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContext_context]

[fetchRequest setEntityentity]NSError errorselflistaPersonas = [_context executeFetchRequestfetchRequest

erroramperror]selftitle = Listado Personas[fetchRequest release]

Ahora completamos el resto de meacutetodos heredados de UITableViewController de lamisma forma que hicimos en sesiones anteriores

- (NSInteger)numberOfSectionsInTableView(UITableView )tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection(NSInteger)section

Persistencia

70Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Return the number of rows in the sectionreturn [_listaPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]

if (cell == nil) cell = [[[UITableViewCell alloc]

initWithStyleUITableViewCellStyleSubtitlereuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona p = (Persona )[_listaPersonas objectAtIndexindexPathrow]celltextLabeltext = pnombrecelldetailTextLabeltext = [NSString stringWithFormatd antildeos

[pedad intValue]]

return cell

Ahora para poder mostrar la tabla dentro de nuestra aplicacioacuten debemos asignarla dentrode la clase delegada para ello abrimos la vista MainWindowxib y arrastramos unacontroladora de navegacioacuten Navigation Controller a la lista de objetos de laizquierda La desplegamos y dentro de View Controller en la pestantildea de la derechade Identity Inspector escribimos el nombre de nuestra clase que tiene la vista de latabla PersonasViewController

Pantalla del disentildeador

Ahora abrimos el fichero sesion04_ejemplo2AppDelegateh y creamos un

Persistencia

71Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

UINavigationController dentro de la declaracioacuten de variables dejando el ficherocomo sigue

imports iniciales

interface sesion04_ejemplo2AppDelegate NSObject ltUIApplicationDelegategt

UINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet UINavigationControllernavController

property (nonatomic retain readonly) NSManagedObjectContextmanagedObjectContextproperty (nonatomic retain readonly) NSManagedObjectModelmanagedObjectModelproperty (nonatomic retain readonly) NSPersistentStoreCoordinatorpersistentStoreCoordinator

- (void)saveContext- (NSURL )applicationDocumentsDirectory

end

Dentro del m importamos la clase PersonasViewControllerh y completamos elsynthetize

synthesize navController = _navController

Y por uacuteltimo justo antes de la llamada al meacutetodo makeKeyAndVisible escribimos elsiguiente coacutedigo para asignar el contexto de la clase delegada alPersonasViewController

PersonasViewController root = (PersonasViewController ) [_navControllertopViewController]

rootcontext = [self managedObjectContext][selfwindow addSubview_navControllerview]

Ahora compilamos y ejecutamos nuestro proyecto y si todo ha ido bien deberiacutea de salirla tabla con los datos de las personas

Persistencia

72Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Listado de personas

736 Migraciones de bases de datos con Core Data

Una vez que tenemos disentildeada nuestra base de datos inicial iquestqueacute pasariacutea si queremosmodificarla para antildeadirle un campo maacutes a una tabla iquestQueacute haraacute Core Data para realizar lamigracioacuten de la versioacuten anterior a la actual

Siguiendo con el ejemplo anterior si despueacutes de ejecutar la aplicacioacuten en nuestrodispositivo o simulador modificamos la estructura de la base de datos antildeadiendo unatributo nuevo en la entidad DetallePersona por ejemplo codigoPostal con el tipoInteger 16 veremos que al volver a arrancar la aplicacioacuten nos apareceraacute un error entiempo de ejecucioacuten similar al siguiente

Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 Theoperationcouldnrsquot be completed(Cocoa error 134100) UserInfo=0x6b6c680 metadata=ltCFBasicHash 0x6b68380[0x1337b38]gttype = immutable dict count = 7entries =gt

2 ltCFString 0x6b70320 [0x1337b38]gtcontents =NSStoreModelVersionIdentifiers

=ltCFArray 0x6b706e0 [0x1337b38]gttype = immutable count = 1

values = (0 ltCFString 0x1332cd8 [0x1337b38]gtcontents =

)4 ltCFString 0x6b70350 [0x1337b38]gtcontents =

NSPersistenceFrameworkVersion=

Persistencia

73Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltCFNumber 0x6b6f2c0 [0x1337b38]gtvalue = +386 type =kCFNumberSInt64Type

6 ltCFString 0x6b70380 [0x1337b38]gtcontents =NSStoreModelVersionHashes =

ltCFBasicHash 0x6b707e0 [0x1337b38]gttype = immutable dict count =2entries =gt

1 ltCFString 0x6b70700 [0x1337b38]gtcontents = Persona =ltCFData 0x6b70740

[0x1337b38]gtlength = 32 capacity = 32 bytes =0x1c50e5a379ff0ddc3cda7b82a3934c5e 75649ac3c919beea2 ltCFString 0x6b70720 [0x1337b38]gtcontents = DetallePersona

=ltCFData 0x6b70790[0x1337b38]gtlength = 32 capacity = 32 bytes =0xb7ba2eb498f9a1acdd6630d56f6cd12c e8963dd3e488ba95

7 ltCFString 0x10e3aa8 [0x1337b38]gtcontents = NSStoreUUID =ltCFString 0x6b70510[0x1337b38]gtcontents = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E8 ltCFString 0x10e3948 [0x1337b38]gtcontents = NSStoreType =ltCFString 0x10e3958[0x1337b38]gtcontents = SQLite9 ltCFString 0x6b703b0 [0x1337b38]gtcontents =

_NSAutoVacuumLevel = ltCFString0x6b70830 [0x1337b38]gtcontents = 210 ltFString 0x6b703d0 [0x1337b38]gtcontents =

NSStoreModelVersionHashesVersion=ltCFNumber 0x6b61950 [0x1337b38]gtvalue = +3 type =

kCFNumberSInt32Type reason=The model used to open the store is incompatible with the oneused tocreate the store

metadata = NSPersistenceFrameworkVersion = 386NSStoreModelVersionHashes =

DetallePersona = ltb7ba2eb4 98f9a1ac dd6630d5 6f6cd12c f890007746b16f00

e8963dd3 e488ba95gtPersona = lt1c50e5a3 79ff0ddc 3cda7b82 a3934c5e 5cb4ee4b

4ac98838 75649ac3c919beeagt

NSStoreModelVersionHashesVersion = 3NSStoreModelVersionIdentifiers = (

)NSStoreType = SQLiteNSStoreUUID = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E_NSAutoVacuumLevel = 2

reason = The model used to open the store is incompatible with the

one usedto create the store

Este error es muy comuacuten cuando estamos desarrollando aplicaciones con Core DataBaacutesicamente nos dice que el modelo de base de datos que estaacute intentando cargar dememoria es distinto al que se indica en la aplicacioacuten esto es porque no hemos indicadoninguacuten meacutetodo de migracioacuten Realmente existen dos formas de solucionar este error

Persistencia

74Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Por un lado y soacutelo cuando estemos desarrollando y no tengamos ninguna versioacutende nuestra aplicacioacuten lanzada en la App Store podremos eliminar la base de datosdel dispositivo simulador simplemente borrando la aplicacioacuten de el Tambieacutenpodemos ejecutar el siguiente coacutedigo una soacutela vez [[NSFileManagerdefaultManager] removeItemAtURLstoreURL errornil]

bull En el caso de que estemos realizando una migracioacuten sobre una versioacuten ya publicadaen la App Store el meacutetodo anterior no nos serviriacutea ya que obligariacuteamos al usuario aborrar su aplicacioacuten del dispositivo con lo que todos los datos que tuviera seperderiacutean Esto no es viable por lo que necesitaremos usar otro meacutetodo para realizarla migracioacuten y solucionar el error de una forma maacutes limpia este meacutetodo es el quevamos a comentar a continuacioacuten

XCode soporta Versioning para Core Data esto es que podemos crear versiones de labase de datos de una forma sencilla si seguimos una serie de pasos

1) Antildeadimos las siguientes opciones en forma de NSDictionary a nuestro coacutedigo delpersistentStoreCoordinator

NSDictionary options = [NSDictionary dictionaryWithObjectsAndKeys[NSNumber numberWithBoolYES]NSMigratePersistentStoresAutomaticallyOption[NSNumber numberWithBoolYES]NSInferMappingModelAutomaticallyOptionnil]

Y modificamos el meacutetodo addPersistentStoreWithType pasaacutendole como paraacutemetro eldiccionario options

if ([__persistentStoreCoordinatoraddPersistentStoreWithTypeNSSQLiteStoreTypeconfigurationnil URLstoreURL optionsoptions erroramperror])

Con el coacutedigo anterior indicamos al persistentStoreCoordinator que la migracioacuten enel caso de que se deba de realizar sea de forma automaacutetica

AtencioacutenDeberemos de tener en cuenta que este tipo de migracioacuten que estamos estudiando en este puntosoacutelo es vaacutelida para cambios simples en Core Data lo que Apple llama como lightweightmigration Este tipo de migracioacuten admite una serie de cambios que se indican dentro de ladocumentacioacuten de Apple para cambios maacutes complejos deberemos realizar migracionespersonalizadas y mucho maacutes complejas El documento en donde Apple explica el tipo demigraciones que existen para Core Data y su forma de implementarlas lo podemos consultardesde esta web

2) Ahora deberemos de crear la versioacuten del esquema de Core Data dentro de XCode para

Persistencia

75Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ello seleccionamos el fichero del esquema sesion04_ejemplo2xcdatamodeld yseleccionamos en el menuacute principal Editor gt Add Model Version A la nueva versioacuten lallamaremos sesion04_ejemplo_2 (versioacuten 2) Una vez que hayamos pulsado sobreFinish se nos habraacute creado la nueva versioacuten del esquema dentro de la principal y estaraacutemarcada con un tick la versioacuten anterior

3) Una vez que tenemos creada la nueva versioacuten deberemos modificarla antildeadiendole elnuevo atributo Seleccionamos la versioacuten recieacuten creadasesion04_ejemplo_2xcdatamodel y antildeadimos a la entidad DetallePersona elatributo codigoPostal Atencioacuten Si este atributo lo hemos antildeadido anteriormentedeberemos de borrarlo de la versioacuten anterior y antildeadirlo en la segunda versioacuten

4) Ahora nos queda indicar a Core Data que queremos usar la versioacuten 2 de la base dedatos para nuestra aplicacioacuten Para ello seleccionamos el esquema principal (el raiz)sesion04_ejemplo2xcdatamodeld y en la ventana de la derecha en la pestantildea de FileInspector seleccionamos como Versioacuten actual la versioacuten 2 esto lo hacemos dentro delapartado de Versioned Core Data Model Automaacuteticamente veremos que el siacutembolo detick cambia a la versioacuten 2

5) Ahora ya podemos volver a compilar y veremos que no nos aparece el error y funcionatodo seguacuten lo esperado

Magical RecordExisten varias librerias de coacutedigo libre que facilitan enormemente el uso de Core Data ennuestras aplicaciones iOS Una de ellas es Magical Record Mediante esta libreriacutea podremos usarCore Data sin tener que preocuparnos de todo el proceso de configuracioacuten en las controladorasde la configuracioacuten de migraciones etc Su instalacioacuten en un proyecto de XCode es muy faacutecil yaltamente recomendable ya que proporciona una API muy clara y sencilla de usar

Persistencia

76Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios

81 Usando User Defaults (1 punto)

En este ejercicio vamos a usar las variables de usuario para almacenar y mostrar los datosdel dispositivo que estamos usando y las veces que hemos arrancado la aplicacioacuten

Crear un proyecto nuevo usando la plantilla Single View Application con el nombreejercicio-userdefaults-ios Se debe de ejecutar en iPhone e iPad (Universal) Comoidentificador de empresa escribiremos esuajtech y como prefijo de clase UA Debe detener el ARC activado (desmarcar el resto de opciones)

Ayuda meacutetodos de UIDevicePara obtener los datos del dispositivo usaremos el singleton UIDevice estos son algunos de losmeacutetodos que podemos usar [[UIDevice currentDevice] name] [[UIDevicecurrentDevice] systemName] [[UIDevice currentDevice]systemVersion] [[UIDevice currentDevice] model] [[UIDevicecurrentDevice] localizedModel]

82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)

En este ejercicio disentildearemos un modelo de datos de dos tablas relacionadas entre ellasantildeadiremos datos por coacutedigo y los mostraremos en una vista de tabla La base de datosque vamos a implementar constaraacute de dos tablas por un lado una que contendraacute los tiacutetulosde series y otra que tendraacute los detalles A la primera la llamaremos Serie y la segundaDetalleSerie Para realizar correctamente el ejercicio deberemos seguir los siguientespasos

1) Crear un proyecto nuevo usando la plantilla Master-Detail Application con elnombre ejercicio-coredata-ios Se debe de ejecutar en iPhone como identificador deempresa escribiremos esuajtech y como prefijo de clase UA Debe de tener el ARCactivado y la opcioacuten de Use Core Data (desmarcar el resto de opciones)

2) Disentildear el siguiente modelo de datos

Persistencia

77Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esquema modelo de datos

3) Revisar el fichero UAAppDelegatem y comprobar que tenemos todos los meacutetodosnecesarios para que funcione Core Data en nuestra aplicacioacuten

4) Modificar el fichero UAMasterViewControllerm para que se muestren los tiacutetulos delas series en la tabla

5) Probar la aplicacioacuten antildeadiendo unos tiacutetulos de prueba

83 Migrando datos con Core Data (05 puntos)

Una vez que hemos disentildeado la Base de Datos con Core Data vamos a modificarlaantildeadiendo un atributo maacutes a la tabla de DetalleSerie valoracion

a) iquestQueacute pasa si cambiamos el modelo de datos y arrancamos la aplicacioacuten iquestQueacute errornos da y por queacute

b) iquestCoacutemo podemos evitar este tipo de errores cuando estemos desarrollando iquestY siestamos en produccioacuten con la aplicacioacuten ya a la venta

c) Crea una nueva versioacuten del modelo

d) Modifica el coacutedigo necesario dentro de la clase UAAppDelegate para evitar este tipo deerrores cuando estemos en un entorno de produccioacuten

e) Vuelve a arrancar la aplicacioacuten y comprueba que funciona bien

Persistencia

78Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Persistencia

79Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

  • 1 Persistencia en Android ficheros y SQLite
    • 11 Introduccioacuten
    • 12 Manejo de ficheros tradicionales en Android
      • 121 Apertura de ficheros
      • 122 Ficheros como recursos
      • 123 Operar con ficheros
      • 124 Almacenar datos en la tarjeta de memoria
        • 13 Base de datos SQLite
          • 131 Content Values
          • 132 Cursores
          • 133 Trabajar con bases de datos SQLite
          • 134 La clase SQLiteOpenHelper
          • 135 Crear una base de datos sin SQLiteHelper
          • 136 Realizar una consulta
          • 137 Extraer resultados de un cursor
          • 138 Antildeadir actualizar y borrar filas
              • 2 Ejercicios - Persistencia en Android ficheros y SQLite
                • 21 Uso de ficheros (05 puntos)
                • 22 Persistencia con ficheros (05 puntos)
                • 23 Base de datos SQLiteOpenHelper (05 puntos)
                • 24 Base de datos insercioacuten y borrado (05 puntos)
                • 25 Base de datos probar nuestro adaptador (05 puntos)
                • 26 Base de datos cambios en la base de datos (05 puntos)
                  • 3 Persistencia en Android proveedores de contenidos y SharedPreferences
                    • 31 Shared Preferences
                      • 311 Guardar Shared Preferences
                      • 312 Leer Shared Preferences
                      • 313 Interfaces para Shared Preferences
                      • 314 Definiendo una pantalla de preferencias con un layout en XML
                      • 315 Controles nativos para preferencias
                      • 316 Actividades de preferencias
                      • 317 Shared Preference Change Listeners
                        • 32 Proveedores de contenidos
                          • 321 Proveedores nativos
                          • 322 Proveedores propios crear un nuevo proveedor de contenidos
                          • 323 Proveedores propios crear la interfaz de consultas
                          • 324 Proveedores propios tipo MIME
                          • 325 Proveedores propios registrar el proveedor
                          • 326 Content Resolvers
                          • 327 Otras operaciones con Content Resolvers
                              • 4 Ejercicios - Persistencia en Android proveedores de contenidos y SharedPreferences
                                • 41 Compartir datos entre actividades con Shared Preferences (075 puntos)
                                • 42 Actividad de preferencias (075 puntos)
                                • 43 Proveedor de contenidos propio (075 puntos)
                                • 44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)
                                  • 5 Persistencia de datos en iOS Ficheros y SQLite
                                    • 51 Introduccioacuten
                                    • 52 Ficheros plist (Property Lists)
                                      • 521 Leyendo datos desde ficheros plist
                                      • 522 Escribiendo datos en ficheros plist
                                        • 53 SQLite
                                          • 531 Herramientas disponibles
                                          • 532 Lectura de datos
                                          • 533 Mostrando los datos en una tabla
                                              • 6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios
                                                • 61 Creando y leyendo un fichero plist (15 puntos)
                                                • 62 Implementar vista de detalle de la serie (05 puntos)
                                                • 63 Uso baacutesico de una base de datos SQLite (1 punto)
                                                  • 7 Persistencia de datos en iOS User Defaults y Core Data
                                                    • 71 Introduccioacuten
                                                    • 72 User Defaults
                                                    • 73 Core Data
                                                      • 731 Creando el proyecto
                                                      • 732 Definiendo el modelo de datos
                                                      • 733 Probando el modelo
                                                      • 734 Generando los modelos automaacuteticamente
                                                      • 735 Creando la vista de tabla
                                                      • 736 Migraciones de bases de datos con Core Data
                                                          • 8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios
                                                            • 81 Usando User Defaults (1 punto)
                                                            • 82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)
                                                            • 83 Migrando datos con Core Data (05 puntos)
Page 2: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de

63 Uso baacutesico de una base de datos SQLite (1 punto)55

7 Persistencia de datos en iOS User Defaults y Core Data 57

71 Introduccioacuten 57

72 User Defaults 57

73 Core Data 61

8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios77

81 Usando User Defaults (1 punto) 77

82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos) 77

83 Migrando datos con Core Data (05 puntos)78

Persistencia

2Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

1 Persistencia en Android ficheros y SQLite

11 Introduccioacuten

En este moacutedulo trataremos diferentes mecanismos existentes en los sistemas Android eiOS para el almacenamiento de datos tanto en la memoria del dispositivo como enmemoria externa Comenzaremos en primer lugar tratando el manejo de ficheros enAndroid para a continuacioacuten explicar coacutemo utilizar el motor de base de datos SQLite ennuestras aplicaciones en dicho sistema A continuacioacuten presentaremos los proveedores decontenidos los cuales nos proporcionan un mecanismo estaacutendar para el intercambio deinformacioacuten entre aplicaciones Android Veremos coacutemo usar los proporcionados por elsistema y crear los nuestros propios

En las sesiones 3 y 4 comentaremos los meacutetodos maacutes usados para almacenar datos dentrode nuestras aplicaciones iOS Empezaremos explicando de forma detallada el uso de losficheros de propiedades (plist) un tipo de ficheros con formato XML que es muy usadoen sistemas iOS Continuaremos con el manejo de bases de datos SQLite dentro de lasaplicaciones iOS explicando la libreria que disponemos dentro del SDK de iPhone Poruacuteltimo en la sesioacuten 4 del moacutedulo empezaremos explicando el meacutetodo de almacenamientomediante variables de tipo User Defaults para terminar con algo maacutes complejo como es eluso de Core Data forma de almacenamiento de datos muy extendida en estos uacuteltimosantildeos dentro de los sistemas iOS

12 Manejo de ficheros tradicionales en Android

El uso de ficheros tradicionales estaacute permitido para los programas de Android aunquecomo veremos en eacutesta y otras sesiones hay otras tecnologiacuteas mucho maacutes avanzadascomo el uso del motor de base de datos SQLite y para el caso de preferencias de lasaplicaciones las SharedPreferences

121 Apertura de ficheros

En Android podemos abrir un fichero para lectura o para escritura de la siguiente forma

Abrir un fichero de salida privado a la aplicacioacutenFileOutputStream fos = openFileOutput(ficherotxtContextMODE_PRIVATE) Abrir un fichero de entradaFileInputStream fis = openFileInput(ficherotxt)

Al abrir el fichero para salida el modo ContextMODE_PRIVATE hace que eacuteste seaprivado a la aplicacioacuten Si el fichero a abrir no existe eacuteste se creariacutea Existe un paraacutemetropara permitir compartir el fichero pero lo adecuado en este caso seriacutea hacer uso deproveedores de contenidos por lo que no trataremos dicho paraacutemetro

Persistencia

3Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Por otra parte el paraacutemetro ContextMODE_APPEND permite antildeadir contenido a un ficheroque ya se hubiera creado previamente (o crear uno nuevo en el caso en el que eacuteste noexistiera)

Estos meacutetodos tan soacutelo permiten abrir ficheros en la carpeta de la aplicacioacuten por lo que sise intenta antildeadir alguacuten separador de ruta se produciraacute una excepcioacuten El fichero abiertoutilizando OpenFileOutput (usando cualquiera de los dos paraacutemetros anteriores) seguardariacutea dentro del aacuterbol de directorios del dispositivo en la carpetadatadata[paquete]files donde [paquete] seriacutea el nombre del paquete de laaplicacioacuten Se trata de ficheros de texto normales por los que se puede acceder a ellosutilizando el shell de adb y utilizando el comando cat Para acceder al shell podemoshacer lo siguiente

adb shell

Por supuesto no debemos olvidarnos de cerrar el fichero una vez vayamos a dejar deutilizarlo mediante la correspondiente llamada al meacutetodo close()

fosclose()fisclose()

122 Ficheros como recursos

Es posible antildeadir ficheros como recursos de la aplicacioacuten Para ello los almacenamos enla carpeta resraw de nuestro proyecto Estos ficheros seraacuten de soacutelo lectura y paraacceder a ellos llamaremos al meacutetodo openRawResource del objeto Resource de nuestraaplicacioacuten Como resultado obtendremos un objeto de tipo InputStream Un ejemploseriacutea el que se muestra a continuacioacuten

Resources myResources = getResources()InputStream myFile = myResourcesopenRawResource(Rrawfichero)

Antildeadir ficheros a los recursos de la aplicacioacuten es un mecanismo para disponer de fuentesde datos estaacuteticas de gran tamantildeo como podriacutea ser por ejemplo un diccionario Esto seraacuteasiacute en aquellos casos en los que no sea aconsejable (o deseable) almacenar estainformacioacuten en una base de datos

Como en el caso de cualquier otro recurso seriacutea posible guardar diferentes versiones deun mismo archivo para diferentes configuraciones del dispositivo Podriacuteamos tener porejemplo un fichero de diccionario para diferentes idiomas

123 Operar con ficheros

Una alternativa simple para escribir en un fichero o leer de eacutel es utilizar las clasesDataInputStream y DataOutputStream que nos permiten leer o escribir a partir dediferentes tipos de datos como enteros o cadenas En el siguiente ejemplo vemos coacutemo esposible guardar una cadena en un fichero y a continuacioacuten leerla

Persistencia

4Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

FileOutputStream fos = openFileOutput(ficherotxtContextMODE_PRIVATE)String cadenaOutput = new String(Contenido del ficheron)DataOutputStream dos = new DataOutputStream(fos)doswriteBytes(cadenaOutput)fosclose()

FileInputStream fin = openFileInput(ficherotxt)DataInputStream dis = new DataInputStream(fin)String string = disreadLine() Hacer algo con la cadenafinclose()

124 Almacenar datos en la tarjeta de memoria

Veamos ahora queacute pasos debemos seguir para poder escribir datos en la tarjeta dememoria (SD card) Para ello se requiere el uso del meacutetodoEnvironmentgetExternalStorageDirectory() y una instancia de la claseFileWriter tal como se muestra en el siguiente ejemplo

try File raiz = EnvironmentgetExternalStorageDirectory()if (raizcanWrite())

File file = new File(raiz ficherotxt)BufferedWriter out = new BufferedWriter(new

FileWriter(file))outwrite(Mi texto escrito desde Androidn)outclose()

catch (IOException e)

Loge(FILE IO Error en la escritura de fichero +egetMessage())

Para probarlo en el emulador necesitamos tener creada una tarjeta SD lo cual se puedeconseguir mediante la herramienta del Android SDK mksdcard

mksdcard 512M sdcardiso

Podremos cargar esta tarjeta SD en nuestro emulador seleccionando el archivo recieacutencreado dentro del apartado SD card de la ventana de edicioacuten de las propiedades delemulador Para copiar fuera del emulador el archivo que hemos grabado en la tarjeta dememoria podemos utilizar el comando

adb pull sdcardficherotxt ficherotxt

Por uacuteltimo no debemos olvidar antildeadir el permiso correspondiente en el archivo Manifestde nuestra aplicacioacuten para poder escribir datos en un dispositivo de almacenamientoexterno Para ello antildeadimos lo siguiente al elemento manifest del AndroidManifestxml(recuerda que esto no va ni dentro del elemento application ni dentro del elementoactivity)

ltuses-permission androidname=androidpermissionWRITE_EXTERNAL_STORAGEgt

Persistencia

5Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

13 Base de datos SQLite

SQLite es un gestor de bases de datos relacional y es de coacutedigo abierto cumple con losestaacutendares y es extremadamente ligero Ademaacutes guarda toda la base de datos en un uacutenicofichero Su utilidad es evidente en el caso de aplicaciones de pequentildeo tamantildeo pues eacutestasno requeriraacuten la instalacioacuten adicional de ninguacuten gestor de bases de datos Esto tambieacutenseraacute asiacute en el caso de dispositivos empotrados con recursos limitados Android incluyesoporte a SQLite y en esta seccioacuten veremos coacutemo usarlo

Por medio de SQLite podremos crear bases de datos relacionales independientes paranuestras aplicaciones Con ellas podremos almacenar datos complejos y estructuradosAunque el disentildeo de bases de datos quedaraacute fuera del contenido de este curso ya quenecesitariacuteamos de maacutes tiempo para poder tratarlo es conveniente resaltar el hecho de quelas mejores praacutecticas en este campo siguen siendo uacutetiles en el caso de aplicacionesAndroid En particular la normalizacioacuten de datos para evitar redundancia es un paso muyimportante en el caso en el que estemos disentildeando una base de datos para dispositivoscon recursos limitados

En el caso del sistema Android SQLite se implementa como una libreriacutea C compacta enlugar de ejecutarse en un proceso propio Debido a ello cualquier base de datos quecreemos seraacute integrada como parte de su correspondiente aplicacioacuten Esto reducedependencias externas minimiza la latencia y simplifica ciertos procesos como lasincronizacioacuten

NotaLas bases de datos de Android se almacenan en el directoriodatadata[PAQUETE]databases donde [PAQUETE] es el paquetecorrespondiente a la aplicacioacuten Por defecto todas las bases de datos son privadas y tan soacuteloaccesibles por la aplicacioacuten que las creoacute

131 Content Values

Para insertar nuevas filas en tablas haremos uso de objetos de la clase ContentValuesCada objeto de este tipo representa una uacutenica fila en una tabla y se encarga de emparejarnombres de columnas con valores concretos Hay que tener en cuenta que SQLite difierede muchos otros motores de bases de datos en cuanto al tipado de las columnas La ideabaacutesicamente es que los valores para las columnas no tienen por queacute ser de un uacutenico tipoes decir los datos en SQLite son deacutebilmente tipados La consecuencia de esto es que noes necesario comprobar tipos cuando se asignan o extraen valores de cada columna de unafila con lo que se mejora la eficiencia

132 Cursores

Persistencia

6Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Los cursores son una herramienta fundamental en el tratamiento de informacioacuten enAndroid y tambieacuten cuando se trabaja con bases de datos Por lo tanto en esta seccioacutenintroducimos este concepto tan importante

Cualquier consulta a una base de datos en Android devolveraacute un objeto de la claseCursor Los cursores no contienen una copia de todos los resultados de la consulta sinoque maacutes bien se trata de punteros al conjunto de resultados Estos objetos proporcionanun mecanismo para controlar nuestra posicioacuten (fila) en el conjunto de resultadosobtenidos tras la consulta

La clase Cursor incluye entre otras las siguientes funciones de navegacioacuten entreresultados

bull moveToFirst desplaza el cursor a la primera fila de los resultados de la consultabull moveToNext desplaza el cursor a la siguiente filabull moveToPrevious desplaza el cursor a la fila anteriorbull getCount devuelve el nuacutemero de filas del conjunto de resultados de la consultabull getColumnName devuelve el nombre de la columna especificada mediante un iacutendice

que se pasa como paraacutemetrobull getColumnNames devuelve un array de cadenas conteniendo el nombre de todas las

columnas en el cursorbull moveToPosition mueve el cursor a la fila especificadabull getPosition devuelve el iacutendice de la posicioacuten apuntada actualmente por el cursor

A lo largo de esta sesioacuten aprenderemos coacutemo realizar consultas a una base de datos ycomo extraer valores o nombres de columnas concretos a partir de los cursores obtenidospor medio de dichas consultas

133 Trabajar con bases de datos SQLite

Suele considerarse una buena praacutectica el crear una clase auxiliar que nos facilite lainteraccioacuten con la base de datos Se trataraacute de una clase de tipo adaptador que crearaacute unacapa de abstraccioacuten que encapsularaacute dichas interacciones De esta forma podremosantildeadir eliminar o actualizar elementos en la base de datos sin necesidad de introducir niuna uacutenica instruccioacuten en SQL en el resto del coacutedigo permitiendo ademaacutes que se hagan lascomprobaciones de tipos correspondientes

Una clase de este tipo deberiacutea incluir meacutetodos para crear abrir o cerrar la base de datosTambieacuten puede ser un lugar conveniente para publicar constantes estaacuteticas relacionadascon la base de datos como por ejemplo constantes que almacenen el nombre de la tabla olas diferentes columnas

A continuacioacuten se muestra el coacutedigo de lo que podriacutea ser un adaptador para una base dedatos estaacutendar Incluye una extensioacuten de la clase SQLiteOpenHelper que seraacute tratada enmaacutes detalle en la siguiente seccioacuten y que se utiliza para simplificar la apertura lacreacioacuten y la actualizacioacuten de la base de datos

Persistencia

7Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

import androidcontentContextimport androiddatabaseimport androiddatabasesqliteimport androiddatabasesqliteSQLiteDatabaseCursorFactoryimport androidutilLog

public class MiAdaptadorBD private static final String NOMBRE_BASE_DATOS =

mibasededatosdbprivate static final String TABLA_BASE_DATOS = mainTableprivate static final int VERSION_BASE_DATOS = 1

Nombre de la columna de clave primariapublic static final String CP_ID=_id El nombre e iacutendice de cada columna en la base de datospublic static final String COLUMNA_NOMBRE=nombrepublic static final int COLUMNA_INDICE = 1 PENDIENTE crear campos adicionales para el resto de columnas de la base de datos

Sentencia SQL para crear una nueva base de datosprivate static final String CREAR_BASE_DATOS = create table +

TABLA_BASE_DATOS + ( + CP_ID + integer primary key autoincrement +COLUMNA_NOMBRE + text not null)

Variable para almacenar la instancia de la base de datosprivate SQLiteDatabase db Contexto de la aplicacioacuten que estaacute usando la base de datosprivate final Context contexto Clase helper usada para crear o actualizar la base de datos (hablamos de ella en la siguiente seccioacuten)private miHelperBD dbHelper

Crea la base de datos con ayuda del helperpublic MiAdaptadorBD(Context _contexto)

contexto = _contextodbHelper = new miHelperBD(contexto NOMBRE_BASE_DATOS

nullVERSION_BASE_DATOS)

Abre una base de datos ya existentepublic MiAdaptadorBD open() throws SQLException

db = dbHelpergetWritableDatabase()return this

Cierra la base de datos cuando eacutesta ya no va a ser utilizadapublic void close()

dbclose()

Meacutetodo para insertar un elemento en la tabla de la base dedatos

public int insertar(MiObjeto _miObjeto) PENDIENTE crear nuevos elementos ContentValues para

representar al objeto e insertarlos en la base de datos

Devolvemos el iacutendice del nuevo elemento en la base dedatos

return index

Persistencia

8Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Meacutetodo para eliminar un elemento de la tabla de la base dedatos

Devuelve un valor booleano indicando el eacutexito o no de la operacioacutenpublic boolean eliminar(long _indiceFila)

return dbdelete(TABLA_BASE_DATOS CP_ID + = +_indiceFila null) gt 0

Meacutetodo para obtener un Cursor que apunte a todas las filascontenidas

en la tabla de la base de datospublic Cursor obtenerTodos ()

return dbquery(TABLA_BASE_DATOS new String[] CP_IDCOLUMNA_NOMBRE

null null null nullnull)

Meacutetodo para obtener un elemento determinado de la base de datos a partir de su iacutendice de filapublic MiObjeto getObjeto(long _indiceFila)

PENDIENTE obtener un cursor a una fila de la base de datos y usar sus valores para inicializar una instancia de MiObjeto

return objectInstance

Meacutetodo para actualizar un elemento de la tabla de la base dedatos

public boolean actualizar(long _indiceFila MiObjeto _miObjeto) PENDIENTE crear nuevos ContentValues a partir del

objeto y usarlos para actualizar una fila en la tabla

correspondiente

return true

Clase privada auxiliar para ayudar en la creacioacuten yactualizacioacuten

de la base de datosprivate static class miHelperBD extends SQLiteOpenHelper

public miHelperBD(Context contexto String nombreCursorFactory factory int version)

super(contexto nombre factory version)

Meacutetodo llamado cuando la base de datos no existe en eldisco

y la clase Helper necesita crearla desde ceroOverridepublic void onCreate(SQLiteDatabase _db)

_dbexecSQL(CREAR_BASE_DATOS)

Meacutetodo llamado cuando la versioacuten de la base de datos en disco es diferente a la indicada en este caso la base de datos en disco necesita ser actualizadaOverridepublic void onUpgrade(SQLiteDatabase _db int

_versionAnterior

Persistencia

9Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

int _versionNueva) Mostramos en LogCat la operacioacutenLogw(AdaptadorBD Actualizando desde la

versioacuten +_versionAnterior + a la versioacuten +_versionNueva + lo cual borraraacute los

datospreviamente almacenados)

Actualizamos la base de datos para que seadapte a la nueva versioacuten

La forma maacutes sencilla de llevar a cabo dichaactualizacioacuten es

eliminar la tabla antigua y crear una nueva_dbexecSQL(DROP TABLE IF EXISTS +

TABLA_BASE_DATOS)onCreate(_db)

134 La clase SQLiteOpenHelper

La clase SQLiteOpenHelper es una clase abstracta utilizada como medio paraimplementar un patroacuten de creacioacuten apertura y actualizacioacuten de una determinada base dedatos Al implementar una subclase de SQLiteOpenHelper podemos abstraernos de laloacutegica subyacente a la decisioacuten de crear o actualizar una base de datos de manera previa aque eacutesta deba ser abierta

En el ejemplo de coacutedigo anterior se vio coacutemo crear una subclase de SQLiteOpenHelper

por medio de la sobrecarga de sus meacutetodos onCreate y onUpgrade los cuales permitencrear una nueva base de datos o actualizar a una nueva versioacuten respectivamente

NotaEn el ejemplo anterior el meacutetodo onUpgrade simplemente eliminaba la tabla existente y lareemplazaba con la nueva definicioacuten de la misma En la praacutectica la mejor solucioacuten seriacutea migrarlos datos ya existentes a la nueva tabla

Mediante los meacutetodos getReadableDatabase y getWritableDatabase podemos abrir yobtener una instancia de la base de datos de soacutelo lectura o de lectura y escriturarespectivamente La llamada a getWritableDatabase podriacutea fallar debido a falta deespacio en disco o cuestiones relativas a permisos por lo que es una buena praacutectica teneren cuenta esta posibilidad por medio del mecanismo de manejo de excepciones de JavaEn caso de fallo la solucioacuten podriacutea pasar por al menos proporcionar una copia de soacutelolectura tal como se muestra en el siguiente ejemplo

dbHelper = new miHelperBD(contexto NOMBRE_BASE_DATOS nullVERSION_BASE_DATOS)

SQLiteDatabase dbtry

db = dbHelpergetWritableDatabase() catch (SQLiteException ex)

db = dbHelpergetReadableDatabase()

Persistencia

10Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Al hacer una llamada a cualquiera de los dos meacutetodos anteriores se invocaraacuten losmanejadores de evento adecuados Asiacute pues si por ejemplo la base de datos no existieraen disco se ejecutariacutea el coacutedigo de onCreate Si por otra parte la versioacuten de la base dedatos se hubiera modificado se disparariacutea el manejador onUpgrade Lo que sucedeentonces es que al hacer una llamada a getWritableDatabase o getReadableDatabase

se devolveraacute una base de datos nueva actualizada o ya existente seguacuten el caso

135 Crear una base de datos sin SQLiteHelper

Tambieacuten es posible crear y abrir bases de datos sin necesidad de utilizar una subclase deSQLiteHelper Para ello haremos uso del meacutetodo openOrCreateDatabase En este casoseraacute necesario tambieacuten hacer uso del meacutetodo execSQL sobre la instancia de la base dedatos devuelta por el meacutetodo anterior para ejecutar los comandos SQL que permitiraacutencrear las tablas de la base de datos y establecer las relaciones entre ellas El siguientecoacutedigo muestra un ejemplo

private static final String NOMBRE_BASE_DATOS = mibasededatosdbprivate static final String TABLA_BASE_DATOS = tablaPrincipal

private static final String CREAR_BASE_DATOS =create table + TABLA_BASE_DATOS + _id integer primare key

autoincrement +columna_uno text not null)

SQLiteDatabase db

private void crearBaseDatos() db = openOrCreateDatabase(NOMBRE_BASE_DATOS ContextMODE_PRIVATE

null)dbexecSQL(CREAR_BASE_DATOS)

NotaAunque no es necesariamente obligatorio es recomendable que cada tabla incluya un campo detipo clave primaria con autoincremento a modo de iacutendice para cada fila En el caso en el que sedesee compartir la base de datos mediante un proveedor de contenidos un campo uacutenico de estetipo es obligatorio

136 Realizar una consulta

El resultado de cualquier consulta a una base de datos seraacute un objeto de la clase CursorEsto permite a Android manejar los recursos de manera maacutes eficiente de tal forma que enlugar de devolver todos los resultados eacutestos se van proporcionando conforme senecesitan

Para realizar una consulta a una base de datos haremos uso del meacutetodo query de lainstancia correspondiente de la clase SQLiteDatabase Los paraacutemetros que requiere este

Persistencia

11Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

meacutetodo son los siguientes

bull Un booleano opcional que permite indicar si el resultado deberiacutea contener tan soacutelovalores uacutenicos

bull El nombre de la tabla sobre la que realizar la consultabull Un array de cadenas que contenta un listado de las columnas que deben incluirse en el

resultadobull Una claacuteusula where que defina las filas que se deben obtener de la tabla Se puede

utilizar el comodiacuten el cual seraacute reemplazado por los valores que se pasen a traveacutesdel siguiente paraacutemetro

bull Un array de cadenas correspondientes a argumentos de seleccioacuten que reemplazaraacutenlos siacutembolos en la claacuteusula where

bull Una claacuteusula group by que define coacutemo se agruparaacuten las filas obtenidas comoresultado

bull Un filtro having que indique que grupos incluir en el resultado en el caso en el que sehaya hecho uso del paraacutemetro anterior

bull Una cadena que describa la ordenacioacuten que se llevaraacute a cabo sobre las filas obtenidascomo resultado

bull Una cadena opcional para definir un liacutemite en el nuacutemero de resultados a devolver

NotaComo se puede observar la mayoriacutea de estos paraacutemetros hacen referencia al lenguaje SQLTratar este tema queda fuera de los objetivos de este curso por lo que en nuestros ejemplos ledaremos en la mayoriacutea de las ocasiones un valor null a estos paraacutemetros

El siguiente coacutedigo muestra un ejemplo de consultas utilizando diversos paraacutemetros

Devolver los valores de las columnas uno y tres para todas las filas sin duplicadosString[] columnas = new String[] CLAVE_ID CLAVE_COL1 CLAVE_COL3

Cursor todasFilas = dbquery(true TABLA_BASE_DATOS columnas null nullnull null

null null)

Devolvemos los valores de todas las columnas para aquellas filas cuyovalor de la columna 3 sea igual al de una determinada variable Ordenamos las filasseguacuten el valor de la columna 5 Como queremos los valores de todas las columnas le damos al paraacutemetro correspondiente a las columnas a devolver el valor null En este caso no se ha hecho uso del primer paraacutemetro booleano que esoptativoString where = CLAVE_COL3 + = + valorString orden = CLAVE_COL5Cursor miResultado = dbquery(TABLA_BASE_DATOS null where null nullnull orden)

137 Extraer resultados de un cursor

Persistencia

12Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El primer paso para obtener resultados de un cursor seraacute desplazarnos a la posicioacuten apartir de la cual queremos obtener los datos usando cualquiera de los meacutetodosespecificados anteriormente (como por ejemplo moveToFirst o moveToPosition) Unavez hecho esto hacemos uso de meacutetodos get[TIPO] pasando como paraacutemetro el iacutendice dela columna Esto tendraacute como resultado la devolucioacuten del valor para dicha columna de lafila actualmente apuntada por el cursor

String valor = miResultadogetString(indiceColumna)

A continuacioacuten podemos ver un ejemplo maacutes completo en el que se va iterando a lo largode los resultados apuntados por un cursor extrayendo y sumando los valores de unacolumna de flotantes

int COLUMNA_VALORES_FLOTANTES = 2Cursor miResultado = dbquery(Tabla null null null null nullnull)float total = 0

Nos aseguramos de que se haya devuelto al menos una filaif (miResultadomoveToFirst())

Iteramos sobre el cursordo

float valor =miResultadogetFloat(COLUMNA_VALORES_FLOTANTES)

total += valor while (miResultadomoveToNext())

float media = total miResultadogetCount()

NotaDebido a que las columnas de las bases de datos en SQLite son debilmente tipadas podemosrealizar castings cuando sea necesario Por ejemplo los valores guardados como flotantes en labase de datos podriacutean leerse maacutes adelante como cadenas

138 Antildeadir actualizar y borrar filas

La clase SQLiteDatabase proporciona los meacutetodos insert delete y update queencapsulan las instrucciones SQL requeridas para llevar a cabo estas acciones Ademaacutesdisponemos de la posibilidad de utilizar el meacutetodo execSQL el cual nos permite ejecutarcualquier sentencia SQL vaacutelida en nuestras tablas de la base de datos en el caso en el quequeramos llevar a cabo estas (u otras) operaciones manualmente

NotaCada vez que modifiques los valores de la base de datos puedes actualizar un cursor que se puedaver afectado por medio del meacutetodo refreshQuery de la clase Cursor

Para insertar una nueva fila es necesario construir un objeto de la clase ContentValues

y usar su meacutetodo put para proporcionar valor a cada una de sus columnas Para insertar la

Persistencia

13Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

nueva fila pasaremos como paraacutemetro este objeto del tipo ContentValues al meacutetodoinsert de la instancia de la base de datos junto por supuesto con el nombre de la tablaA continuacioacuten se muestra un ejemplo

Crear la nueva filaContentValues nuevaFila = new ContentValues()

Asignamos valores a cada columnanuevaFilaput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas ]

Insertar la nueva fila en la tabladbinsert(TABLA_BASE_DATOS null nuevaFila)

NotaLos ficheros como imaacutegenes o ficheros de audio no se suelen almacenar dentro de tablas en unabase de datos En estos casos se suele optar por almacenar en la base de datos una cadena con laruta al fichero o una URI

La operacioacuten de actualizar una fila tambieacuten puede ser llevada a cabo por medio del usode la clase ContentValues En este caso deberemos llamar al meacutetodo update de lainstancia de la base de datos pasando como paraacutemetro el nombre de la tabla el objetoContentValues y una claacuteusula where que especifique la fila o filas a actualizar tal comose puede ver en el siguiente ejemplo

Creamos el objeto utilizado para definir el contenido a actualizarContentValues valoresActualizar = new ContentValues()

Asignamos valores a las columnas correspondientesvaloresActualizarput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas a actualizar ]

String where = CLAVE_ID + = + idFila

Actualizamos la fila con el iacutendice especificado en la instruccioacutenanteriordbupdate(TABLA_BASE_DATOS valoresActualizar where null)

Por uacuteltimo para borrar una fila simplemente llamaremos al meacutetodo delete de lainstancia de la base de datos especificando en este caso el nombre de la tabla que se veraacuteafectada por la operacioacuten y una claacuteusula where que utilizaremos para especificar las filasque queremos borrar Un ejemplo podriacutea ser el siguiente

dbdelete(TABLA_BASE_DATOS CLAVE_ID + = + idFila null)

Persistencia

14Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

2 Ejercicios - Persistencia en Android ficheros y SQLite

21 Uso de ficheros (05 puntos)

En este ejercicio vamos a crear una aplicacioacuten que muestre un listado de cadenas porpantalla Estas cadenas se almacenaraacuten en un fichero de texto privado para la aplicacioacutenPodremos antildeadir nuevas cadenas a partir de un cuadro de edicioacuten presente en la interfazPartiremos del proyecto Ficheros proporcionado en las plantillas de la aplicacioacuten Debesseguir los siguientes pasos

bull Antildeadir un manejador al botoacuten de la actividad principal para que cada vez que seapulsado se guarde en un fichero de texto El fichero se llamaraacute ficherotxt Al abrirel fichero para escritura se utilizaraacute el paraacutemetro ContextMODE_APPEND con lo quecada nueva cadena se antildeadiraacute al final del fichero en el caso en el que eacuteste ya existieraPara escribir en el fichero utilizaremos el meacutetodo writeBytes del objetoDataOutputStream correspondiente

bull Al pulsar el botoacuten tambieacuten se deberaacute borrar el contenido del elemento EditText (leasignamos a la vista una cadena vaciacutea con el meacutetodo setText)

bull Ejecutamos la aplicacioacuten e introducimos algunas liacuteneas en el fichero Vamos acomprobar ahora que todo ha funcionado correctamente Para ello accedemos alsistema de ficheros de nuestro dispositivo ejecutando el comando adb shell

dentro de la carpeta platform-tools de nuestra carpeta de instalacioacuten del SDK deAndroid

bull Comprueba que se ha creado el fichero ficherotxt en la carpetadatadataesuajtechandroidficherosfiles Examina su contenido ejecutando cat

ficherotxt Si algo no ha salido bien siempre podraacutes eliminar el fichero con rm

ficherotxtbull En la interfaz de la actividad se ha incluido una vista de tipo TextView debajo del

botoacuten Antildeade un manejador para dicha vista de tal forma que cada vez que se hagaclick sobre ella se muestre el contenido del fichero ficherotxt Para ello leeremos elfichero liacutenea a liacutenea antildeadiendo cada una al TextView por medio del meacutetodo append

Persistencia

15Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Interfaz de la aplicacioacuten Ficheros

22 Persistencia con ficheros (05 puntos)

Seguramente habraacutes observado que si abandonas la aplicacioacuten anterior (pulsando el botoacutenBACK del dispositivo) al volver a ejecutarla el contenido del TextView ha desaparecidoDebes volver a pulsar sobre la vista para que se vuelva a mostrar el contenido del ficheroHaz las modificaciones pertinentes para que esto no sea asiacute es decir para que cuandovuelva a mostrarse la actividad tras haber sido eliminada de la memoria se muestre elTextView tal cual se veiacutea antes de abandonar la aplicacioacuten

AvisoEn este ejercicio no se estaacute pidiendo que cargues el contenido del fichero en el TextView nadamaacutes arrancar la aplicacioacuten Puede darse el caso de que lo que esteacute mostrando el TextViewcuando la actividad sea eliminada de memoria no se corresponda con el contenido actualizado delfichero

NotaPara poder completar el ejercicio deberaacutes repasar los manejadores de evento de la primera sesioacutendel moacutedulo de Android relacionados con el ciclo de vida de ejecucioacuten de actividades

23 Base de datos SQLiteOpenHelper (05 puntos)

En las plantillas de la aplicacioacuten se incluye la aplicacioacuten BaseDatos En el proyecto de laaplicacioacuten se incluye el esqueleto de una clase MiAdaptadorBD que tendremos quecompletar Se trata de un patroacuten que nos va a permitir acceder a una base de datos de

Persistencia

16Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

usuarios dentro de la aplicacioacuten sin necesidad de hacer uso de coacutedigo SQL

La clase MiAdaptadorBD incluye a su vez otro patroacuten en este caso implementado comouna subclase de SQLiteOpenHelper Eacuteste nos obliga a definir queacute ocurre cuando la basede datos todaviacutea no existe y debe ser creada y queacute ocurre si ya existe pero debe seractualizada porque ha cambiado de versioacuten Asiacute el SQLiteOpenHelper queimplementemos en este caso MiOpenHelper nos devolveraacute siempre una base de datosseparaacutendonos de la loacutegica encargada de comprobar si la base de datos existe o no

En este primer ejercicio se pide hacer lo siguiente

bull Ejecutar la sentencia de creacioacuten de bases de datos (la tenemos declarada comoconstante de la clase) en el meacutetodo MiOpenHelperonCreate

bull Implementar tambieacuten el meacutetodo onUpgrade Idealmente eacuteste deberiacutea portar las tablasde la versioacuten antigua a la versioacuten nueva copiando todos los datos Nosotros vamos aeliminar directamente la tabla que tenemos con la sentencia SQL DROP TABLE IF

EXISTS + NOMBRE_TABLA y volveremos a crearlabull En el constructor de MiAdaptadorBD debemos obtener en el campo db la base de

datos utilizando MiOpenHelper

24 Base de datos insercioacuten y borrado (05 puntos)

Continuamos trabajando con el proyecto anterior En este ejercicio completaremos elcoacutedigo relacionado con las sentencias de insercioacuten y borrado Para realizar la insercioacutenvamos a hacer uso de un mecanismo que no hemos visto en la sesioacuten de teoriacutea y queconsiste en utilizar una sentencia de insercioacuten SQL precompilada que se completaraacute conlos valores concretos a insertar en la base de datos justo antes de realizar dicha insercioacutenLa ventaja de las sentencias compiladas es que evitan que se produzcan ataques deinyeccioacuten de coacutedigo

Como se puede observar se ha incluido un atributo a la clase que representa la insercioacutenSQL

private static final String INSERT = insert into + NOMBRE_TABLA +(+COLUMNAS[1]+) values ()

En esta sentencia no se indica ninguacuten valor concreto para la columna nombre En el lugaren el que deberiacutean aparecer los valores de dicho campo se ha escrito simplemente unsiacutembolo de interrogacioacuten Tambieacuten se ha antildeadido una instancia de la claseSQLiteStatement como parte de los atributos de la clase

private SQLiteStatement insertStatement

Realizamos los siguientes pasos

bull En el constructor de la clase MiAdaptadorBD llevamos a cabo la compilacioacuten de lasentencia

thisinsertStatement = thisdbcompileStatement(INSERT)

Persistencia

17Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Una vez hecho esto cada vez que deseemos insertar un nuevo usuario en la base dedatos deberemos dar valores concretos a la columna nombre y ejecutar la sentenciaPara ello debemos antildeadir el siguiente coacutedigo dentro del meacutetodo insert de la claseMiAdaptadorBD

thisinsertStatementbindString(1 nombreUsuario)return thisinsertStatementexecuteInsert()

bull Por uacuteltimo completamos el coacutedigo del meacutetodo deleteAll cuyo contenido es eliminara todos los usuarios de la base de datos Para que ademaacutes se devuelva el nuacutemero defilas afectadas podemos insertar la siguiente liacutenea en dicho meacutetodo

return dbdelete(NOMBRE_TABLA null null)

Una vez hechos todos estos cambios podremos utilizar la clase MiAdaptadorBD parahacer operaciones con la base de datos de usuarios de manera transparente

25 Base de datos probar nuestro adaptador (05 puntos)

Antildeade coacutedigo en la actividad Main para eliminar todos los usuarios de la base de datosantildeadir dos cualesquiera y listarlos por medio de la vista de tipo TextView que seencuentra en el layout de dicha actividad

Podemos comprobar mediante la liacutenea de comandos (comando adb shell) que la basede datos ha sido creada Para ello puede ser uacutetil hacer uso de los siguientes comandos

cd datadataesuajtechandroidbasedatosdatabasessqlite3 misusuariosdbsqlitegt schemasqlitegt tablessqlitegt select from usuarios

26 Base de datos cambios en la base de datos (05 puntos)

Ahora vamos a cambiar en la clase MiAdaptadorBD el nombre de la segunda columnaque en lugar de nombre se va a llamar nombres Ejecutamos la aplicacioacuten y comprobamosque sale con una excepcioacuten Comprueba por medio de LogCat cuaacutel ha sido el erroriquestCoacutemo lo podemos solucionar

NotaPista conforme hemos programado la clase MiAdaptadorBD y siguiendo el patroacuten de disentildeode SQLiteOpenHelper es posible arreglar el problema simplemente modificando el valor deun campo

Persistencia

18Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

3 Persistencia en Android proveedores de contenidos ySharedPreferences

31 Shared Preferences

Comenzamos esta sesioacuten hablando de una de las teacutecnicas maacutes simples de persistencia enAndroid junto al uso de ficheros Shared Preferences Se trata de un mecanismo ligeropara almacenar datos basado en pares clave-valor utilizado normalmente para guardarpreferencias u opciones de la aplicacioacuten Tambieacuten puede ser utilizado para almacenar elestado de la interfaz graacutefica para contemplar el caso de que nuestra actividad debafinalizar abruptamente su ejecucioacuten Otro posible uso es el de compartir informacioacutenentre componentes de una misma aplicacioacuten

NotaLos Shared Preferences nunca son compartidos entre diferentes aplicaciones

Este meacutetodo se basa en el uso de la clase SharedPreferences Es posible utilizarla paraguardar datos en muy diversos formatos booleanos cadenas flotantes y enteros

311 Guardar Shared Preferences

Para crear o modificar un Shared Preference llamamos al meacutetodogetSharedPreferences del contexto de la aplicacioacuten pasando como paraacutemetro elidentificador del conjunto de valores de preferencias con el que queremos trabajar Pararealizar la modificacioacuten del valor de un Shared Preference hacemos uso de la claseSharedPreferencesEditor Para obtener el objeto editor invocamos el meacutetodo edit

del objeto Shared Preferences que queremos modificar Los cambios se guardan medianteel meacutetodo commit Veamos un ejemplo

int modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences = getSharedPreferences(Mispreferencias modo)

Obtenemos un editor para modificar las preferenciasSharedPreferencesEditor editor = misSharedPreferencesedit()

Guardamos nuevos valores en el objeto SharedPreferenceseditorputBoolean(isTruetrue)editorputFloat(unFloat10)editorputInt(numeroEntero12)editorputLong(otroNumero2)editorputString(unaCadenavalor)

Guardamos los cambioseditorcommit()

Persistencia

19Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

312 Leer Shared Preferences

Para leer Shared Preferences utilizamos el meacutetodo getSharedPreferences tal como seha comentado anteriormente Pasamos como paraacutemetro el identificador del conjunto deShared Preferences al que deseamos acceder Una vez hecho esto ya podemos hacer usode meacutetodos de tipo get para acceder a preferencias individuales Hay diferentes meacutetodosget para diferentes tipos de datos Cada uno de ellos requiere como paraacutemetro la claveque identifica la preferencia cuyo valor deseamos obtener y un valor por defecto el cualse usaraacute en el caso concreto en el que no existiera la preferencia con la clave pasada comoprimer paraacutemetro Podemos ver un ejemplo a continuacioacuten

public static String MIS_PREFS = MIS_PREFS

public void cargarPreferences() Obtener las preferencias almacenadasint modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences =

getSharedPreferences(MIS_PREFS modo)

boolean isTrue = misSharedPreferencesgetBoolean(isTruefalse)float unFloat = misSharedPreferencesgetFloat(unFloat0)int numeroEntero = misSharedPreferencesgetInt(numeroEntero0)long otroNumero = misSharedPreferencesgetLong(otroNumero0)String unaCadena = misSharedPreferencesgetString(unaCadena)

313 Interfaces para Shared Preferences

Android proporciona una plataforma basada en XML para la creacioacuten de interfacesgraacuteficas para el manejo de preferencias Estas interfaces tendraacuten un aspecto similar al delas del resto de aplicaciones del sistema Al utilizarla nos estaremos asegurando de quenuestras actividades para el manejo de preferencias seraacuten consistentes con las del resto deaplicaciones del sistema De esta forma los usuarios estaraacuten familiarizados con el manejode esta parte de nuestra aplicacioacuten

La plataforma consiste en tres elementos

bull Layout de la actividad de preferencias se trata de un archivo XML que definiraacute lainterfaz graacutefica de la actividad que muestre los controles para modificar los valores delas preferencias Permite especificar las vistas a mostrar los posibles valores que sepueden introducir y a queacute clave de Shared Preferences se corresponde cada vista

bull Actividad de preferencias una subclase de PreferenceActivity que secorresponderaacute con la pantalla de preferencias de nuestra aplicacioacuten

bull Shared Preference Change Listener una implementacioacuten de la claseonSharedPreferenceChangeListener que actuaraacute en el caso en el que cambie elvalor de alguna Shared Preference

314 Definiendo una pantalla de preferencias con un layout en XML

Persistencia

20Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esta es sin duda la parte maacutes importante de una actividad de preferencias Se trata de unarchivo XML utilizado para definir varios aspectos de dicha actividad Al contrario queen el caso de los archivos de layout para interfaces graacuteficas que vimos en la sesioacutencorrespondiente del moacutedulo de Android estos archivos de layout se guardan en la carpetaresxml de los recursos de la aplicacioacuten

Aunque conceptualmente estos layouts son similares a los utilizados para definirinterfaces graacuteficas los layouts de actividades de preferencias utilizan un conjuntoespecializado de componentes especiacuteficos para este tipo de actividades Estaacuten pensadospara proporcionar interfaces con un aspecto similar al del resto de actividades depreferencias del sistema Estos controles se describen en maacutes detalle en la siguienteseccioacuten

Los layout de preferencias contendraacuten un elemento PreferenceScreen

ltxml version=10 encoding=utf-8gtltPreferenceScreen

xmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceScreengt

Se pueden antildeadir maacutes elementos de este tipo si se desea Al hacerlo eacutestos serepresentaraacuten como un elemento seleccionable en un listado que mostraraacute una nuevaventana de preferencias en caso de que sea seleccionado

Dentro de cada elemento PreferenceScreen podemos incluir tantos elementos de tipoPreferenceCategory y elementos especiacuteficos para antildeadir controles como se desee Loselementos PreferenceCategory son utilizados para dividir cada pantalla de preferenciasen subcategoriacuteas mediante una barra de tiacutetulo Su sintaxis es la siguiente

ltPreferenceCategoryandroidtitle=My Preference Categorygt

ltPreferenceCategorygt

Una vez dividida la pantalla en subcategoriacuteas tan soacutelo quedariacutea antildeadir los elementoscorrespondientes a los controles especiacuteficos que veremos en la siguiente seccioacuten Losatributos de los elementos XML para estos controles pueden variar aunque existe unconjunto de atributos comunes

bull androidkey la clave de Shared Preference que se usaraacute para guardar el valor delcontrol

bull androidtitle texto a mostrar para describir la preferenciabull androidsummary una descripcioacuten maacutes larga a mostrar debajo del texto definido con

el campo anteriorbull androiddefaultValue el valor por defecto que se mostraraacute inicialmente y tambieacuten

el que finalmente se guardaraacute si ninguacuten otro valor fue asignado a este campo

El siguiente listado muestra una pantalla de preferencias muy simple con una uacutenicacategoriacutea y un uacutenico control (un check box)

ltxml version=10 encoding=utf-8gt

Persistencia

21Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltPreferenceScreenxmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceCategory

androidtitle=Mi categoriacuteagtltCheckBoxPreference

androidkey=MI_CHECK_BOXandroidtitle=Preferencia Check Boxandroidsummary=Descripcioacuten de la preferencia

Check BoxandroiddefaultValue=true

gtltPreferenceCategorygt

ltPreferenceScreengt

Esta pantalla de preferencias se mostrariacutea de la siguiente forma

Ejemplo sencillo de ventana de preferencias

315 Controles nativos para preferencias

Android incluye diferentes controles que podemos antildeadir a nuestras ventanas depreferencias por medio de los elementos XML que se indican a continuacioacuten

bull CheckBoxPreference un check box estaacutendar que puede ser utilizado parapreferencias de tipo booleano

bull EditTextPreference permite al usuario introducir una cadena como valor para unapreferencia Al seleccionar el texto se mostraraacute un diaacutelogo con el que introducir elnuevo valor

bull ListPreference el equivalente a un Spinner Seleccionar este elemento de lainterfaz mostraraacute un diaacutelogo con las diferentes opciones que es posible seleccionar Seutilizan arrays para asociar valores y textos a las opciones de la lista

bull RingtonePreference un tipo especiacutefico de preferencia de tipo listado que permiteseleccionar entre diferentes tonos de teleacutefono Esta opcioacuten es particularmente uacutetil en

Persistencia

22Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

el caso en el que se esteacute creando una pantalla de preferencias para configurar lasopciones de notificacioacuten de una aplicacioacuten o componente de la misma

Tambieacuten es posible crear nuestros propios controles personalizados definiendo unasubclase de Preference (o cualquiera de sus subclases) Se puede encontrar maacutesinformacioacuten en la documentacioacuten de AndroidhttpdeveloperandroidcomreferenceandroidpreferencePreferencehtml

316 Actividades de preferencias

Para mostrar una pantalla de preferencias debemos definir una subclase dePreferenceActivity

public class MisPreferencias extends PreferenceActivity

La interfaz graacutefica de la interfaz se puede crear a partir del layout en el meacutetodo onCreatemediante una llamada a addPreferencesFromResource

Overridepublic void onCreate(Bundle savedInstaceState)

superonCreate(savedInstanceState)addPreferencesFromResource(Rxmlmilayout)

Como en el caso de cualquier otra actividad la actividad de preferencias que hayamosdefinido debe ser incluida en el Manifest de la aplicacioacuten

ltactivity androidname=MisPreferenciasandroidlabel=Mis preferenciasgt

ltactivitygt

Eso es todo lo que necesitamos para crear este tipo de actividades y mostrar una ventanacon las preferencias definidas en el layout Esta actividad puede ser iniciada comocualquier otra mediante un Intent ya sea a traveacutes de una llamada a startActivity obien a startActivityForResult

Intent i = new Intent(this MisPreferenciasclass)startActivityForResult(i MOSTRAR_PREFERENCIAS)

Los valores para las diferentes preferencias de la actividad son almacenadas en elcontexto de la aplicacioacuten Esto permite a cualquier componente de dicha aplicacioacutenincluyendo a cualquier actividad y servicio de la misma acceder a los valores tal como semuestra en el siguiente coacutedigo de ejemplo

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto) PENDIENTE hacer uso de meacutetodos get para obtener los valores

317 Shared Preference Change Listeners

Persistencia

23Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El uacuteltimo elemento que nos queda por examinar en esta plataforma de Shared Preferenceses la interfaz onSharedPreferenceChangeListener que es utilizada para invocar unevento cada vez que una preferencia es antildeadida eliminada o modificada Para registrarListeners usamos un coacutedigo como el que se muestra a continuacioacuten en el que la parte maacutesimportante es sin duda el meacutetodo onSharedPreferenceChanged dentro del cualdeberemos determinar queacute preferencia ha cambiado de estado a partir de sus paraacutemetrospara llevar a cabo las acciones oportunas

public class MiActividad extends Activity implementsOnSharedPreferenceChangeListener

Overridepublic void onCreate(Bundle SavedInstanceState)

Registramos este objetoOnSharedPreferenceChangeListener

Context contexto = getApplicationContext()SharedPreferences prefs =

PreferenceManagergetDefaultSharedPreferences(contexto)prefsregisterOnSharedPreferenceChangeListener(this)

public void onSharedPreferenceChanged(SharedPreferences prefsString clave)

PENDIENTE comprobar queacute clave concreta ha cambiado ysu nuevo valor

para modificar la interfaz de una actividad o elcomportamiento de

alguacuten componente

Otra forma maacutes sencilla de controlar los cambios en las preferencias es registrar unlistener en el constructor de la actividad principal o aquella que haga uso de los valores depreferencias de la forma

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto)

mListener = new OnSharedPreferenceChangeListener() public void onSharedPreferenceChanged(SharedPreferences

sharedPreferences String key)

if( keyequals(MICLAVE) )boolean valor =

sharedPreferencesgetBoolean(MICLAVE false)

prefsregisterOnSharedPreferenceChangeListener(mListener)

NotaEn este segundo ejemplo mListener es una variable miembro de la clase Para este caso esnecesario registrar el listener de esta forma porque sino el garbage collector lo eliminariacutea (debidoa particularidades en la programacioacuten de los listeners asociados a las SharedPreferences)

Persistencia

24Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

32 Proveedores de contenidos

Los proveedores de contenidos o ContentProvider proporcionan una interfaz parapublicar y consumir datos identificando la fuente de datos con una direccioacuten URI queempieza por content Son una forma maacutes estaacutendar que los adaptadores (como el quevimos en la sesioacuten anterior en la seccioacuten dedicada a SQLite) de desacoplar la capa deaplicacioacuten de la capa de datos

Podemos encontrar dos tipos principales de proveedores de contenidos en Android Losproveedores nativos son proveedores que el propio sistema nos proporciona para poderacceder a datos del dispostivo incluyendo audio viacutedeo imaacutegenes informacioacuten personaletc Por otra parte tambieacuten es posible que creemos nuestros propios proveedores con talde permitir a nuestra aplicacioacuten compartir datos con el resto del sistema En esta sesioacutenhablaremos de ambos tipos

321 Proveedores nativos

Android incluye una serie de proveedores de contenidos que nos pueden proporcionarinformacioacuten de diferentes tipos informacioacuten sobre nuestros contactos informacioacuten delcalendario e incluso ficheros multimedia Ejemplos de estos proveedores de contenidosnativos son el Browser CallLog ContactsContract MediaStore Settings y elUserDictionary Para poder hacer uso de estos proveedores de contenidos y acceder alos datos que nos proporcionan debemos antildeadir los permisos adecuados al ficheroAndroidManifestxml Por ejemplo para acceder al listiacuten telefoacutenico podemos antildeadir elpermiso correspondiente de la siguiente forma

ltuses-sdk androidminSdkVersion=8 gtltuses-permission androidname=androidpermissionREAD_CONTACTSgt

ltmanifestgt

Para obtener informacioacuten de un ContentProvider debemos hacer uso del meacutetodo query

de la clase ContentResolver El primero de los paraacutemetros de dicho meacutetodo seraacute la URIdel proveedor de contenidos al que deseamos acceder Este meacutetodo devuelve un cursorque nos permitiraacute iterar entre los resultados tal como se vio en la sesioacuten anterior en elapartado de cursores

ContentResolverquery(Uri uriString[] projectionString selectionString[] selectionArgsString sortOrder)

Por ejemplo la siguiente llamada nos proporcionaraacute un cursor que nos permitiraacute acceder atodos los contactos de nuestro teleacutefono moacutevil Para ello hemos hecho uso de la constante

Persistencia

25Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ContactsContractContactsCONTENT_URI que almacena la URI del proveedor decontenidos correspondiente Al resto de paraacutemetros se le ha asignado el valor null

ContentResolver cr = getContentResolver()Cursor cursor = crquery(ContactsContractContactsCONTENT_URI

null null null null)

NotaEn versiones anteriores a Android 20 las estructuras de datos de los contactos son diferentes y nose accede a esta URI

Una vez obtenido el cursor es posible mapear sus campos con alguacuten componente de lainterfaz graacutefica de la actividad De esta forma cualquier cambio que se produzca en elcursor se reflejaraacute automaacuteticamente en el componente graacutefico sin que sea necesario queprogramemos el coacutedigo que lo refresque Para esto tenemos que llamar al meacutetodosetNotificationUri del cursor con lo cual nos aseguraremos de que los listeners seraacutennotificados cuando se produzca alguacuten cambio en los datos referenciados por dicho cursor

cursorsetNotificationUri(crContactsContractContactsCONTENT_URI)

Para asignar un adaptador a una lista de la interfaz graacutefica de la actividad maacutesconcretamente una ListView utilizamos el meacutetodo setAdapter(Adapter)

ListView lv = (ListView)findViewById(RidListView01)SimpleCursorAdapter adapter = new SimpleCursorAdapter(

getApplicationContext()Rlayouttextviewlayoutcursornew String[]

ContactsContractContacts_IDContactsContractContactsDISPLAY_NAME

new int[]RidTextView1RidTextView2)

lvsetAdapter(adapter)

En este ejemplo los identificadores RidTextView1 y RidTextView2 se correspondencon views del layout que define cada fila de la ListView como se puede ver en elarchivo textviewlayoutxml que tendriacuteamos que crear

ltxml version=10 encoding=utf-8gt

ltLinearLayout xmlnsandroid=httpschemasandroidcomapkresandroidandroidorientation=horizontalandroidlayout_width=fill_parentandroidlayout_height=wrap_contentgt

ltTextView androidid=+idTextView1androidtextStyle=bold

Persistencia

26Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

androidems=2androidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltTextView androidid=+idTextView2androidtextStyle=boldandroidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltLinearLayoutgt

La lista en siacute (identificada por RidListView01 en el presente ejemplo) estariacutea en otroXML layout por ejemplo en mainxml

Se debe tener en cuenta que en el caso de este ejemplo si quisieacuteramos obtener losnuacutemeros de teleacutefono de cada uno de nuestros contactos tendriacuteamos que recorrer el cursorobtenido a partir del proveedor de contenidos y por cada persona en nuestra agenda crearun nuevo cursor que recorriera los teleacutefonos ya que una persona puede tener asignadosvarios teleacutefonos

Puedes acceder a un listado completo de los proveedores de contenidos nativos existentesen Android consultando su manual de desarrollo Concretamente leyendo la seccioacutendedicada al paquete androidprovider enhttpdeveloperandroidcomreferenceandroidproviderpackage-summaryhtml

322 Proveedores propios crear un nuevo proveedor de contenidos

Para acceder a nuestras fuentes de datos propias de manera estaacutendar nos interesaimplementar nuestros propios ContentProvider Gracias a que proporcionamos nuestrosdatos a partir de una URI quien use nuestro proveedor de contenidos no deberaacutepreocuparse de doacutende provienen los datos indicados por dicha URI podriacutean provenir deficheros locales en la tarjeta de memoria de un servidor en Internet o de una base dedatos SQLite Dada una URI nuestro proveedor de contenidos deberaacute proporcionar en suinterfaz los meacutetodos necesarios para realizar las operaciones baacutesicas en una base de datosinsercioacuten lectura actualizacioacuten y borrado

Para crear un nuevo proveedor de contenidos definimos una subclase deContentProvider Utilizaremos una sobrecarga del meacutetodo onCreate para crear einicializar la fuente de datos que queramos hacer puacuteblica a traveacutes de dicho proveedor Unesqueleto de subclase de ContentProvide podriacutea ser el siguiente

public class MiProveedor extends ContentProvider

Overridepublic boolean onCreate()

Inicializar la base de datosreturn true

Dentro de la clase deberemos crear un atributo puacuteblico y estaacutetico de nombreCONTENT_URI en el cual almacenaremos el URI completo del proveedor que estamos

Persistencia

27Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creando Este URI debe ser uacutenico por lo que una buena idea puede ser tomar como baseel nombre de paquete de nuestra aplicacioacuten La forma general de un URI es la siguiente

content[NOMBRE_PAQUETE]provider[NOMBRE_APLICACION][RUTA_DATOS]

Por ejemplo

contentesuajtechandroidprovidermiaplicacionelementos

En general (y por simplicidad) no es necesario antildeadir la cadena provider al nombre delpaquete por lo que podemos utilizar directamente el nombre del paquete seguido de laruta de datos de la forma

contentesuajtechandroidmiaplicacionelementos

Estos URIs se pueden presentar en dos formas La URI anterior representa una consultadirigida a obtener todos los valores de ese tipo (en este caso todos los elementos) Si a laURI anterior antildeadimos un sufijo [NUMERO_FILA] estamos representando una consultadestinada a obtener un uacutenico elemento (por ejemplo el quinto elemento en el siguienteejemplo)

contentesuajtechandroidprovidermiaplicacionelementos5

NotaSe considera una buena praacutectica de programacioacuten el proporcionar acceso a nuestro proveedor decontenidos utilizando cualquiera de estas dos formas

La forma maacutes simple de determinar cuaacutel de las dos formas anteriores de URI se utilizoacutepara hacer una peticioacuten a nuestro proveedor de contenidos es mediante un Uri MatcherCrearemos y configuramos un elemento Uri Matcher para analizar una URI y determinara cuaacutel de las dos formas se corresponde En el siguiente coacutedigo se muestra el coacutedigobaacutesico que deberemos emplear para implementar este patroacuten

public class MiProveedor extends ContentProvider

private static final String miURI =contentesuajtechandroidprovidermiaplicacionelementos

public static final Uri CONTENT_URI = Uriparse(miUri)

Creamos las constantes que nos van a permitir diferenciar entre los dos tipos distintos de peticiones a partir de la URIprivate static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

Inicializamos el objeto UriMatcher Una URI que termine en elementos se corresponderaacute con una consulta para todas las filas mientras que una URI con el sufijo elementos[FILA] representaraacute una uacutenica filastatic

uriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidprovidermiaplicacion

elementos TODAS_FILAS)

Persistencia

28Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

uriMatcheraddURI(esuajtechandroidprovidermiaplicacionelementos UNA_FILA)

Overridepublic boolean onCreate()

PENDIENTE inicializar la base de datosreturn true

Esta misma teacutecnica se puede emplear para proporcionar diferentes alternativas de URIpara diferentes subconjuntos de datos como por ejemplo diferentes tablas en una base dedatos a traveacutes del mismo proveedor de contenidos

323 Proveedores propios crear la interfaz de consultas

Para permitir realizar operaciones con los datos publicados a traveacutes de nuestro proveedorde contenidos deberemos implementar los meacutetodos delete insert update y query

para el borrado insercioacuten actualizacioacuten y realizacioacuten de consultas respectivamenteEstos meacutetodos son la interfaz estaacutendar utilizada con proveedores de contenidos de formaque para compartir datos entre aplicaciones no se tendraacuten interfaces distintos paradiferentes fuentes de datos

Lo maacutes habitual suele ser implementar un proveedor de contenidos como un mecanismopara permitir el acceso a una base de datos SQLite privada a una aplicacioacuten aunque atraveacutes de estos meacutetodos indicados se podriacutea en principio acceder a cualquier fuente dedatos

El siguiente coacutedigo extiende el ejemplo anterior (MiProveedor) y muestra el esqueleto deestos meacutetodos dentro de una subclase de ContentProvider Obseacutervese que se hace usodel objeto UriMatcher para determinar que tipo de consulta se estaacute realizando

Overridepublic Cursor query(Uri uri

String[] projectionString selectionString[] selectionArgsString sort)

Si se trata de una consulta para obtener una uacutenica filalimitamos

los resultados a obtener de la fuente de datos por medio de una claacuteusula whereswitch(UriMatchermatch(uri))

case UNA_FILA PENDIENTE modifica la sentencia SELECT

mediante una claacuteusula where obteniendo el identificador de fila

como numeroFila = urigetPathSegments()get(1))

return null

Override

Persistencia

29Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public Uri insert(Uri _uri ContentValues _valores) long idFila = [ Antildeadir un nuevo elemento ]

Devolvemos la URI del elemento recieacuten antildeadidoif (idFila gt 0)

return ContentUriswithAppendendId(CONTENT_URI idFila)

throw new SQLException(No se pudo antildeadir un nuevo elemento a +_uri)

Overridepublic int delete(Uri uri String where String[] whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

Overridepublic int update(Uri uri ContentValues valores String where String[]whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

324 Proveedores propios tipo MIME

El uacuteltimo paso para crear un proveedor de contenidos es definir el tipo MIME queidentifica el tipo de datos que devuelve el proveedor Debemos sobrecargar el meacutetodogetType para que devuelva una cadena que identifique nuestro tipo de datos de manerauacutenica Se deberiacutean definir dos tipos posibles uno para el caso de una uacutenica fila y otro parael caso de devolver todos los resultados Se debe utilizar una sintaxis similar a la delsiguiente ejemplo

bull Un uacutenico elementovndesuajtechandroidmiaplicacioncursoritemmiproveedorcontent

bull Todos los elementosvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

Como se puede observar la cadena comienza siempre por vnd A continuacioacutentendriacuteamos el nombre del paquete de nuestra aplicacioacuten Lo siguiente es cursor ya quenuestro proveedor de contenidos estaacute devolviendo datos de tipo Cursor Justo antes de labarra pondremos item en el caso del tipo MIME para un uacutenico elemento y dir para elcaso del tipo MIME para todos los elementos Finalmente tras la barra pondremos elnombre de nuestra clase (en minuacutesculas) seguido de content En el siguiente coacutedigo deejemplo se muestra coacutemo sobrecargar getType seguacuten la URI

Persistencia

30Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Overridepublic String getType(Uri _uri)

switch (uriMatchermatch(_uri)) case TODAS_FILAS

returnvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

case UNA_FILAreturn

vndesuajtechandroidmiaplicacioncursoritemmiproveedorcontentdefault

throw new IllegalArgumentException(URI nosoportada + _uri)

325 Proveedores propios registrar el proveedor

No debemos olvidar incorporar nuestro proveedor de contenidos al Manifest de laaplicacioacuten una vez que lo hemos completado Esto se haraacute por medio del elementoprovider cuyo atributo authorities podremos utilizar para especificar la URI base talcomo se puede ver en el siguiente ejemplo

ltprovider androidname=MiProveedorandroidauthorities=esuajtechandroidmiaplicaciongt

326 Content Resolvers

Aunque en la seccioacuten de proveedores nativos ya se ha dado alguacuten ejemplo de coacutemo haceruso de un Content Resolver para realizar una consulta ahora que hemos podido observaren detalle la estructura de un proveedor de contenidos propio es un buen momento paraver en detalle coacutemo realizar cada una de las operaciones que eacutestos permiten

El contexto de una aplicacioacuten incluye siempre una instancia de la claseContentResolver a la cual se puede acceder mediante el meacutetodo getContentResolver

ContentResolver cr = getContentResolver()

Esta clase incorpora diversos meacutetodos para realizar consultas a proveedores decontenidos Cada uno de estos meacutetodos acepta como paraacutemetro una URI que indica conqueacute proveedor de contenidos se desea interactuar

Las consultas realizadas sobre un proveedor de contenidos recuerdan a las consultasrealizadas sobre bases de datos Los resultados de las consultas se devuelven comoobjetos de la clase Cursor Se pueden extraer valores del cursor de la misma forma en laque se explicoacute en el apartado de bases de datos de la sesioacuten anterior El meacutetodo query dela clase ContentResolver acepta los siguientes paraacutemetros

bull La URI del proveedor del contenidos al que se desea realizar la consultabull Una proyeccioacuten que enumera las columnas que se quieren incluir en los resultados

obtenidosbull Una claacuteusula where que define las filas a obtener Se pueden utilizar comodines que

Persistencia

31Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

se reemplazaraacuten por valores incluidos en el siguiente paraacutemetrobull Una matriz de cadenas que se pueden utilizar para reemplazar los comodines en la

claacuteusula where anteriorbull Una cadena que describa la ordenacioacuten de los elementos devueltos

El siguiente coacutedigo muestra un ejemplo

ContentResolver cr = getContentResolver() Devolver todas las filasCursor todasFilas = crquery(MiProveedorCONTENT_URI null null nullnull) Devolver todas columnas de las filas para las que la columna 3 tiene un valor determinado ordenadas seguacuten el valor de la columna 5String where = COL3 + = + valorString orden = COL5Cursor algunasFilas = crquery(MiProveedorCONTENT_URI null where nullorden)

327 Otras operaciones con Content Resolvers

Para realizar otro tipo de operaciones con los datos de un proveedor de contenidosharemos uso de los meacutetodos delete update e insert de un objeto ContentResolver

Con respecto a la insercioacuten la clase ContentResolver proporciona en su interfaz dosmeacutetodos diferentes insert y bulkInsert Ambos aceptan como paraacutemetro la URI delproveedor de contenidos pero mientras que el primer meacutetodo tan solo toma comoparaacutemetro un objeto de la clase ContentValues (ya vistos en la seccioacuten de bases dedatos) el segundo toma como paraacutemetro una matriz de elementos de este tipo El meacutetodoinsert devolveraacute la URI del elemento recieacuten antildeadido mientras que bulkInsert

devolveraacute el nuacutemero de filas correctamente insertadas

Obtener el Content ResolverContentResolver cr = getContentResolver()

Crear una nueva fila de valores a insertarContentValues nuevosValores = new ContentValues()

Asignar valores a cada columnanuevosValoresput(NOMBRE_COLUMNA nuevoValor)[ Repetir para cada columna ]

Uri miFilaUri = crinsert(MiProveedorCONTENT_URI nuevosValores)

Insertamos ahora una matriz de nuevas filasContentValues[] arrayValores = new ContentValues[5] PENDIENTE rellenar el array con los valores correspondientesint count = crbulkInsert(MiProveedorCONTENT_URI arrayValores)

Para el caso del borrado hacemos uso del meacutetodo delete del Content Resolver pasandocomo paraacutemetro la URI de la fila que deseamos eliminar de la fuente de datos Otraposibilidad es incluir una claacuteusula where para eliminar muacuteltiples filas Ambas teacutecnicas semuestran en el siguiente ejemplo

ContentResolver cr = getContentResolver()

Persistencia

32Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Eliminar una fila especiacuteficacrdelete(miFilaUri null null) Eliminar las primeras cinco filasString where = _id lt 5crdelete(MiProveedorCONTENT_URI where null)

Una actualizacioacuten se lleva a cabo mediante el meacutetodo update del objeto ContentResolver Este meacutetodo toma como paraacutemetro la URI del proveedor de contenidosobjetivo un objeto de la clase ContentValues que empareja nombres de columna convalores actualizados y una claacuteusula where que indica queacute filas deben ser actualizadas Elresultado de la actualizacioacuten seraacute que en cada fila en la que se cumpla la condicioacuten de laclaacuteusula where se cambiaraacuten los valores de las columnas especificados por el objeto de laclase ContentValues tambieacuten se devolveraacute el nuacutemero de filas correctamenteactualizadas A continuacioacuten se muestra un ejemplo

ContentValues nuevosValores = new ContentValues()

Utilizamos el objeto ContentValues para especificar en queacute columnas queremos que se produzca un cambio y cuaacutel debe ser el nuevo valor de dichas columnasnuevosValuesput(NOMBRE_COLUMNA nuevoValor)

Aplicamos los cambios a las primeras cinco filasString where = _id lt 5

getContentResolver()update(MiProveedorCONTENT_URI nuevosValores wherenull)

Persistencia

33Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

4 Ejercicios - Persistencia en Android proveedores de contenidos ySharedPreferences

41 Compartir datos entre actividades con Shared Preferences (075 puntos)

Descarga de las plantillas el proyecto Dni Dicho proyecto estaacute compuesto de dosactividades Formulario y Resumen El objetivo del ejercicio es conseguir que al pulsar elbotoacuten Siguiente en la actividad Formulario se muestre en la actividad Resumen cuaacutelesfueron los datos introducidos

En este ejercicio vamos a hacer uso de SharedPreferences para pasar los datos de unaactividad a la siguiente Al pulsar en Siguiente en Formulario deberemos guardar el valorde todos los campos mediante esta plataforma Al mostrarse la actividad Resumendeberemos leer estos datos tambieacuten a partir de SharedPreferences para mostrarlos porpantalla

Por uacuteltimo crea un meacutetodo que valide el DNI (debe tratarse de una secuencia de ochodiacutegitos entre 0 y 9 y una letra al final) Si se pulsa Siguiente y el DNI no tiene el formatocorrecto no se deberaacute pasar a la actividad Resumen En lugar de esto se mostraraacute unToast en pantalla con un mensaje de error

42 Actividad de preferencias (075 puntos)

En este ejercicio seguimos trabajando con el proyecto del ejercicio anterior Vamos aantildeadir un menuacute de opciones que permita escoger en queacute momento se comprobaraacute lavalidez del DNI Para ello antildeade en primer lugar un menuacute a la actividad Formulario conuna uacutenica opcioacuten cuyo nombre seraacute Opciones

Al seleccionar esta opcioacuten se deberaacute mostrar una actividad de preferencias cuyo aspectoseraacute el siguiente

Persistencia

34Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Preferencias de la aplicacioacuten Dni

El significado de las opciones seraacute el siguiente

bull En el campo con esta opcioacuten seleccionada al pulsar sobre el campo de DNI en elformulario deberaacute mostrarse un Toast indicando si la sintaxis del DNI es correcta ono

bull Al pulsar si se desactiva esta opcioacuten (que deberaacute estar activada por defecto) no seharaacute la comprobacioacuten del DNI al pulsar el botoacuten Siguiente por lo que siempre seraacuteposible pasar de la actividad Formulario a la actividad Resumen sea cual sea elformato del DNI introducido

bull El uacuteltimo check box se encontraraacute siempre deshabilitado iquestQueacute atributo debemosutilizar para conseguir esto

Crea dos variables booleanas llamadas enElCampo y alPulsar Su valor dependeraacute delestado de los checkboxes anteriores y se actualizaraacute por medio del eventoonSharedPreferenceChange Utiliza el valor de estas variables en el coacutedigo de tuactividad para que esta tenga el comportamiento indicado

43 Proveedor de contenidos propio (075 puntos)

Vamos a implementar otra forma de acceder a la base de datos de usuarios de laaplicacioacuten BaseDatos desarrollada durante los ejercicios de la sesioacuten anterior siguiendoesta vez el patroacuten de disentildeo ContentProvider de Android Seguiremos trabajando puescon dicho proyecto

bull Creamos una nueva clase llamada UsuariosProvider que herede deContentProvider Esto nos obligaraacute a sobrecargar una serie de meacutetodos abstractosAntes de implementar la query vamos a configurar el provider

bull Antildeadimos algunos campos tiacutepicos de los content provider

Persistencia

35Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public static final Uri CONTENT_URI =Uriparse(contentesuajtechandroidbasedatosusuarios)

private static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

staticuriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

TODAS_FILAS)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

UNA_FILA)

bull Vamos a acceder a la misma base de datos de usuarios utilizada en los ejercicios de lasesioacuten anterior pero no vamos a hacerlo a traveacutes del adaptador que tuvimos queimplementar sino que vamos a copiar de eacutel el coacutedigo que nos haga falta Copia loscampos que definen el nombre de la base de datos de la tabla de las columnas laversioacuten asiacute como la referencia al contexto y a la base de datos La sentenciacompilada del insert ya no va a hacer falta Inicializa los valores necesarios en elconstructor Para inicializar la referencia a la base de datos vamos a utilizar una vezmaacutes MiOpenHelper Podemos copiarlo del adaptador que ya implementamos en losejercicios de la sesioacuten anterior

bull Implementa de forma apropiada el getType para devolver un tipo MIME diferenteseguacuten si se trata de una URI de una fila o de todas las filas Para ello ayuacutedate deluriMatcher

bull Implementa el meacutetodo query Simplemente se trata de devolver el cursor queobtenemos al hacer una consulta a la base de datos SQLite Algunos de los paraacutemetrosque le pasaremos los recibimos como paraacutemetros del meacutetodo del provider Los que notengamos iraacuten con valor null

bull Aunque no tenemos todos los meacutetodos del UsuariosProvider implementadospodemos probarlo Para ello debemos registarlo en el AndroidManifestxml

ltprovider androidname=UsuariosProvider

androidauthorities=esuajtechandroidbasedatosgtltapplicationgt

ltmanifestgt

bull En la clase Main se antildeadioacute codigo para insertar una serie de valores en la base dedatos y mostrarlos en el campo de texto Manteniendo este coacutedigo vamos a antildeadir alcampo de texto el resultado obtenido con la consulta del UsuariosProvider paracomprobar que tanto el adaptador de la base de datos como el proveedor nosdevuelven el mismo resultado

44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)

Porque es la forma estaacutendar que establece Android de acceder a contenidos Ademaacutes elproveedor de contenidos nos permitiraacute notificar al ContentResolver de los cambiosocurridos Asiacute componentes en la pantalla podraacuten refrescarse de forma automaacutetica

Persistencia

36Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Utiliza el proyecto ProveedorContenidos de las plantillas Implementa la insercioacuten enel proveedor de contenidos Prueacutebala insertando algunos usuarios de ejemplo en laclase Main Implementa tambieacuten el OnClickListener del botoacuten que inserta nuevosusuarios El nombre del nuevo usuario iraacute indicado en el EditText

bull Comprueba que la insercioacuten funciona y que gracias a la siguiente liacutenea y al estarusando un proveedor de contenidos la lista se actualiza automaacuteticamente cuandoocurre alguacuten cambio sin necesidad de pedir expliacutecitamente la actualizacioacuten al pulsarel botoacuten

cursorsetNotificationUri(cr UsuariosProviderCONTENT_URI)

NotaEsto no va a funcionar si se notifica que se ha producido un cambio al insertar o borrar unelemento Para ello usamosgetContext()getContentResolver()notifyChange(UsuariosProviderCONTENT_URI null)

bull Implementa el meacutetodo delete del proveedor de contenidos Prueacutebalo en la claseMain Termina de implementar el onCreateContextMenuListener que se ejecutaraacutecada vez que se haga una pulsacioacuten larga sobre alguna entrada de la lista Compruebaque funciona (eliminando el usuario correspondiente y no otro)

Persistencia

37Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

5 Persistencia de datos en iOS Ficheros y SQLite

51 Introduccioacuten

La persistencia de datos en iOS se realiza en una pequentildea memoria flash que tienenuestro dispositivo la cual es equivalente a un disco duro de tamantildeo limitado Los datosque ahiacute se almacenen se conservaraacuten aunque el dispositivo se apague Por motivos deseguridad el SO de iOS no permite que las aplicaciones accedan directamente a lamemoria interna pero a cambio existe una API completa de acceso a la porcioacuten dememoria que le corresponde a la app que desarrollemos

En una aplicacioacuten iOS encontraremos baacutesicamente 4 modos de almacenamiento deinformacioacuten de forma persistente

bull Ficheros de propiedades (plist)bull Bases de datos embebidas SQLitebull Datos de usuario (User Defaults)bull Core Data

En esta primera sesioacuten veremos coacutemo usar los ficheros de texto (Property Lists) y elalmacenamiento en base de datos de tipo SQLite

52 Ficheros plist (Property Lists)

El almacenamiento de datos en ficheros de propiedades es uno de los maacutes usados en eldesarrollo de las aplicaciones para iOS Se usa principalmente para guardar datos deconfiguracioacuten de la aplicacioacuten u otra informacioacuten poco compleja y acceder a ella deforma sencilla Si estamos desarrollando un juego podemos usarlos para la definicioacuten delos niveles almacenar las puntuaciones del jugador etc

521 Leyendo datos desde ficheros plist

Los ficheros plist (Property Lists) tienen un formato XML aunque XCode nosproporciona una interfaz que nos permite acceder a ellos de forma muy sencilla Paraentender el funcionamiento de este tipo de ficheros vamos a realizar un ejemplo en el queprimero crearemos un fichero plist lo completaremos con datos de ejemplo y despueacutes loleeremos para mostrarlo por pantalla

Un ejemplo de un fichero plist podriacutea ser el siguiente

Persistencia

38Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Fichero plist

Antes de nada creamos un proyecto nuevo en XCode de tipo Window-based

Application al que llamaremos sesion03-ejemplo1 Para crear un fichero plistdebemos hacer click derecho sobre el directorio Supporting Files de nuestro proyectoy seleccionamos iOS gt Resource gt Property List Lo guardaremos con el nombreconfigUsuario Seguidamente ya podemos acceder al fichero recieacuten creado haciendoclick sobre el en este momento XCode nos mostraraacute un pequentildeo editor totalmente vacioen el que iremos rellenando con datos Podemos verlo tambieacuten en formato XML sihacemos click derecho sobre el y seleccionamos Open As gt Preview

Para empezar a rellenar el fichero que acabamos de crear hacemos click derecho dentrode su contenido (ahora vacio) y seleccionamos Add row Entonces nos apareceraacute unafila vaciacutea Dentro del campo Key escribimos la clave un nombre descriptivo quedeberemos utilizar maacutes adelante para acceder a los datos En nuestro caso vamos aescribir config como clave Dentro de la columna Type seleccionamosDictionary Veremos que aparece una flecha en el lado izquierdo que indica quepueden haber subniveles dentro de nuestro diccionario

Ahora antildeadimos un nuevo item que cuelgue del diccionario que acabamos de crear paraello hacemos click sobre la flecha de la izquierda esta se giraraacute hacia abajo entonceshacemos click ahora sobre el siacutembolo + (maacutes) Nos apareceraacute una nueva liacutenea que cuelgadel diccionario

En esta nueva liacutenea dentro del campo key escribimos nombre el campo type lodejamos como de tipo String ya que va a ser una cadena de texto y en la columna deValue escribimos nuestro nombre por ejemplo Javi

Ahora vamos a antildeadir otro campo nuevo en nuestra configuracioacuten para ello hacemosclick sobre el siacutembolo + y nos apareceraacute una nueva liacutenea justo al mismo nivel que laanterior y colgando del diccionario Dentro del campo de Key escribimos ciudad yen Value escribimos Alicante El campo de Type lo dejamos como String

Persistencia

39Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora dentro de nuestro fichero plist vamos a indicar los dispositivos que dispone elusuario para ello creamos una nueva linea dentro del diccionario y le pondremos comoclave dispositivos Seleccionamos el tipo Array y seguimos los mismos pasosque al crear el diccionario para ello hacemos click sobre la flecha de la izquierda ydespueacutes sobre el siacutembolo + (maacutes) Ahora nos apareceraacute una nueva fila con clave Item 0Seleccionamos el tipo String y en el campo Value iPhone

Ahora repetimos el paso 3 veces indicando como values iPad Android y

Blackberry

Una vez seguidos todos estos pasos tendremos nuestro fichero de propiedades listo Debede quedar como se muestra a continuacioacuten

Fichero plist terminado

Este fichero se almacenaraacute dentro del directorio de bundle cuando compilemos laaplicacioacuten En el caso de que queramos escribir en el debermos almacenar el ficherodentro del directorio de Documents del binario

Ahora desde nuestro coacutedigo vamos a acceder a eacutel y a mostrarlo por pantalla para elloescribimos el siguiente coacutedigo dentro del meacutetodo didFinishLaunchingWithOptions dela clase delegada

Cargamos el fichero PLISTNSString mainBundlePath = [[NSBundle mainBundle] bundlePath]NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]NSDictionary diccionario = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionario delraiz

NSString nombre = [dicConfig objectForKeynombre]

Persistencia

40Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

[selfwindow makeKeyAndVisible]return YES

Si todo ha ido correctamente deberemos obtener la siguiente salida por la consola

Lista de dispositivos

522 Escribiendo datos en ficheros plist

Partiendo del ejemplo anterior vamos a escribir ahora en el fichero para ello primerodebemos comprobar que el fichero plist se encuentre dentro del directorio de Documents

del dispositivo en el caso de que no lo esteacute (por ejemplo si es la primera vez que arrancala aplicacioacuten) debemos copiarlo a ese directorio Una vez que tenemos el fichero dentrodel directorio de documentos ya podemos escribir en el

Para realizar todo el proceso de comprobacioacuten de la existencia del fichero dentro deldirectorio de documentos copiarlo en ese directorio y leer los datos debemos de sustituirel coacutedigo que hay dentro del meacutetodo didFinishLaunchingWithOptions de la clasedelegada por este otro

AtencioacutenPara que funcione la escritura en un fichero plist este debe de estar dentro del directorio deDocuments de nuestro dispositivo iOS no en el boundle

NSDictionary diccionarioRaiz

Ruta del directorio de documentos de nuestro dispositivoNSArray paths =

NSSearchPathForDirectoriesInDomains(NSDocumentDirectoryNSUserDomainMask YES)

if ([paths count] gt 0)

Persistencia

41Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

Primero comprobamos si existe el fichero en eldirectorio de documentos

BOOL fileExists = [[NSFileManager defaultManager]fileExistsAtPathdocumentsFilename]if (fileExists)

NSLog(Fichero encontrado en directorio de documentosOK

documentsFilename)

Si existe ya cargamos los datos desde aquidirectamente

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

else

NSLog(No se encuentra el fichero configUsuarioplisten

el directorio de documentos-gtLo creamos)

Si no existe primero cargamos el fichero PLISTdesde el Boundle

NSString mainBundlePath = [[NSBundle mainBundle]bundlePath]

NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Y despues escribimos los datos cargados (eldiccionario completo)

a un fichero nuevo de la carpeta de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

NSLog(plist de configUsuario creado)

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionariodel raiz

NSString nombre = [dicConfig objectForKeynombre]NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

Persistencia

42Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Por ultimo liberamos la memoria del diccionario raiz[diccionarioRaiz release]

[selfwindow makeKeyAndVisible]return YES

Para comprobar el funcionamiento arrancamos la aplicacioacuten y veremos que la primera vezse crearaacute el fichero dentro de la carpeta de Documents de nuestro dispositivo y en lassiguientes ya no se crearaacute y simplemente lo leeraacute desde la carpeta de Documents

Ahora que ya tenemos la gestioacuten de ficheros baacutesica completada vamos a escribir sobre elalguacuten dato Para ello simplemente escribimos el siguiente fragmento de coacutedigo justo antesde la liacutenea [selfwindow makeKeyAndVisible]

Cambiamos el nombre y lo guardamos en el fichero Ruta del directorio de documentos de nuestro dispositivo

if ([paths count] gt 0)

Cargamos el fichero desde el directorio dedocumentos del dispositivo

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cambiamos el valor del nombre en el diccionario[dicConfig setValueOtro nombre forKeynombre]

Escribimos todo el diccionario de nuevo al fichero del directorio de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

Por ultimo liberamos el diccionario de lamemoria

[diccionarioRaiz release]

Lo que hemos hecho en el coacutedigo anterior es cargar todo el diccionario de nuevo desde eldirectorio de documentos modificar los datos que queremos cambiar o incluso antildeadir ydespueacutes guardar el diccionario en el fichero

Volvemos a arrancar la aplicacioacuten y veremos que ha cambiado el campo de nombre

Nota

Persistencia

43Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

La lectura y escritura de datos en un fichero plist se debe de hacer de una soacutela vez nunca porpartes Siempre se debe de cargar o escribir un diccionario NSDictionary como elementoprincipal del fichero

53 SQLite

SQLite es una base de datos relacional ligera con la que se puede trabajar de formasencilla mediante sentencias SQL La base de datos se almacenaraacute en un fichero dentrode nuestra aplicacioacuten Este meacutetodo de persistencia puede ser el adecuado cuando los datosa almacenar esteacuten en un formato de tablas filas y columnas y se necesite que los datossean accesibles de forma raacutepida

531 Herramientas disponibles

Para explicar el funcionamiento de las librerias de SQLite de Cocoa Touch vamos a seguirun ejemplo completo Comenzamos creando la base de datos para ello podemos utilizaruno de los frontends que facilitan la gestioacuten del SQLite uno bastante sencillo yrecomendado es el SQLite Database Browser y otro es fmdb

Nosotros utilizaremos el primero (SQLite Database Browser) Abrimos la aplicacioacuten yhacemos click sobre el botoacuten de crear nueva base de datos que llamaremospersonasDBsqlite Seguidamente creamos las tablas para simplificar en nuestroejemplo crearemos una soacutela tabla llamada personas Esta tabla tendraacute las siguientescolumnas id (NUMERIC) nombre (TEXT) apellidos (TEXT) dni (TEXT) edad

(NUMERIC) y localidad (TEXT)

Persistencia

44Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Estructura de la Base de Datos

Una vez creada la tabla pasamos a insertar datos dentro de ella para ello abrimos lapestantildea de Browse Data y hacemos click en New Record Cuando tengamos al menos 3registros metidos dentro de la tabla ya podemos guardar la base de datos Hacemos clicksobre guardar le ponemos el nombre que queramos y elegimos cualquier directorio delMAC

Datos de la Base de Datos

532 Lectura de datos

Una vez que tenemos la base de datos SQLite creada ya podemos empezar a escribir elcoacutedigo para leer y mostrar los datos dentro de nuestra aplicacioacuten

Primero empezamos creando un proyecto nuevo en XCode usando la plantilla basada envistas View-based application y lo llamaremos sesion03-ejemplo2 Ahora antildeadimosel framework de sqlite a nuestro proyecto para ello hacemos click sobre el raiz delproyecto y seleccionamos la pestantildea Build Phases Desplegamos el listado de Link

Binary With Libraries y hacemos click sobre el botoacuten (+) El el listadoseleccionamos el framework libsqlite3dylib y hacemos click en Add

Persistencia

45Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Antildeadiendo el framework libsqlite3dylib

Ahora antildeadimos a nuestro proyecto la base de datos que hemos creado anteriormentepara ello arrastramos el fichero personasDBsqlite a nuestro directorio Supporting

Files Asegurate de seleccionar la opcioacuten de Copy items to destination groups

folder (if needed) para que se copie el fichero dentro de la carpeta del proyecto

Para acelerar el proceso de lectura de la base de datos y ahorrar memoria vamos a cargarprimero todos los datos en objetos para ello vamos a crear una clase que se encargue dealmacenar los datos Hacemos click derecho sobre el raiz del proyecto New file gtObjective-C class seleccionamos NSObject como clase principal y le damos a NextEscribimos como nombre de la clase Persona y hacemos click en save

Personah

imports iniciales

interface Persona NSObject int _uIdNSString _nombreNSString _apellidos

Persistencia

46Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSInteger _edadNSString _localidad

property (nonatomic assign) int uIdproperty (nonatomic copy) NSString nombreproperty (nonatomic copy) NSString apellidosproperty (nonatomic) NSInteger edadproperty (nonatomic copy) NSString localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidos edad(NSInteger)edadlocalidad(NSString )localidad

end

Personam

import Personah

implementation Persona

synthesize uId = _uIdsynthesize nombre = _nombresynthesize apellidos = _apellidossynthesize edad = _edadsynthesize localidad = _localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidosedad(NSInteger)edad localidad(NSString )localidad

if ((self = [super init])) selfuId = uIdselfnombre = nombreselfapellidos = apellidosselfedad = edadselflocalidad = localidad

return self

- (void) dealloc selfnombre = nilselfapellidos = nilselflocalidad = nil[super dealloc]

end

Una vez creada la clase Persona vamos a crear la clase encargada de manejar la basede datos sqlite3 Otra opcioacuten seriacutea cargar todos los datos directamente desde la clasedelegada al arrancar la aplicacioacuten pero no es recomendable ya que si en alguacuten momentotenemos que cambiar de motor de base de datos lo tendremos maacutes complicado

Persistencia

47Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creamos entonces una clase dentro de nuestro proyecto llamada BDPersistencia queherede de NSObject y escribimos el siguiente coacutedigo

BDPersistenciah

imports iniciales

interface BDPersistencia NSObject sqlite3 _database

+ (BDPersistencia)database- (NSArray )arrayPersonas

end

BDPersistenciam

import BDPersistenciahimport Personah

implementation BDPersistencia

static BDPersistencia _database

+ (BDPersistencia)database if (_database == nil)

_database = [[BDPersistencia alloc] init]return _database

- (id)init if ((self = [super init]))

NSString sqLiteDb = [[NSBundle mainBundle]pathForResourcepersonasDB

ofTypesqlite]

if (sqlite3_open([sqLiteDb UTF8String]amp_database) = SQLITE_OK)

NSLog(Fallo al abrir la BD)

return self

- (void)dealloc sqlite3_close(_database)[super dealloc]

- (NSArray )arrayPersonas

NSMutableArray arraySalida = [[[NSMutableArray alloc]init]

autorelease]

Persistencia

48Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString query = SELECT id nombre apellidoslocalidad edad

FROM personas ORDER BY apellidos DESC

sqlite3_stmt statementif (sqlite3_prepare_v2(_database [query UTF8String]

-1ampstatement nil)

== SQLITE_OK) while (sqlite3_step(statement) == SQLITE_ROW)

int uniqueId = sqlite3_column_int(statement0)

char nombreChar = (char )sqlite3_column_text(statement 1)

char apellidosChar = (char )sqlite3_column_text(statement 2)

char localidadChar = (char )sqlite3_column_text(statement 3)

int edad = sqlite3_column_int(statement 4)

NSString nombre = [[NSString alloc]initWithUTF8StringnombreChar]NSString apellidos = [[NSString alloc]initWithUTF8StringapellidosChar]NSString localidad = [[NSString alloc]initWithUTF8StringlocalidadChar]Persona persona = [[Persona alloc]initWithUiduniqueId nombrenombreapellidosapellidos edadedad

localidadlocalidad]

[arraySalida addObjectpersona][nombre release][apellidos release][localidad release][persona release]

sqlite3_finalize(statement)

return arraySalida

end

Con esto ya podemos leer de nuestra base de datos las personas Para ver que funcionatodo a la perfeccioacuten escribimos el siguiente coacutedigo dentro de la clase delegada en elmeacutetodo applicationDidFinishLaunching Antes debemos incluir en la parte superiorlas clases creadas

import BDPersistenciahimport Personah

NSArray personas = [BDPersistenciadatabase]arrayPersonas

for (Persona persona in personas) NSLog(d d personauId

personanombrepersonaapellidos personalocalidad personaedad)

Persistencia

49Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Si ejecutamos el proyecto obtendremos la siguiente salida por consola

Listado de personas

533 Mostrando los datos en una tabla

Una vez que tenemos el coacutedigo para obtener todas las filas de la tabla de personas de labase de datos queda lo maacutes faacutecil mostrarlas dentro de una vista de la aplicacioacuten En estecaso utilizaremos la vista de tabla UITableView

Comenzamos creando una nueva vista en nuestro proyecto para ello hacemos clickderecho sobre el raiz y seleccionamos New File gt UIVIewController SubclassAsegurate de marcar Subclass of UITableViewController y la opcoacuten de With

XIB for user interface Hacemos click en Next y guardamos el fichero comoPersonasViewController por ejemplo

Ahora abrimos el fichero PersonasViewControllerh y antildeadimos un NSArray queseraacute el que almacene las personas de la base de datos

imports iniciales

interface PersonasViewController UITableViewController

NSArray _listadoPersonas

property (nonatomic retain) NSArray listadoPersonas

end

Ahora abrimos el fichero PersonasViewControllerm antildeadimos el syntetizeimportamos los ficheros de gestioacuten de la base de datos la clase Persona y todo el coacutedigobaacutesico para la gestioacuten de la vista de la tabla

import PersonasViewControllerhimport Personahimport BDPersistenciah

implementation PersonasViewController

synthesize listadoPersonas = _listadoPersonas

Persistencia

50Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

- (id)initWithStyle(UITableViewStyle)style

self = [super initWithStylestyle]if (self)

Custom initializationreturn self

- (void)dealloc

[super dealloc]

selflistadoPersonas = nil

- (void)didReceiveMemoryWarning

Releases the view if it doesnt have a superview[super didReceiveMemoryWarning]

Release any cached data images etc that arent inuse

pragma mark - View lifecycle

- (void)viewDidLoad

[super viewDidLoad]

selftitle = Listado de personas

selflistadoPersonas = [BDPersistenciadatabase]arrayPersonas

- (void)viewDidUnload

[super viewDidUnload] Release any retained subviews of the main view eg selfmyOutlet = nil

- (void)viewWillAppear(BOOL)animated

[super viewWillAppearanimated]

- (void)viewDidAppear(BOOL)animated

[super viewDidAppearanimated]

- (void)viewWillDisappear(BOOL)animated

[super viewWillDisappearanimated]

- (void)viewDidDisappear(BOOL)animated

[super viewDidDisappearanimated]

- (BOOL)shouldAutorotateToInterfaceOrientation

Persistencia

51Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

(UIInterfaceOrientation)interfaceOrientation

Return YES for supported orientationsreturn (interfaceOrientation ==

UIInterfaceOrientationPortrait)

pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView(UITableView)tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection

(NSInteger)section

Return the number of rows in the sectionreturn [selflistadoPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath

(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]if (cell == nil)

cell = [[[UITableViewCell alloc]initWithStyleUITableViewCellStyleSubtitle

reuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona persona = (Persona )[selflistadoPersonas

objectAtIndexindexPathrow]celltextLabeltext = [NSString stringWithFormat

(d) personanombrepersonaapellidos personaedad]celldetailTextLabeltext = personalocalidad

return cell

pragma mark - Table view delegate

- (void)tableView(UITableView )tableViewdidSelectRowAtIndexPath

(NSIndexPath )indexPath

Navigation logic may go here Create and pushanother view controller

end

Persistencia

52Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora soacutelo nos queda incorporar la vista que acabamos de crear dentro de nuestraaplicacioacuten Para ello vamos a llamarla desde el Delegate y a meterla dentro de unNavigation Controller Primero debemos de antildeadir un Outlet en la clasedelegada hacia un Navigation Controller

imports iniciales

interface sesion03_ejemplo1AppDelegate NSObjectltUIApplicationDelegategt

UIWindow _windowUINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet

UINavigationController navController

end

Ahora hacemos click sobre la vista MainWindowxib y arrastramos un Navigation

Controller desde el listado de la columna de la derecha hacia la columna de laizquierda donde pone Objects Nos apareceraacute el Navigation Controller dibujadoen pantalla Ahora desplegamos el Navigation Controller y hacemos click sobre elView Controller que hay dentro Nos vamos a la pestantildea de Identity Inspector

de la columna de la derecha y escribimos dentro de Class el nombre de la vista dellistado de personas PersonasViewController

Finalmente hacemos click sobre el objeto Delegate vamos a la pestantildea de Show

Connections Inspector y arrastramos desde navController hasta el objetoNavigation Controller de la columna de la izquierda Ya tenemos los controladoresy las vistas conectadas

Una vez hecho esto nos queda indicar dentro de nuestro coacutedigo que la aplicacioacuten arranquecon el Navigation Controller para ello escribimos lo siguiente al final del meacutetododidFinishLaunchingWithOptions de la implementacioacuten de la clase delegada

selfwindowrootViewController = selfnavController[selfwindow makeKeyAndVisible]return YES

Ya estaacute listo todo y preparado para compilar Si ejecutamos el proyecto veremos que nosaparece el listado de personas en la vista de tabla

Persistencia

53Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios

61 Creando y leyendo un fichero plist (15 puntos)

En este primer ejercicio vamos a crear nuestra primera aplicacioacuten usando el meacutetodo depersistencia de ficheros La aplicacioacuten mostraraacute en una vista de tabla TableView unlistado de series en la que si seleccionamos una veremos su informacioacuten maacutes importanteen otra vista Comenzaremos creando un fichero de propiedades (plist) siguiendo unaestructura determinada y posteriormente mostraremos en una vista de tabla todos susdatos iexclComenzamos

1) Crear un proyecto usando la plantilla Master-Detail application Lo llamaremosejercicio-plist-ios como identificador de empresa escribiremos esuajtech y como prefijode clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremos UseAutomatic Reference Counting para olvidarnos de la gestioacuten de memoria El resto deopciones las dejaremos sin marcar

2) Crear un archivo de propiedades (plist) con el nombre de series

3) Editar el archivo plist creando la siguiente estructura de datos (5 series miacutenimo)

Plist series

4) Rellenar el archivo plist con datos de series reales

5) Escribir el coacutedigo necesario en UAMasterViewControllerm que recorra el archivoplist que acabamos de crear y muestre por consola los titulos de las series

6) Mostrar en la tabla que hay implementada los tiacutetulos de la serie Para ello deberemosde completar todos los meacutetodos del protocolo del TableView

7) Ejecutar la aplicacioacuten y comprobar que funciona correctamente

Persistencia

54Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

62 Implementar vista de detalle de la serie (05 puntos)

Dentro del proyecto anterior (no hace falta crear uno nuevo) vamos a mostrar toda lainformacioacuten adicional de cada una de las series en una vista de detalle Cuandoseleccionemos una fila de la tabla nos deberaacute aparecer una nueva vista con el resto deinformacioacuten (antildeo director principal sinopsis y duracioacuten)

63 Uso baacutesico de una base de datos SQLite (1 punto)

En este ejercicio vamos a crear la misma aplicacioacuten que en el ejercicio anterior perousando el meacutetodo de persistencia de SQLite Para completar el ejercicio deberemos seguirlos siguientes pasos

1) Crear un proyecto usando la plantilla Master-Detail application lo llamaremosejercicio-sqlite-ios como identificador de empresa escribiremos esuajtech y comoprefijo de clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremosUse Automatic Reference Counting para olvidarnos de la gestioacuten de memoria El restode opciones las dejaremos sin marcar

2) Nos descargamos e instalamos el programa SQLite Database Browser desde estadireccioacuten

3) Disentildear la estructura de la base de datos usando SQLite Database Browser tal y comose muestra en la imagen siguiente

Sqlite series

4) Insertar al menos 5 series en la base de datos usando el programa SQLite DatabaseBrowser

5) Cargar todos los datos de la base de datos desde el meacutetododidFinishLaunchingWithOptions de la clase UAAppDelegate Mostrar por consola losdatos cargados

6) Mostrar los datos cargados en el paso anterior en la vista de tabla UITableView quehay creada en la clase UAMasterViewController

Persistencia

55Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7) Arrancar la aplicacioacuten y comprobar que se muestran las series en la tabla de formacorrecta

Persistencia

56Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7 Persistencia de datos en iOS User Defaults y Core Data

71 Introduccioacuten

En esta sesioacuten veremos como utilizar dos nuevos meacutetodos de persistencia en iOS ambosmuy interesantes y usados muy frecuentemente

bull User Defaults Muy uacutetil para almacenar datos simples y de uso repetido dentro denuestras aplicaciones iOS

bull Core Data Meacutetodo avanzado de almacenamiento de objetos enteros dentro de lamemoria del dispositivo iOS Muy uacutetil para almacenar datos complejos conrelaciones etc

72 User Defaults

Este meacutetodo es muy uacutetil cuando queremos almacenar pequentildeas cantidades de datos comopuntuaciones informacioacuten de usuario el estado de nuestra aplicacioacuten configuracionesetc

Este meacutetodo es el maacutes raacutepido de usar ya que no requiere de una base de datos ni ficherosni ninguna otra capa intermedia Los datos se almacenan directamente en la memoria deldispositivo dentro de un objeto de la clase NSUserDefaults

Para aprender a usar este meacutetodo de almacenamiento de datos vamos a crear unaaplicacioacuten muy simple que lea un nombre y una edad de pantalla y las guarde en unobjeto NSUserDefaults Para ello seguimos los siguientes pasos

bull 1) Comenzamos creando un nuevo proyecto llamado sesion04-ejemplo1 dentrode XCode

bull 2) Abrimos la vista sesion04_ejemplo1ViewController y arrastramos dos TextField un botoacuten y dos labels quedando la vista de la siguiente manera

Persistencia

57Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista de la aplicacioacuten

bull 3) Abrimos el fichero sesion04_ejemplo1ViewControllerh y definimos el Outletdel evento onclick del botoacuten El fichero quedaraacute de la siguiente manera

imports iniciales

interface sesion04_ejemplo1ViewController UIViewController UITextField tfNombreUITextField tfEdad

property(nonatomic retain) IBOutlet UITextField tfNombreproperty(nonatomic retain) IBOutlet UITextField tfEdad

-(IBAction) guardaDatos

end

bull 4) Ahora implementamos el coacutedigo para el meacutetodo del botoacuten dentro desesion04_ejemplo1ViewControllerm

synthesize tfEdadsynthesize tfNombre

-(IBAction) guardaDatos NSLog(Guardamos los datos)

Persistencia

58Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creamos el objeto NSUserDefaultsNSUserDefaults datos = [NSUserDefaults standardUserDefaults]

guardamos el nombre (NSString)[datos setObjecttfNombretext forKeynombre]

guardamos la edad (Integer)[datos setInteger[tfEdadtext integerValue] forKeyedad]

almacenamos los datos en la memoria[datos synchronize]

bull 5) Tenemos que enlazar el botoacuten con el evento que hemos programado en el pasoanterior para ello abrimos la vista sesion04_ejemplo1ViewControllerxib y conla tecla ctlr apretada arrastramos desde el botoacuten hasta Files Owner (columna de laizquierda parte superior) y seleccionamos el meacutetodo que hemos creado(guardaDatos) De esta forma ya tenemos conectado el botoacuten

bull 6) Ahora hacemos lo mismo con los Text Field Tenemos que conectarlos alcontrolador para ello seleccionamos el objeto Files Owner y abrimos la pestantildeade conexiones (la que estaacute maacutes a la derecha en la columna de la derecha) Hacemosclick en tfEdad y arrastramos hasta el Text Field de edad Hacemos lo mismo para eltext field de nombre Ya tenemos todos los elementos de la vista conectados

Conexioacuten de Outlets

bull 7) Arrancamos la aplicacioacuten y la probamos

Persistencia

59Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Aplicacioacuten

bull 8) Ahora vamos a hacer que la aplicacioacuten cargue los datos que se han guardado alarrancar la vista para ello simplemente tenemos que escribir el siguiente coacutedigo en elmeacutetodo (void)viewDidLoad de la clase sesion04_ejemplo1ViewControllerm

- (void)viewDidLoad

[super viewDidLoad]

NSUserDefaults datos = [NSUserDefaults standardUserDefaults]

obtenemos un NSStringNSString nombre = [datos stringForKeynombre]

obtenemos un NSIntegerNSInteger edad = [datos integerForKeyedad]

if (nombre=nil ampamp edad=0)NSLog(Nombre cargado edad dnombreedad)

else

NSLog(Sin datos)

bull 9) Ya podemos arrancar de nuevo la aplicacioacuten y veremos que si escribimos cualquier

Persistencia

60Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

texto en los Text Fields estos se almacenaraacuten dentro del objeto NSUserDefaults Alapagar la aplicacioacuten y volver a arrancarla estos datos se cargaraacuten (apareceraacuten enconsola)

Consola

NotaLos datos permaneceraacuten en nuestro dispositivo hasta que se elimine la aplicacioacuten en ese casotodos los datos guardados en NSUserDefaults se borraraacuten

bull 10) Probar eliminar la aplicacioacuten del dispositivosimulador y volver arrancarComprobamos que los datos se han borrado

Consola

73 Core Data

De todas las formas de persistencia de datos que existen en iOS Core Data es la maacutesapropiada para usar en caso de manejar datos no triviales con relaciones algo maacutescomplejas entre ellos El uso de Core Data dentro de nuestra aplicacioacuten optimiza almaacuteximo el consumo de memoria mejora el tiempo de respuesta y reduce la cantidad decoacutedigo redundante a escribir

Core Data utiliza de fondo el meacutetodo de almacenamiento SQLite visto en la sesioacuten 3 deeste moacutedulo por tanto para explicar el funcionamiento de este meacutetodo de persistenciavamos a utilizar el mismo modelo de datos que usamos en esa sesioacuten Persona -gtDetallePersona

El el siguiente ejemplo haremos la misma aplicacioacuten que hicimos con SQLite perousando este nuevo meacutetodo de persistencia

731 Creando el proyecto

Para empezar tenemos que crearnos un nuevo proyecto en XCode Para ello abrimosXCode y seleccionamos crear una nueva aplicacioacuten basada en ventanas (Window-basedapplication) Hacemos click en Next escribimos como nombre del proyectosesion04-ejemplo2 y seleccionamos Use Core Data

Persistencia

61Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creando un proyecto nuevo

Con esto ya tenemos la estructura baacutesica para empezar a programar Antes de nadahechamos un vistazo raacutepido a toda la estructura de directorios creada y vemos que dentrodel directorio principal tenemos un archivo que se llamasesion04_ejemplo2xcdatamodeld Al hacer click sobre el archivo se nos abre uneditor especial que en principio estaacute vacio y que maacutes adelante utilizaremos para disentildearnuestro modelo de datos

Ahora hechamos un vistazo a la clase delegada sesion04_ejemplo2AppDelegatemvemos que hay unos cuantos meacutetodos nuevos que serviraacuten para configurar el Core DataVamos a explicar los maacutes importantes

bull - (NSManagedObjectModel )managedObjectModel NSManagedObjectModel esla clase que contiene la definicioacuten de cada uno de los objetos o entidades quealmacenamos en la base de datos Normalmente este meacutetodo no lo utilizaremos ya quenosotros vamos a utilizar el editor que hemos visto antes con el que podremos crearnuevas entidades crear sus atributos y relaciones

bull - (NSPersistentStoreCoordinator )persistentStoreCoordinator Aquiacute esdonde configuramos los nombres y las ubicaciones de las bases de datos que se usaraacutenpara almacenar los objetos Cuando un managed object necesite guardar algo pasaraacutepor este meacutetodo

bull - (NSManagedObjectContext )managedObjectContext Esta claseNSManagedObjectContext seraacute la maacutes usada con diferencia de las tres y por tanto la

Persistencia

62Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

maacutes importante La utilizaremos baacutesicamente para obtener objetos insertarlos oborrarlos

732 Definiendo el modelo de datos

En el ejemplo que hicimos en la sesioacuten anterior de SQLite creamos dos tablas una paralos datos baacutesicos de la persona y otra para el detalle Ahora haremos lo mismo perousando Core Data la diferencia principal es que mientras que con SQLite obteniamos soacutelolos datos que necesitaacutebamos de la base de datos aquiacute obtenemos el objeto completo

Para definir nuestro modelo de datos basado en dos objetos (Persona y DetallePersona)abrimos el editor visual haciendo click sobre el ficherosesion04_ejemplo2xcdatamodeld Empezaremos creando una nueva entidad para ellohacemos click sobre el botoacuten Add Entity (+) que se encuentra en la parte inferior dela pantalla A la nueva entidad le pondremos como nombre Persona

Dentro de la seccioacuten de Attributes hacemos click sobre (+) y creamos un nuevoatributo para nuestra entidad le llamamos nombre y le asignamos como tipo StringRealizamos este mismo paso 2 veces maacutes para antildeadir apellidos y edad este uacutetlimode tipo Integer 16

Atributos Persona

Ahora creamos la entidad de DetallePersona de la misma manera que la anterior perocreando los atributos localidad pais direccion cp y telefono Todasde tipo String

Atributos DetallePersona

Finalmente nos queda relacionar de alguacuten modo las dos entidades para ello antildeadimos una

Persistencia

63Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

relacioacuten dentro de la entidad de Persona hacia DetallePersona simplementehaciendo click sobre el botoacuten (+) de la seccioacuten de Relationships A la relacioacuten leponemos como nombre detalle seleccionamos como destino la entidadDetallePersona y seraacute No inverse Este tipo de relacioacuten es uno a uno Por dentroCore Data realizaraacute crearaacute una clave ajena en la tabla de persona que apunte aDetallePersona pero eso a nosotros no nos interesa

Vista general modelo de datos (1)

Apple recomienda que siempre que hagamos una relacioacuten de una entidad a otra hagamostambieacuten la relacioacuten inversa por lo tanto hacemos justo lo anterior pero al reveacutes (deDetallePersona a Persona) con la diferencia que en la columna de Inverse

seleccionamos datosPersona

Ya tenemos las entidades y las relaciones creadas Si hacemos click en el botoacuten deEditor Style en la parte de abajo a la derecha del editor podemos ver de una formamaacutes clara la estructura que hemos disentildeado

Persistencia

64Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista general modelo de datos (2)

733 Probando el modelo

Una vez que tenemos el modelo creado pasamos a probarlo para comprobar que estaacute todocorrecto para ello abrimos la clase delegada (sesion04_ejemplo2AppDelegatem) yantildeadimos el siguiente fragmento de coacutedigo arriba del meacutetodoapplicationDidFinishLaunching

NSManagedObjectContext context = [self managedObjectContext]NSManagedObject persona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

[persona setValueJuan forKeynombre][persona setValueMartinez forKeyapellidos][persona setValue[NSNumber numberWithInt28] forKeyedad]

NSManagedObject detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersonainManagedObjectContextcontext]

[detallePersona setValueCalle Falsa 123 forKeydireccion][detallePersona setValueAlicante forKeylocalidad][detallePersona setValueEspantildea forKeypais][detallePersona setValue965656565 forKeytelefono]

[detallePersona setValuepersona forKeydatosPersona][persona setValuedetallePersona forKeydetalle]

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Persistencia

65Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Con este coacutedigo hemos creado un objeto Persona enlazado con otro objetoDetallePersona iexclComo puedes ver no hemos necesitado nada de coacutedigo SQLPrimero creamos un objeto del tipo NSManagedObjectContext que almacena el conextode Core Data Despueacutes creamos el objeto Persona en siacute con NSManagedObject A esteobjeto le pondremos los valores para cada una de las propiedades definidas en el modeloHacemos lo mismo con DetallePersona Finalmente relacionamos el objetoPersona con el objeto DetallePersona

Compilamos el proyecto comprobamos que todo funciona bien y no salen errores

Para comprobar que los datos se han almacenado correctamente en nuestro dispositivo losmostramos en la consola de la siguiente manera

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

intValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(------------------)

[fetchRequest release]

El coacutedigo de arriba lo escribiremos dentro del meacutetododidFinishLaunchingWithOptions de la clase delegada Lo que hace este coacutedigo esrecorrer todos los objetos almacenados en memoria y mostrarlos por consola Siarrancamos la aplicacioacuten veremos que se muestran todos los datos de los objetosalmacenados Cada vez que arranquemos la aplicacioacuten se mostraraacuten maacutes objetos ya quetambieacuten los creamos

Para listar los objetos usamos la clase NSFetchRequest Esta clase es equivalente aejecutar una sentencia Select de SQL Al objeto NSFetchRequest le asignamos laentidad que queremos listar (SELECT FROM ) en nuestro caso seraacute PersonaEl listado de los objetos se obtiene mediante el meacutetodo executeFetchRequest de laclase NSManagedObjectContext que hemos definido en el bloque anterior de coacutedigoEste meacutetodo devolveraacute un NSArray con todos los objetos NSManagedObject

Persistencia

66Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Salida de consola

NotaSi queremos ver queacute sentencias SQL estaacute ejecutando Core Data por debajo tenemos que activarel flag de debug para ello seleccionamos la opcioacuten Product gt Edit Scheme Enla columna de la izquierda seleccionamos el esquema que estamos utilizando para Debug y en laparte de la derecha en el cuadro de Arguments Passed on Launch antildeadimos 2paraacutemetros -comappleCoreDataSQLDebug y 1

Ahora arrancamos de nuevo la aplicacioacuten y veremos que en la consola apareceraacuten lassentencias SQL utilizadas por Core Data

()

INSERT INTO ZDETALLEPERSONA(Z_PK Z_ENT Z_OPT ZDATOSPERSONA ZTELEFONOZLOCALIDAD ZDIRECCION ZPAIS)VALUES( )CoreData sql SELECT 0 t0Z_PK t0Z_OPT t0ZTELEFONO t0ZLOCALIDADt0ZDIRECCION t0ZPAIS t0ZDATOSPERSONAFROM ZDETALLEPERSONA t0 WHERE t0Z_PK =

()

734 Generando los modelos automaacuteticamente

Generar los modelos a mano directamente desde el editor no es lo maacutes recomendado yaque podemos cometer errores de escritura errores de tipo etc La mejor manera dehacerlo es creando un archivo para cada entidad Esto se puede hacer desde 0 peroXCode tiene un generador de clases que es muy faacutecil de usar y ahorremos bastantetiempo iexclVamos a usarlo

Hacemos click sobre el raiz de nuestro proyecto y seleccionamos New File En lacolumna de la izquierda seleccionamos iOS gt Core Data y en el cuadro de laderecha NSManagedObject subclass Click en next En la siguiente pantallaseleccionamos nuestro modelo de datos sesion04_ejemplo2 y click en next Ahoranos aseguramos de seleccionar las dos entidades que hemos creado anteriormentePersona y DetallePersona click en next Seleccionamos el raiz de nuestroproyecto para guardar los ficheros que se generen y aceptamos

Persistencia

67Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ventana de seleccioacuten de entidad

Ahora veremos que xsource ha creado automaacuteticamente 4 ficheros nuevos dentro denuestro proyecto Personamh y DetallePersonamh con todos los atributosespecificados Estas nuevas clases que heredan de NSManagedObject seraacuten las queutilicemos a partir de ahora

Cambiamos el coacutedigo de la clase delegada por el siguiente

NSManagedObjectContext context = [self managedObjectContext]Persona datosPersona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

datosPersonanombre = JuandatosPersonaapellidos = MartinezdatosPersonaedad = [NSNumber numberWithInt28]

DetallePersona detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersona

inManagedObjectContextcontext]detallePersonadireccion = Calle Falsa 123

Persistencia

68Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

detallePersonalocalidad = AlicantedetallePersonapais = EspantildeadetallePersonatelefono = 965656565

detallePersonadatosPersona = datosPersonadatosPersonadetalle = detallePersona

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Listamos los datosNSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

integerValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(-----------------)[fetchRequest release]

Ejecutamos el proyecto y comprobamos que en la consola aparece lo mismo que antespero ahora tenemos el coacutedigo bastante maacutes claro

735 Creando la vista de tabla

Ahora vamos a mostrar los objetos que antes hemos listado por consola en una vista detabla UITableView Para ello creamos un UITableViewController

Click en New file gt UIViewController subclass En la siguiente pantallanos aseguramos de seleccionar Subclass of UITableViewController de esta formanos apareceraacuten ya todos los meacutetodos de la tabla creados por defecto Guardamos elarchivo con el nombre que queramos por ejemplo PersonasViewController

Abrimos ahora PersonasViewControllerh y antildeadimos 2 atributos a la clase

bull Un NSArray que se seraacute el listado de personas (objetos de la clase Persona)bull Una referencia a NSManagedObjectContext

El fichero PersonasViewControllerh quedaraacute de la siguiente manera

Persistencia

69Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

imports iniciales

interface PersonasViewController UITableViewController NSArray _listaPersonasNSManagedObjectContext _context

property (nonatomic retain) NSArray listaPersonasproperty (nonatomic retain) NSManagedObjectContext context

end

Ahora en el m importamos en el encabezado las clases Personah yDetallePersonah y en el meacutetodo viewDidLoad creamos el array listaPersonas contodos los objetos Persona de forma similar a cuando lo hicimos en el apartado anterior

import PersonasViewControllerh

import Personahimport DetallePersonah

implementation PersonasViewController

synthesize listaPersonas = _listaPersonassynthesize context = _context

En viewDidLoad

- (void)viewDidLoad

[super viewDidLoad]

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContext_context]

[fetchRequest setEntityentity]NSError errorselflistaPersonas = [_context executeFetchRequestfetchRequest

erroramperror]selftitle = Listado Personas[fetchRequest release]

Ahora completamos el resto de meacutetodos heredados de UITableViewController de lamisma forma que hicimos en sesiones anteriores

- (NSInteger)numberOfSectionsInTableView(UITableView )tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection(NSInteger)section

Persistencia

70Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Return the number of rows in the sectionreturn [_listaPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]

if (cell == nil) cell = [[[UITableViewCell alloc]

initWithStyleUITableViewCellStyleSubtitlereuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona p = (Persona )[_listaPersonas objectAtIndexindexPathrow]celltextLabeltext = pnombrecelldetailTextLabeltext = [NSString stringWithFormatd antildeos

[pedad intValue]]

return cell

Ahora para poder mostrar la tabla dentro de nuestra aplicacioacuten debemos asignarla dentrode la clase delegada para ello abrimos la vista MainWindowxib y arrastramos unacontroladora de navegacioacuten Navigation Controller a la lista de objetos de laizquierda La desplegamos y dentro de View Controller en la pestantildea de la derechade Identity Inspector escribimos el nombre de nuestra clase que tiene la vista de latabla PersonasViewController

Pantalla del disentildeador

Ahora abrimos el fichero sesion04_ejemplo2AppDelegateh y creamos un

Persistencia

71Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

UINavigationController dentro de la declaracioacuten de variables dejando el ficherocomo sigue

imports iniciales

interface sesion04_ejemplo2AppDelegate NSObject ltUIApplicationDelegategt

UINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet UINavigationControllernavController

property (nonatomic retain readonly) NSManagedObjectContextmanagedObjectContextproperty (nonatomic retain readonly) NSManagedObjectModelmanagedObjectModelproperty (nonatomic retain readonly) NSPersistentStoreCoordinatorpersistentStoreCoordinator

- (void)saveContext- (NSURL )applicationDocumentsDirectory

end

Dentro del m importamos la clase PersonasViewControllerh y completamos elsynthetize

synthesize navController = _navController

Y por uacuteltimo justo antes de la llamada al meacutetodo makeKeyAndVisible escribimos elsiguiente coacutedigo para asignar el contexto de la clase delegada alPersonasViewController

PersonasViewController root = (PersonasViewController ) [_navControllertopViewController]

rootcontext = [self managedObjectContext][selfwindow addSubview_navControllerview]

Ahora compilamos y ejecutamos nuestro proyecto y si todo ha ido bien deberiacutea de salirla tabla con los datos de las personas

Persistencia

72Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Listado de personas

736 Migraciones de bases de datos con Core Data

Una vez que tenemos disentildeada nuestra base de datos inicial iquestqueacute pasariacutea si queremosmodificarla para antildeadirle un campo maacutes a una tabla iquestQueacute haraacute Core Data para realizar lamigracioacuten de la versioacuten anterior a la actual

Siguiendo con el ejemplo anterior si despueacutes de ejecutar la aplicacioacuten en nuestrodispositivo o simulador modificamos la estructura de la base de datos antildeadiendo unatributo nuevo en la entidad DetallePersona por ejemplo codigoPostal con el tipoInteger 16 veremos que al volver a arrancar la aplicacioacuten nos apareceraacute un error entiempo de ejecucioacuten similar al siguiente

Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 Theoperationcouldnrsquot be completed(Cocoa error 134100) UserInfo=0x6b6c680 metadata=ltCFBasicHash 0x6b68380[0x1337b38]gttype = immutable dict count = 7entries =gt

2 ltCFString 0x6b70320 [0x1337b38]gtcontents =NSStoreModelVersionIdentifiers

=ltCFArray 0x6b706e0 [0x1337b38]gttype = immutable count = 1

values = (0 ltCFString 0x1332cd8 [0x1337b38]gtcontents =

)4 ltCFString 0x6b70350 [0x1337b38]gtcontents =

NSPersistenceFrameworkVersion=

Persistencia

73Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltCFNumber 0x6b6f2c0 [0x1337b38]gtvalue = +386 type =kCFNumberSInt64Type

6 ltCFString 0x6b70380 [0x1337b38]gtcontents =NSStoreModelVersionHashes =

ltCFBasicHash 0x6b707e0 [0x1337b38]gttype = immutable dict count =2entries =gt

1 ltCFString 0x6b70700 [0x1337b38]gtcontents = Persona =ltCFData 0x6b70740

[0x1337b38]gtlength = 32 capacity = 32 bytes =0x1c50e5a379ff0ddc3cda7b82a3934c5e 75649ac3c919beea2 ltCFString 0x6b70720 [0x1337b38]gtcontents = DetallePersona

=ltCFData 0x6b70790[0x1337b38]gtlength = 32 capacity = 32 bytes =0xb7ba2eb498f9a1acdd6630d56f6cd12c e8963dd3e488ba95

7 ltCFString 0x10e3aa8 [0x1337b38]gtcontents = NSStoreUUID =ltCFString 0x6b70510[0x1337b38]gtcontents = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E8 ltCFString 0x10e3948 [0x1337b38]gtcontents = NSStoreType =ltCFString 0x10e3958[0x1337b38]gtcontents = SQLite9 ltCFString 0x6b703b0 [0x1337b38]gtcontents =

_NSAutoVacuumLevel = ltCFString0x6b70830 [0x1337b38]gtcontents = 210 ltFString 0x6b703d0 [0x1337b38]gtcontents =

NSStoreModelVersionHashesVersion=ltCFNumber 0x6b61950 [0x1337b38]gtvalue = +3 type =

kCFNumberSInt32Type reason=The model used to open the store is incompatible with the oneused tocreate the store

metadata = NSPersistenceFrameworkVersion = 386NSStoreModelVersionHashes =

DetallePersona = ltb7ba2eb4 98f9a1ac dd6630d5 6f6cd12c f890007746b16f00

e8963dd3 e488ba95gtPersona = lt1c50e5a3 79ff0ddc 3cda7b82 a3934c5e 5cb4ee4b

4ac98838 75649ac3c919beeagt

NSStoreModelVersionHashesVersion = 3NSStoreModelVersionIdentifiers = (

)NSStoreType = SQLiteNSStoreUUID = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E_NSAutoVacuumLevel = 2

reason = The model used to open the store is incompatible with the

one usedto create the store

Este error es muy comuacuten cuando estamos desarrollando aplicaciones con Core DataBaacutesicamente nos dice que el modelo de base de datos que estaacute intentando cargar dememoria es distinto al que se indica en la aplicacioacuten esto es porque no hemos indicadoninguacuten meacutetodo de migracioacuten Realmente existen dos formas de solucionar este error

Persistencia

74Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Por un lado y soacutelo cuando estemos desarrollando y no tengamos ninguna versioacutende nuestra aplicacioacuten lanzada en la App Store podremos eliminar la base de datosdel dispositivo simulador simplemente borrando la aplicacioacuten de el Tambieacutenpodemos ejecutar el siguiente coacutedigo una soacutela vez [[NSFileManagerdefaultManager] removeItemAtURLstoreURL errornil]

bull En el caso de que estemos realizando una migracioacuten sobre una versioacuten ya publicadaen la App Store el meacutetodo anterior no nos serviriacutea ya que obligariacuteamos al usuario aborrar su aplicacioacuten del dispositivo con lo que todos los datos que tuviera seperderiacutean Esto no es viable por lo que necesitaremos usar otro meacutetodo para realizarla migracioacuten y solucionar el error de una forma maacutes limpia este meacutetodo es el quevamos a comentar a continuacioacuten

XCode soporta Versioning para Core Data esto es que podemos crear versiones de labase de datos de una forma sencilla si seguimos una serie de pasos

1) Antildeadimos las siguientes opciones en forma de NSDictionary a nuestro coacutedigo delpersistentStoreCoordinator

NSDictionary options = [NSDictionary dictionaryWithObjectsAndKeys[NSNumber numberWithBoolYES]NSMigratePersistentStoresAutomaticallyOption[NSNumber numberWithBoolYES]NSInferMappingModelAutomaticallyOptionnil]

Y modificamos el meacutetodo addPersistentStoreWithType pasaacutendole como paraacutemetro eldiccionario options

if ([__persistentStoreCoordinatoraddPersistentStoreWithTypeNSSQLiteStoreTypeconfigurationnil URLstoreURL optionsoptions erroramperror])

Con el coacutedigo anterior indicamos al persistentStoreCoordinator que la migracioacuten enel caso de que se deba de realizar sea de forma automaacutetica

AtencioacutenDeberemos de tener en cuenta que este tipo de migracioacuten que estamos estudiando en este puntosoacutelo es vaacutelida para cambios simples en Core Data lo que Apple llama como lightweightmigration Este tipo de migracioacuten admite una serie de cambios que se indican dentro de ladocumentacioacuten de Apple para cambios maacutes complejos deberemos realizar migracionespersonalizadas y mucho maacutes complejas El documento en donde Apple explica el tipo demigraciones que existen para Core Data y su forma de implementarlas lo podemos consultardesde esta web

2) Ahora deberemos de crear la versioacuten del esquema de Core Data dentro de XCode para

Persistencia

75Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ello seleccionamos el fichero del esquema sesion04_ejemplo2xcdatamodeld yseleccionamos en el menuacute principal Editor gt Add Model Version A la nueva versioacuten lallamaremos sesion04_ejemplo_2 (versioacuten 2) Una vez que hayamos pulsado sobreFinish se nos habraacute creado la nueva versioacuten del esquema dentro de la principal y estaraacutemarcada con un tick la versioacuten anterior

3) Una vez que tenemos creada la nueva versioacuten deberemos modificarla antildeadiendole elnuevo atributo Seleccionamos la versioacuten recieacuten creadasesion04_ejemplo_2xcdatamodel y antildeadimos a la entidad DetallePersona elatributo codigoPostal Atencioacuten Si este atributo lo hemos antildeadido anteriormentedeberemos de borrarlo de la versioacuten anterior y antildeadirlo en la segunda versioacuten

4) Ahora nos queda indicar a Core Data que queremos usar la versioacuten 2 de la base dedatos para nuestra aplicacioacuten Para ello seleccionamos el esquema principal (el raiz)sesion04_ejemplo2xcdatamodeld y en la ventana de la derecha en la pestantildea de FileInspector seleccionamos como Versioacuten actual la versioacuten 2 esto lo hacemos dentro delapartado de Versioned Core Data Model Automaacuteticamente veremos que el siacutembolo detick cambia a la versioacuten 2

5) Ahora ya podemos volver a compilar y veremos que no nos aparece el error y funcionatodo seguacuten lo esperado

Magical RecordExisten varias librerias de coacutedigo libre que facilitan enormemente el uso de Core Data ennuestras aplicaciones iOS Una de ellas es Magical Record Mediante esta libreriacutea podremos usarCore Data sin tener que preocuparnos de todo el proceso de configuracioacuten en las controladorasde la configuracioacuten de migraciones etc Su instalacioacuten en un proyecto de XCode es muy faacutecil yaltamente recomendable ya que proporciona una API muy clara y sencilla de usar

Persistencia

76Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios

81 Usando User Defaults (1 punto)

En este ejercicio vamos a usar las variables de usuario para almacenar y mostrar los datosdel dispositivo que estamos usando y las veces que hemos arrancado la aplicacioacuten

Crear un proyecto nuevo usando la plantilla Single View Application con el nombreejercicio-userdefaults-ios Se debe de ejecutar en iPhone e iPad (Universal) Comoidentificador de empresa escribiremos esuajtech y como prefijo de clase UA Debe detener el ARC activado (desmarcar el resto de opciones)

Ayuda meacutetodos de UIDevicePara obtener los datos del dispositivo usaremos el singleton UIDevice estos son algunos de losmeacutetodos que podemos usar [[UIDevice currentDevice] name] [[UIDevicecurrentDevice] systemName] [[UIDevice currentDevice]systemVersion] [[UIDevice currentDevice] model] [[UIDevicecurrentDevice] localizedModel]

82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)

En este ejercicio disentildearemos un modelo de datos de dos tablas relacionadas entre ellasantildeadiremos datos por coacutedigo y los mostraremos en una vista de tabla La base de datosque vamos a implementar constaraacute de dos tablas por un lado una que contendraacute los tiacutetulosde series y otra que tendraacute los detalles A la primera la llamaremos Serie y la segundaDetalleSerie Para realizar correctamente el ejercicio deberemos seguir los siguientespasos

1) Crear un proyecto nuevo usando la plantilla Master-Detail Application con elnombre ejercicio-coredata-ios Se debe de ejecutar en iPhone como identificador deempresa escribiremos esuajtech y como prefijo de clase UA Debe de tener el ARCactivado y la opcioacuten de Use Core Data (desmarcar el resto de opciones)

2) Disentildear el siguiente modelo de datos

Persistencia

77Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esquema modelo de datos

3) Revisar el fichero UAAppDelegatem y comprobar que tenemos todos los meacutetodosnecesarios para que funcione Core Data en nuestra aplicacioacuten

4) Modificar el fichero UAMasterViewControllerm para que se muestren los tiacutetulos delas series en la tabla

5) Probar la aplicacioacuten antildeadiendo unos tiacutetulos de prueba

83 Migrando datos con Core Data (05 puntos)

Una vez que hemos disentildeado la Base de Datos con Core Data vamos a modificarlaantildeadiendo un atributo maacutes a la tabla de DetalleSerie valoracion

a) iquestQueacute pasa si cambiamos el modelo de datos y arrancamos la aplicacioacuten iquestQueacute errornos da y por queacute

b) iquestCoacutemo podemos evitar este tipo de errores cuando estemos desarrollando iquestY siestamos en produccioacuten con la aplicacioacuten ya a la venta

c) Crea una nueva versioacuten del modelo

d) Modifica el coacutedigo necesario dentro de la clase UAAppDelegate para evitar este tipo deerrores cuando estemos en un entorno de produccioacuten

e) Vuelve a arrancar la aplicacioacuten y comprueba que funciona bien

Persistencia

78Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Persistencia

79Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

  • 1 Persistencia en Android ficheros y SQLite
    • 11 Introduccioacuten
    • 12 Manejo de ficheros tradicionales en Android
      • 121 Apertura de ficheros
      • 122 Ficheros como recursos
      • 123 Operar con ficheros
      • 124 Almacenar datos en la tarjeta de memoria
        • 13 Base de datos SQLite
          • 131 Content Values
          • 132 Cursores
          • 133 Trabajar con bases de datos SQLite
          • 134 La clase SQLiteOpenHelper
          • 135 Crear una base de datos sin SQLiteHelper
          • 136 Realizar una consulta
          • 137 Extraer resultados de un cursor
          • 138 Antildeadir actualizar y borrar filas
              • 2 Ejercicios - Persistencia en Android ficheros y SQLite
                • 21 Uso de ficheros (05 puntos)
                • 22 Persistencia con ficheros (05 puntos)
                • 23 Base de datos SQLiteOpenHelper (05 puntos)
                • 24 Base de datos insercioacuten y borrado (05 puntos)
                • 25 Base de datos probar nuestro adaptador (05 puntos)
                • 26 Base de datos cambios en la base de datos (05 puntos)
                  • 3 Persistencia en Android proveedores de contenidos y SharedPreferences
                    • 31 Shared Preferences
                      • 311 Guardar Shared Preferences
                      • 312 Leer Shared Preferences
                      • 313 Interfaces para Shared Preferences
                      • 314 Definiendo una pantalla de preferencias con un layout en XML
                      • 315 Controles nativos para preferencias
                      • 316 Actividades de preferencias
                      • 317 Shared Preference Change Listeners
                        • 32 Proveedores de contenidos
                          • 321 Proveedores nativos
                          • 322 Proveedores propios crear un nuevo proveedor de contenidos
                          • 323 Proveedores propios crear la interfaz de consultas
                          • 324 Proveedores propios tipo MIME
                          • 325 Proveedores propios registrar el proveedor
                          • 326 Content Resolvers
                          • 327 Otras operaciones con Content Resolvers
                              • 4 Ejercicios - Persistencia en Android proveedores de contenidos y SharedPreferences
                                • 41 Compartir datos entre actividades con Shared Preferences (075 puntos)
                                • 42 Actividad de preferencias (075 puntos)
                                • 43 Proveedor de contenidos propio (075 puntos)
                                • 44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)
                                  • 5 Persistencia de datos en iOS Ficheros y SQLite
                                    • 51 Introduccioacuten
                                    • 52 Ficheros plist (Property Lists)
                                      • 521 Leyendo datos desde ficheros plist
                                      • 522 Escribiendo datos en ficheros plist
                                        • 53 SQLite
                                          • 531 Herramientas disponibles
                                          • 532 Lectura de datos
                                          • 533 Mostrando los datos en una tabla
                                              • 6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios
                                                • 61 Creando y leyendo un fichero plist (15 puntos)
                                                • 62 Implementar vista de detalle de la serie (05 puntos)
                                                • 63 Uso baacutesico de una base de datos SQLite (1 punto)
                                                  • 7 Persistencia de datos en iOS User Defaults y Core Data
                                                    • 71 Introduccioacuten
                                                    • 72 User Defaults
                                                    • 73 Core Data
                                                      • 731 Creando el proyecto
                                                      • 732 Definiendo el modelo de datos
                                                      • 733 Probando el modelo
                                                      • 734 Generando los modelos automaacuteticamente
                                                      • 735 Creando la vista de tabla
                                                      • 736 Migraciones de bases de datos con Core Data
                                                          • 8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios
                                                            • 81 Usando User Defaults (1 punto)
                                                            • 82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)
                                                            • 83 Migrando datos con Core Data (05 puntos)
Page 3: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de

1 Persistencia en Android ficheros y SQLite

11 Introduccioacuten

En este moacutedulo trataremos diferentes mecanismos existentes en los sistemas Android eiOS para el almacenamiento de datos tanto en la memoria del dispositivo como enmemoria externa Comenzaremos en primer lugar tratando el manejo de ficheros enAndroid para a continuacioacuten explicar coacutemo utilizar el motor de base de datos SQLite ennuestras aplicaciones en dicho sistema A continuacioacuten presentaremos los proveedores decontenidos los cuales nos proporcionan un mecanismo estaacutendar para el intercambio deinformacioacuten entre aplicaciones Android Veremos coacutemo usar los proporcionados por elsistema y crear los nuestros propios

En las sesiones 3 y 4 comentaremos los meacutetodos maacutes usados para almacenar datos dentrode nuestras aplicaciones iOS Empezaremos explicando de forma detallada el uso de losficheros de propiedades (plist) un tipo de ficheros con formato XML que es muy usadoen sistemas iOS Continuaremos con el manejo de bases de datos SQLite dentro de lasaplicaciones iOS explicando la libreria que disponemos dentro del SDK de iPhone Poruacuteltimo en la sesioacuten 4 del moacutedulo empezaremos explicando el meacutetodo de almacenamientomediante variables de tipo User Defaults para terminar con algo maacutes complejo como es eluso de Core Data forma de almacenamiento de datos muy extendida en estos uacuteltimosantildeos dentro de los sistemas iOS

12 Manejo de ficheros tradicionales en Android

El uso de ficheros tradicionales estaacute permitido para los programas de Android aunquecomo veremos en eacutesta y otras sesiones hay otras tecnologiacuteas mucho maacutes avanzadascomo el uso del motor de base de datos SQLite y para el caso de preferencias de lasaplicaciones las SharedPreferences

121 Apertura de ficheros

En Android podemos abrir un fichero para lectura o para escritura de la siguiente forma

Abrir un fichero de salida privado a la aplicacioacutenFileOutputStream fos = openFileOutput(ficherotxtContextMODE_PRIVATE) Abrir un fichero de entradaFileInputStream fis = openFileInput(ficherotxt)

Al abrir el fichero para salida el modo ContextMODE_PRIVATE hace que eacuteste seaprivado a la aplicacioacuten Si el fichero a abrir no existe eacuteste se creariacutea Existe un paraacutemetropara permitir compartir el fichero pero lo adecuado en este caso seriacutea hacer uso deproveedores de contenidos por lo que no trataremos dicho paraacutemetro

Persistencia

3Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Por otra parte el paraacutemetro ContextMODE_APPEND permite antildeadir contenido a un ficheroque ya se hubiera creado previamente (o crear uno nuevo en el caso en el que eacuteste noexistiera)

Estos meacutetodos tan soacutelo permiten abrir ficheros en la carpeta de la aplicacioacuten por lo que sise intenta antildeadir alguacuten separador de ruta se produciraacute una excepcioacuten El fichero abiertoutilizando OpenFileOutput (usando cualquiera de los dos paraacutemetros anteriores) seguardariacutea dentro del aacuterbol de directorios del dispositivo en la carpetadatadata[paquete]files donde [paquete] seriacutea el nombre del paquete de laaplicacioacuten Se trata de ficheros de texto normales por los que se puede acceder a ellosutilizando el shell de adb y utilizando el comando cat Para acceder al shell podemoshacer lo siguiente

adb shell

Por supuesto no debemos olvidarnos de cerrar el fichero una vez vayamos a dejar deutilizarlo mediante la correspondiente llamada al meacutetodo close()

fosclose()fisclose()

122 Ficheros como recursos

Es posible antildeadir ficheros como recursos de la aplicacioacuten Para ello los almacenamos enla carpeta resraw de nuestro proyecto Estos ficheros seraacuten de soacutelo lectura y paraacceder a ellos llamaremos al meacutetodo openRawResource del objeto Resource de nuestraaplicacioacuten Como resultado obtendremos un objeto de tipo InputStream Un ejemploseriacutea el que se muestra a continuacioacuten

Resources myResources = getResources()InputStream myFile = myResourcesopenRawResource(Rrawfichero)

Antildeadir ficheros a los recursos de la aplicacioacuten es un mecanismo para disponer de fuentesde datos estaacuteticas de gran tamantildeo como podriacutea ser por ejemplo un diccionario Esto seraacuteasiacute en aquellos casos en los que no sea aconsejable (o deseable) almacenar estainformacioacuten en una base de datos

Como en el caso de cualquier otro recurso seriacutea posible guardar diferentes versiones deun mismo archivo para diferentes configuraciones del dispositivo Podriacuteamos tener porejemplo un fichero de diccionario para diferentes idiomas

123 Operar con ficheros

Una alternativa simple para escribir en un fichero o leer de eacutel es utilizar las clasesDataInputStream y DataOutputStream que nos permiten leer o escribir a partir dediferentes tipos de datos como enteros o cadenas En el siguiente ejemplo vemos coacutemo esposible guardar una cadena en un fichero y a continuacioacuten leerla

Persistencia

4Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

FileOutputStream fos = openFileOutput(ficherotxtContextMODE_PRIVATE)String cadenaOutput = new String(Contenido del ficheron)DataOutputStream dos = new DataOutputStream(fos)doswriteBytes(cadenaOutput)fosclose()

FileInputStream fin = openFileInput(ficherotxt)DataInputStream dis = new DataInputStream(fin)String string = disreadLine() Hacer algo con la cadenafinclose()

124 Almacenar datos en la tarjeta de memoria

Veamos ahora queacute pasos debemos seguir para poder escribir datos en la tarjeta dememoria (SD card) Para ello se requiere el uso del meacutetodoEnvironmentgetExternalStorageDirectory() y una instancia de la claseFileWriter tal como se muestra en el siguiente ejemplo

try File raiz = EnvironmentgetExternalStorageDirectory()if (raizcanWrite())

File file = new File(raiz ficherotxt)BufferedWriter out = new BufferedWriter(new

FileWriter(file))outwrite(Mi texto escrito desde Androidn)outclose()

catch (IOException e)

Loge(FILE IO Error en la escritura de fichero +egetMessage())

Para probarlo en el emulador necesitamos tener creada una tarjeta SD lo cual se puedeconseguir mediante la herramienta del Android SDK mksdcard

mksdcard 512M sdcardiso

Podremos cargar esta tarjeta SD en nuestro emulador seleccionando el archivo recieacutencreado dentro del apartado SD card de la ventana de edicioacuten de las propiedades delemulador Para copiar fuera del emulador el archivo que hemos grabado en la tarjeta dememoria podemos utilizar el comando

adb pull sdcardficherotxt ficherotxt

Por uacuteltimo no debemos olvidar antildeadir el permiso correspondiente en el archivo Manifestde nuestra aplicacioacuten para poder escribir datos en un dispositivo de almacenamientoexterno Para ello antildeadimos lo siguiente al elemento manifest del AndroidManifestxml(recuerda que esto no va ni dentro del elemento application ni dentro del elementoactivity)

ltuses-permission androidname=androidpermissionWRITE_EXTERNAL_STORAGEgt

Persistencia

5Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

13 Base de datos SQLite

SQLite es un gestor de bases de datos relacional y es de coacutedigo abierto cumple con losestaacutendares y es extremadamente ligero Ademaacutes guarda toda la base de datos en un uacutenicofichero Su utilidad es evidente en el caso de aplicaciones de pequentildeo tamantildeo pues eacutestasno requeriraacuten la instalacioacuten adicional de ninguacuten gestor de bases de datos Esto tambieacutenseraacute asiacute en el caso de dispositivos empotrados con recursos limitados Android incluyesoporte a SQLite y en esta seccioacuten veremos coacutemo usarlo

Por medio de SQLite podremos crear bases de datos relacionales independientes paranuestras aplicaciones Con ellas podremos almacenar datos complejos y estructuradosAunque el disentildeo de bases de datos quedaraacute fuera del contenido de este curso ya quenecesitariacuteamos de maacutes tiempo para poder tratarlo es conveniente resaltar el hecho de quelas mejores praacutecticas en este campo siguen siendo uacutetiles en el caso de aplicacionesAndroid En particular la normalizacioacuten de datos para evitar redundancia es un paso muyimportante en el caso en el que estemos disentildeando una base de datos para dispositivoscon recursos limitados

En el caso del sistema Android SQLite se implementa como una libreriacutea C compacta enlugar de ejecutarse en un proceso propio Debido a ello cualquier base de datos quecreemos seraacute integrada como parte de su correspondiente aplicacioacuten Esto reducedependencias externas minimiza la latencia y simplifica ciertos procesos como lasincronizacioacuten

NotaLas bases de datos de Android se almacenan en el directoriodatadata[PAQUETE]databases donde [PAQUETE] es el paquetecorrespondiente a la aplicacioacuten Por defecto todas las bases de datos son privadas y tan soacuteloaccesibles por la aplicacioacuten que las creoacute

131 Content Values

Para insertar nuevas filas en tablas haremos uso de objetos de la clase ContentValuesCada objeto de este tipo representa una uacutenica fila en una tabla y se encarga de emparejarnombres de columnas con valores concretos Hay que tener en cuenta que SQLite difierede muchos otros motores de bases de datos en cuanto al tipado de las columnas La ideabaacutesicamente es que los valores para las columnas no tienen por queacute ser de un uacutenico tipoes decir los datos en SQLite son deacutebilmente tipados La consecuencia de esto es que noes necesario comprobar tipos cuando se asignan o extraen valores de cada columna de unafila con lo que se mejora la eficiencia

132 Cursores

Persistencia

6Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Los cursores son una herramienta fundamental en el tratamiento de informacioacuten enAndroid y tambieacuten cuando se trabaja con bases de datos Por lo tanto en esta seccioacutenintroducimos este concepto tan importante

Cualquier consulta a una base de datos en Android devolveraacute un objeto de la claseCursor Los cursores no contienen una copia de todos los resultados de la consulta sinoque maacutes bien se trata de punteros al conjunto de resultados Estos objetos proporcionanun mecanismo para controlar nuestra posicioacuten (fila) en el conjunto de resultadosobtenidos tras la consulta

La clase Cursor incluye entre otras las siguientes funciones de navegacioacuten entreresultados

bull moveToFirst desplaza el cursor a la primera fila de los resultados de la consultabull moveToNext desplaza el cursor a la siguiente filabull moveToPrevious desplaza el cursor a la fila anteriorbull getCount devuelve el nuacutemero de filas del conjunto de resultados de la consultabull getColumnName devuelve el nombre de la columna especificada mediante un iacutendice

que se pasa como paraacutemetrobull getColumnNames devuelve un array de cadenas conteniendo el nombre de todas las

columnas en el cursorbull moveToPosition mueve el cursor a la fila especificadabull getPosition devuelve el iacutendice de la posicioacuten apuntada actualmente por el cursor

A lo largo de esta sesioacuten aprenderemos coacutemo realizar consultas a una base de datos ycomo extraer valores o nombres de columnas concretos a partir de los cursores obtenidospor medio de dichas consultas

133 Trabajar con bases de datos SQLite

Suele considerarse una buena praacutectica el crear una clase auxiliar que nos facilite lainteraccioacuten con la base de datos Se trataraacute de una clase de tipo adaptador que crearaacute unacapa de abstraccioacuten que encapsularaacute dichas interacciones De esta forma podremosantildeadir eliminar o actualizar elementos en la base de datos sin necesidad de introducir niuna uacutenica instruccioacuten en SQL en el resto del coacutedigo permitiendo ademaacutes que se hagan lascomprobaciones de tipos correspondientes

Una clase de este tipo deberiacutea incluir meacutetodos para crear abrir o cerrar la base de datosTambieacuten puede ser un lugar conveniente para publicar constantes estaacuteticas relacionadascon la base de datos como por ejemplo constantes que almacenen el nombre de la tabla olas diferentes columnas

A continuacioacuten se muestra el coacutedigo de lo que podriacutea ser un adaptador para una base dedatos estaacutendar Incluye una extensioacuten de la clase SQLiteOpenHelper que seraacute tratada enmaacutes detalle en la siguiente seccioacuten y que se utiliza para simplificar la apertura lacreacioacuten y la actualizacioacuten de la base de datos

Persistencia

7Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

import androidcontentContextimport androiddatabaseimport androiddatabasesqliteimport androiddatabasesqliteSQLiteDatabaseCursorFactoryimport androidutilLog

public class MiAdaptadorBD private static final String NOMBRE_BASE_DATOS =

mibasededatosdbprivate static final String TABLA_BASE_DATOS = mainTableprivate static final int VERSION_BASE_DATOS = 1

Nombre de la columna de clave primariapublic static final String CP_ID=_id El nombre e iacutendice de cada columna en la base de datospublic static final String COLUMNA_NOMBRE=nombrepublic static final int COLUMNA_INDICE = 1 PENDIENTE crear campos adicionales para el resto de columnas de la base de datos

Sentencia SQL para crear una nueva base de datosprivate static final String CREAR_BASE_DATOS = create table +

TABLA_BASE_DATOS + ( + CP_ID + integer primary key autoincrement +COLUMNA_NOMBRE + text not null)

Variable para almacenar la instancia de la base de datosprivate SQLiteDatabase db Contexto de la aplicacioacuten que estaacute usando la base de datosprivate final Context contexto Clase helper usada para crear o actualizar la base de datos (hablamos de ella en la siguiente seccioacuten)private miHelperBD dbHelper

Crea la base de datos con ayuda del helperpublic MiAdaptadorBD(Context _contexto)

contexto = _contextodbHelper = new miHelperBD(contexto NOMBRE_BASE_DATOS

nullVERSION_BASE_DATOS)

Abre una base de datos ya existentepublic MiAdaptadorBD open() throws SQLException

db = dbHelpergetWritableDatabase()return this

Cierra la base de datos cuando eacutesta ya no va a ser utilizadapublic void close()

dbclose()

Meacutetodo para insertar un elemento en la tabla de la base dedatos

public int insertar(MiObjeto _miObjeto) PENDIENTE crear nuevos elementos ContentValues para

representar al objeto e insertarlos en la base de datos

Devolvemos el iacutendice del nuevo elemento en la base dedatos

return index

Persistencia

8Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Meacutetodo para eliminar un elemento de la tabla de la base dedatos

Devuelve un valor booleano indicando el eacutexito o no de la operacioacutenpublic boolean eliminar(long _indiceFila)

return dbdelete(TABLA_BASE_DATOS CP_ID + = +_indiceFila null) gt 0

Meacutetodo para obtener un Cursor que apunte a todas las filascontenidas

en la tabla de la base de datospublic Cursor obtenerTodos ()

return dbquery(TABLA_BASE_DATOS new String[] CP_IDCOLUMNA_NOMBRE

null null null nullnull)

Meacutetodo para obtener un elemento determinado de la base de datos a partir de su iacutendice de filapublic MiObjeto getObjeto(long _indiceFila)

PENDIENTE obtener un cursor a una fila de la base de datos y usar sus valores para inicializar una instancia de MiObjeto

return objectInstance

Meacutetodo para actualizar un elemento de la tabla de la base dedatos

public boolean actualizar(long _indiceFila MiObjeto _miObjeto) PENDIENTE crear nuevos ContentValues a partir del

objeto y usarlos para actualizar una fila en la tabla

correspondiente

return true

Clase privada auxiliar para ayudar en la creacioacuten yactualizacioacuten

de la base de datosprivate static class miHelperBD extends SQLiteOpenHelper

public miHelperBD(Context contexto String nombreCursorFactory factory int version)

super(contexto nombre factory version)

Meacutetodo llamado cuando la base de datos no existe en eldisco

y la clase Helper necesita crearla desde ceroOverridepublic void onCreate(SQLiteDatabase _db)

_dbexecSQL(CREAR_BASE_DATOS)

Meacutetodo llamado cuando la versioacuten de la base de datos en disco es diferente a la indicada en este caso la base de datos en disco necesita ser actualizadaOverridepublic void onUpgrade(SQLiteDatabase _db int

_versionAnterior

Persistencia

9Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

int _versionNueva) Mostramos en LogCat la operacioacutenLogw(AdaptadorBD Actualizando desde la

versioacuten +_versionAnterior + a la versioacuten +_versionNueva + lo cual borraraacute los

datospreviamente almacenados)

Actualizamos la base de datos para que seadapte a la nueva versioacuten

La forma maacutes sencilla de llevar a cabo dichaactualizacioacuten es

eliminar la tabla antigua y crear una nueva_dbexecSQL(DROP TABLE IF EXISTS +

TABLA_BASE_DATOS)onCreate(_db)

134 La clase SQLiteOpenHelper

La clase SQLiteOpenHelper es una clase abstracta utilizada como medio paraimplementar un patroacuten de creacioacuten apertura y actualizacioacuten de una determinada base dedatos Al implementar una subclase de SQLiteOpenHelper podemos abstraernos de laloacutegica subyacente a la decisioacuten de crear o actualizar una base de datos de manera previa aque eacutesta deba ser abierta

En el ejemplo de coacutedigo anterior se vio coacutemo crear una subclase de SQLiteOpenHelper

por medio de la sobrecarga de sus meacutetodos onCreate y onUpgrade los cuales permitencrear una nueva base de datos o actualizar a una nueva versioacuten respectivamente

NotaEn el ejemplo anterior el meacutetodo onUpgrade simplemente eliminaba la tabla existente y lareemplazaba con la nueva definicioacuten de la misma En la praacutectica la mejor solucioacuten seriacutea migrarlos datos ya existentes a la nueva tabla

Mediante los meacutetodos getReadableDatabase y getWritableDatabase podemos abrir yobtener una instancia de la base de datos de soacutelo lectura o de lectura y escriturarespectivamente La llamada a getWritableDatabase podriacutea fallar debido a falta deespacio en disco o cuestiones relativas a permisos por lo que es una buena praacutectica teneren cuenta esta posibilidad por medio del mecanismo de manejo de excepciones de JavaEn caso de fallo la solucioacuten podriacutea pasar por al menos proporcionar una copia de soacutelolectura tal como se muestra en el siguiente ejemplo

dbHelper = new miHelperBD(contexto NOMBRE_BASE_DATOS nullVERSION_BASE_DATOS)

SQLiteDatabase dbtry

db = dbHelpergetWritableDatabase() catch (SQLiteException ex)

db = dbHelpergetReadableDatabase()

Persistencia

10Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Al hacer una llamada a cualquiera de los dos meacutetodos anteriores se invocaraacuten losmanejadores de evento adecuados Asiacute pues si por ejemplo la base de datos no existieraen disco se ejecutariacutea el coacutedigo de onCreate Si por otra parte la versioacuten de la base dedatos se hubiera modificado se disparariacutea el manejador onUpgrade Lo que sucedeentonces es que al hacer una llamada a getWritableDatabase o getReadableDatabase

se devolveraacute una base de datos nueva actualizada o ya existente seguacuten el caso

135 Crear una base de datos sin SQLiteHelper

Tambieacuten es posible crear y abrir bases de datos sin necesidad de utilizar una subclase deSQLiteHelper Para ello haremos uso del meacutetodo openOrCreateDatabase En este casoseraacute necesario tambieacuten hacer uso del meacutetodo execSQL sobre la instancia de la base dedatos devuelta por el meacutetodo anterior para ejecutar los comandos SQL que permitiraacutencrear las tablas de la base de datos y establecer las relaciones entre ellas El siguientecoacutedigo muestra un ejemplo

private static final String NOMBRE_BASE_DATOS = mibasededatosdbprivate static final String TABLA_BASE_DATOS = tablaPrincipal

private static final String CREAR_BASE_DATOS =create table + TABLA_BASE_DATOS + _id integer primare key

autoincrement +columna_uno text not null)

SQLiteDatabase db

private void crearBaseDatos() db = openOrCreateDatabase(NOMBRE_BASE_DATOS ContextMODE_PRIVATE

null)dbexecSQL(CREAR_BASE_DATOS)

NotaAunque no es necesariamente obligatorio es recomendable que cada tabla incluya un campo detipo clave primaria con autoincremento a modo de iacutendice para cada fila En el caso en el que sedesee compartir la base de datos mediante un proveedor de contenidos un campo uacutenico de estetipo es obligatorio

136 Realizar una consulta

El resultado de cualquier consulta a una base de datos seraacute un objeto de la clase CursorEsto permite a Android manejar los recursos de manera maacutes eficiente de tal forma que enlugar de devolver todos los resultados eacutestos se van proporcionando conforme senecesitan

Para realizar una consulta a una base de datos haremos uso del meacutetodo query de lainstancia correspondiente de la clase SQLiteDatabase Los paraacutemetros que requiere este

Persistencia

11Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

meacutetodo son los siguientes

bull Un booleano opcional que permite indicar si el resultado deberiacutea contener tan soacutelovalores uacutenicos

bull El nombre de la tabla sobre la que realizar la consultabull Un array de cadenas que contenta un listado de las columnas que deben incluirse en el

resultadobull Una claacuteusula where que defina las filas que se deben obtener de la tabla Se puede

utilizar el comodiacuten el cual seraacute reemplazado por los valores que se pasen a traveacutesdel siguiente paraacutemetro

bull Un array de cadenas correspondientes a argumentos de seleccioacuten que reemplazaraacutenlos siacutembolos en la claacuteusula where

bull Una claacuteusula group by que define coacutemo se agruparaacuten las filas obtenidas comoresultado

bull Un filtro having que indique que grupos incluir en el resultado en el caso en el que sehaya hecho uso del paraacutemetro anterior

bull Una cadena que describa la ordenacioacuten que se llevaraacute a cabo sobre las filas obtenidascomo resultado

bull Una cadena opcional para definir un liacutemite en el nuacutemero de resultados a devolver

NotaComo se puede observar la mayoriacutea de estos paraacutemetros hacen referencia al lenguaje SQLTratar este tema queda fuera de los objetivos de este curso por lo que en nuestros ejemplos ledaremos en la mayoriacutea de las ocasiones un valor null a estos paraacutemetros

El siguiente coacutedigo muestra un ejemplo de consultas utilizando diversos paraacutemetros

Devolver los valores de las columnas uno y tres para todas las filas sin duplicadosString[] columnas = new String[] CLAVE_ID CLAVE_COL1 CLAVE_COL3

Cursor todasFilas = dbquery(true TABLA_BASE_DATOS columnas null nullnull null

null null)

Devolvemos los valores de todas las columnas para aquellas filas cuyovalor de la columna 3 sea igual al de una determinada variable Ordenamos las filasseguacuten el valor de la columna 5 Como queremos los valores de todas las columnas le damos al paraacutemetro correspondiente a las columnas a devolver el valor null En este caso no se ha hecho uso del primer paraacutemetro booleano que esoptativoString where = CLAVE_COL3 + = + valorString orden = CLAVE_COL5Cursor miResultado = dbquery(TABLA_BASE_DATOS null where null nullnull orden)

137 Extraer resultados de un cursor

Persistencia

12Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El primer paso para obtener resultados de un cursor seraacute desplazarnos a la posicioacuten apartir de la cual queremos obtener los datos usando cualquiera de los meacutetodosespecificados anteriormente (como por ejemplo moveToFirst o moveToPosition) Unavez hecho esto hacemos uso de meacutetodos get[TIPO] pasando como paraacutemetro el iacutendice dela columna Esto tendraacute como resultado la devolucioacuten del valor para dicha columna de lafila actualmente apuntada por el cursor

String valor = miResultadogetString(indiceColumna)

A continuacioacuten podemos ver un ejemplo maacutes completo en el que se va iterando a lo largode los resultados apuntados por un cursor extrayendo y sumando los valores de unacolumna de flotantes

int COLUMNA_VALORES_FLOTANTES = 2Cursor miResultado = dbquery(Tabla null null null null nullnull)float total = 0

Nos aseguramos de que se haya devuelto al menos una filaif (miResultadomoveToFirst())

Iteramos sobre el cursordo

float valor =miResultadogetFloat(COLUMNA_VALORES_FLOTANTES)

total += valor while (miResultadomoveToNext())

float media = total miResultadogetCount()

NotaDebido a que las columnas de las bases de datos en SQLite son debilmente tipadas podemosrealizar castings cuando sea necesario Por ejemplo los valores guardados como flotantes en labase de datos podriacutean leerse maacutes adelante como cadenas

138 Antildeadir actualizar y borrar filas

La clase SQLiteDatabase proporciona los meacutetodos insert delete y update queencapsulan las instrucciones SQL requeridas para llevar a cabo estas acciones Ademaacutesdisponemos de la posibilidad de utilizar el meacutetodo execSQL el cual nos permite ejecutarcualquier sentencia SQL vaacutelida en nuestras tablas de la base de datos en el caso en el quequeramos llevar a cabo estas (u otras) operaciones manualmente

NotaCada vez que modifiques los valores de la base de datos puedes actualizar un cursor que se puedaver afectado por medio del meacutetodo refreshQuery de la clase Cursor

Para insertar una nueva fila es necesario construir un objeto de la clase ContentValues

y usar su meacutetodo put para proporcionar valor a cada una de sus columnas Para insertar la

Persistencia

13Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

nueva fila pasaremos como paraacutemetro este objeto del tipo ContentValues al meacutetodoinsert de la instancia de la base de datos junto por supuesto con el nombre de la tablaA continuacioacuten se muestra un ejemplo

Crear la nueva filaContentValues nuevaFila = new ContentValues()

Asignamos valores a cada columnanuevaFilaput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas ]

Insertar la nueva fila en la tabladbinsert(TABLA_BASE_DATOS null nuevaFila)

NotaLos ficheros como imaacutegenes o ficheros de audio no se suelen almacenar dentro de tablas en unabase de datos En estos casos se suele optar por almacenar en la base de datos una cadena con laruta al fichero o una URI

La operacioacuten de actualizar una fila tambieacuten puede ser llevada a cabo por medio del usode la clase ContentValues En este caso deberemos llamar al meacutetodo update de lainstancia de la base de datos pasando como paraacutemetro el nombre de la tabla el objetoContentValues y una claacuteusula where que especifique la fila o filas a actualizar tal comose puede ver en el siguiente ejemplo

Creamos el objeto utilizado para definir el contenido a actualizarContentValues valoresActualizar = new ContentValues()

Asignamos valores a las columnas correspondientesvaloresActualizarput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas a actualizar ]

String where = CLAVE_ID + = + idFila

Actualizamos la fila con el iacutendice especificado en la instruccioacutenanteriordbupdate(TABLA_BASE_DATOS valoresActualizar where null)

Por uacuteltimo para borrar una fila simplemente llamaremos al meacutetodo delete de lainstancia de la base de datos especificando en este caso el nombre de la tabla que se veraacuteafectada por la operacioacuten y una claacuteusula where que utilizaremos para especificar las filasque queremos borrar Un ejemplo podriacutea ser el siguiente

dbdelete(TABLA_BASE_DATOS CLAVE_ID + = + idFila null)

Persistencia

14Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

2 Ejercicios - Persistencia en Android ficheros y SQLite

21 Uso de ficheros (05 puntos)

En este ejercicio vamos a crear una aplicacioacuten que muestre un listado de cadenas porpantalla Estas cadenas se almacenaraacuten en un fichero de texto privado para la aplicacioacutenPodremos antildeadir nuevas cadenas a partir de un cuadro de edicioacuten presente en la interfazPartiremos del proyecto Ficheros proporcionado en las plantillas de la aplicacioacuten Debesseguir los siguientes pasos

bull Antildeadir un manejador al botoacuten de la actividad principal para que cada vez que seapulsado se guarde en un fichero de texto El fichero se llamaraacute ficherotxt Al abrirel fichero para escritura se utilizaraacute el paraacutemetro ContextMODE_APPEND con lo quecada nueva cadena se antildeadiraacute al final del fichero en el caso en el que eacuteste ya existieraPara escribir en el fichero utilizaremos el meacutetodo writeBytes del objetoDataOutputStream correspondiente

bull Al pulsar el botoacuten tambieacuten se deberaacute borrar el contenido del elemento EditText (leasignamos a la vista una cadena vaciacutea con el meacutetodo setText)

bull Ejecutamos la aplicacioacuten e introducimos algunas liacuteneas en el fichero Vamos acomprobar ahora que todo ha funcionado correctamente Para ello accedemos alsistema de ficheros de nuestro dispositivo ejecutando el comando adb shell

dentro de la carpeta platform-tools de nuestra carpeta de instalacioacuten del SDK deAndroid

bull Comprueba que se ha creado el fichero ficherotxt en la carpetadatadataesuajtechandroidficherosfiles Examina su contenido ejecutando cat

ficherotxt Si algo no ha salido bien siempre podraacutes eliminar el fichero con rm

ficherotxtbull En la interfaz de la actividad se ha incluido una vista de tipo TextView debajo del

botoacuten Antildeade un manejador para dicha vista de tal forma que cada vez que se hagaclick sobre ella se muestre el contenido del fichero ficherotxt Para ello leeremos elfichero liacutenea a liacutenea antildeadiendo cada una al TextView por medio del meacutetodo append

Persistencia

15Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Interfaz de la aplicacioacuten Ficheros

22 Persistencia con ficheros (05 puntos)

Seguramente habraacutes observado que si abandonas la aplicacioacuten anterior (pulsando el botoacutenBACK del dispositivo) al volver a ejecutarla el contenido del TextView ha desaparecidoDebes volver a pulsar sobre la vista para que se vuelva a mostrar el contenido del ficheroHaz las modificaciones pertinentes para que esto no sea asiacute es decir para que cuandovuelva a mostrarse la actividad tras haber sido eliminada de la memoria se muestre elTextView tal cual se veiacutea antes de abandonar la aplicacioacuten

AvisoEn este ejercicio no se estaacute pidiendo que cargues el contenido del fichero en el TextView nadamaacutes arrancar la aplicacioacuten Puede darse el caso de que lo que esteacute mostrando el TextViewcuando la actividad sea eliminada de memoria no se corresponda con el contenido actualizado delfichero

NotaPara poder completar el ejercicio deberaacutes repasar los manejadores de evento de la primera sesioacutendel moacutedulo de Android relacionados con el ciclo de vida de ejecucioacuten de actividades

23 Base de datos SQLiteOpenHelper (05 puntos)

En las plantillas de la aplicacioacuten se incluye la aplicacioacuten BaseDatos En el proyecto de laaplicacioacuten se incluye el esqueleto de una clase MiAdaptadorBD que tendremos quecompletar Se trata de un patroacuten que nos va a permitir acceder a una base de datos de

Persistencia

16Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

usuarios dentro de la aplicacioacuten sin necesidad de hacer uso de coacutedigo SQL

La clase MiAdaptadorBD incluye a su vez otro patroacuten en este caso implementado comouna subclase de SQLiteOpenHelper Eacuteste nos obliga a definir queacute ocurre cuando la basede datos todaviacutea no existe y debe ser creada y queacute ocurre si ya existe pero debe seractualizada porque ha cambiado de versioacuten Asiacute el SQLiteOpenHelper queimplementemos en este caso MiOpenHelper nos devolveraacute siempre una base de datosseparaacutendonos de la loacutegica encargada de comprobar si la base de datos existe o no

En este primer ejercicio se pide hacer lo siguiente

bull Ejecutar la sentencia de creacioacuten de bases de datos (la tenemos declarada comoconstante de la clase) en el meacutetodo MiOpenHelperonCreate

bull Implementar tambieacuten el meacutetodo onUpgrade Idealmente eacuteste deberiacutea portar las tablasde la versioacuten antigua a la versioacuten nueva copiando todos los datos Nosotros vamos aeliminar directamente la tabla que tenemos con la sentencia SQL DROP TABLE IF

EXISTS + NOMBRE_TABLA y volveremos a crearlabull En el constructor de MiAdaptadorBD debemos obtener en el campo db la base de

datos utilizando MiOpenHelper

24 Base de datos insercioacuten y borrado (05 puntos)

Continuamos trabajando con el proyecto anterior En este ejercicio completaremos elcoacutedigo relacionado con las sentencias de insercioacuten y borrado Para realizar la insercioacutenvamos a hacer uso de un mecanismo que no hemos visto en la sesioacuten de teoriacutea y queconsiste en utilizar una sentencia de insercioacuten SQL precompilada que se completaraacute conlos valores concretos a insertar en la base de datos justo antes de realizar dicha insercioacutenLa ventaja de las sentencias compiladas es que evitan que se produzcan ataques deinyeccioacuten de coacutedigo

Como se puede observar se ha incluido un atributo a la clase que representa la insercioacutenSQL

private static final String INSERT = insert into + NOMBRE_TABLA +(+COLUMNAS[1]+) values ()

En esta sentencia no se indica ninguacuten valor concreto para la columna nombre En el lugaren el que deberiacutean aparecer los valores de dicho campo se ha escrito simplemente unsiacutembolo de interrogacioacuten Tambieacuten se ha antildeadido una instancia de la claseSQLiteStatement como parte de los atributos de la clase

private SQLiteStatement insertStatement

Realizamos los siguientes pasos

bull En el constructor de la clase MiAdaptadorBD llevamos a cabo la compilacioacuten de lasentencia

thisinsertStatement = thisdbcompileStatement(INSERT)

Persistencia

17Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Una vez hecho esto cada vez que deseemos insertar un nuevo usuario en la base dedatos deberemos dar valores concretos a la columna nombre y ejecutar la sentenciaPara ello debemos antildeadir el siguiente coacutedigo dentro del meacutetodo insert de la claseMiAdaptadorBD

thisinsertStatementbindString(1 nombreUsuario)return thisinsertStatementexecuteInsert()

bull Por uacuteltimo completamos el coacutedigo del meacutetodo deleteAll cuyo contenido es eliminara todos los usuarios de la base de datos Para que ademaacutes se devuelva el nuacutemero defilas afectadas podemos insertar la siguiente liacutenea en dicho meacutetodo

return dbdelete(NOMBRE_TABLA null null)

Una vez hechos todos estos cambios podremos utilizar la clase MiAdaptadorBD parahacer operaciones con la base de datos de usuarios de manera transparente

25 Base de datos probar nuestro adaptador (05 puntos)

Antildeade coacutedigo en la actividad Main para eliminar todos los usuarios de la base de datosantildeadir dos cualesquiera y listarlos por medio de la vista de tipo TextView que seencuentra en el layout de dicha actividad

Podemos comprobar mediante la liacutenea de comandos (comando adb shell) que la basede datos ha sido creada Para ello puede ser uacutetil hacer uso de los siguientes comandos

cd datadataesuajtechandroidbasedatosdatabasessqlite3 misusuariosdbsqlitegt schemasqlitegt tablessqlitegt select from usuarios

26 Base de datos cambios en la base de datos (05 puntos)

Ahora vamos a cambiar en la clase MiAdaptadorBD el nombre de la segunda columnaque en lugar de nombre se va a llamar nombres Ejecutamos la aplicacioacuten y comprobamosque sale con una excepcioacuten Comprueba por medio de LogCat cuaacutel ha sido el erroriquestCoacutemo lo podemos solucionar

NotaPista conforme hemos programado la clase MiAdaptadorBD y siguiendo el patroacuten de disentildeode SQLiteOpenHelper es posible arreglar el problema simplemente modificando el valor deun campo

Persistencia

18Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

3 Persistencia en Android proveedores de contenidos ySharedPreferences

31 Shared Preferences

Comenzamos esta sesioacuten hablando de una de las teacutecnicas maacutes simples de persistencia enAndroid junto al uso de ficheros Shared Preferences Se trata de un mecanismo ligeropara almacenar datos basado en pares clave-valor utilizado normalmente para guardarpreferencias u opciones de la aplicacioacuten Tambieacuten puede ser utilizado para almacenar elestado de la interfaz graacutefica para contemplar el caso de que nuestra actividad debafinalizar abruptamente su ejecucioacuten Otro posible uso es el de compartir informacioacutenentre componentes de una misma aplicacioacuten

NotaLos Shared Preferences nunca son compartidos entre diferentes aplicaciones

Este meacutetodo se basa en el uso de la clase SharedPreferences Es posible utilizarla paraguardar datos en muy diversos formatos booleanos cadenas flotantes y enteros

311 Guardar Shared Preferences

Para crear o modificar un Shared Preference llamamos al meacutetodogetSharedPreferences del contexto de la aplicacioacuten pasando como paraacutemetro elidentificador del conjunto de valores de preferencias con el que queremos trabajar Pararealizar la modificacioacuten del valor de un Shared Preference hacemos uso de la claseSharedPreferencesEditor Para obtener el objeto editor invocamos el meacutetodo edit

del objeto Shared Preferences que queremos modificar Los cambios se guardan medianteel meacutetodo commit Veamos un ejemplo

int modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences = getSharedPreferences(Mispreferencias modo)

Obtenemos un editor para modificar las preferenciasSharedPreferencesEditor editor = misSharedPreferencesedit()

Guardamos nuevos valores en el objeto SharedPreferenceseditorputBoolean(isTruetrue)editorputFloat(unFloat10)editorputInt(numeroEntero12)editorputLong(otroNumero2)editorputString(unaCadenavalor)

Guardamos los cambioseditorcommit()

Persistencia

19Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

312 Leer Shared Preferences

Para leer Shared Preferences utilizamos el meacutetodo getSharedPreferences tal como seha comentado anteriormente Pasamos como paraacutemetro el identificador del conjunto deShared Preferences al que deseamos acceder Una vez hecho esto ya podemos hacer usode meacutetodos de tipo get para acceder a preferencias individuales Hay diferentes meacutetodosget para diferentes tipos de datos Cada uno de ellos requiere como paraacutemetro la claveque identifica la preferencia cuyo valor deseamos obtener y un valor por defecto el cualse usaraacute en el caso concreto en el que no existiera la preferencia con la clave pasada comoprimer paraacutemetro Podemos ver un ejemplo a continuacioacuten

public static String MIS_PREFS = MIS_PREFS

public void cargarPreferences() Obtener las preferencias almacenadasint modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences =

getSharedPreferences(MIS_PREFS modo)

boolean isTrue = misSharedPreferencesgetBoolean(isTruefalse)float unFloat = misSharedPreferencesgetFloat(unFloat0)int numeroEntero = misSharedPreferencesgetInt(numeroEntero0)long otroNumero = misSharedPreferencesgetLong(otroNumero0)String unaCadena = misSharedPreferencesgetString(unaCadena)

313 Interfaces para Shared Preferences

Android proporciona una plataforma basada en XML para la creacioacuten de interfacesgraacuteficas para el manejo de preferencias Estas interfaces tendraacuten un aspecto similar al delas del resto de aplicaciones del sistema Al utilizarla nos estaremos asegurando de quenuestras actividades para el manejo de preferencias seraacuten consistentes con las del resto deaplicaciones del sistema De esta forma los usuarios estaraacuten familiarizados con el manejode esta parte de nuestra aplicacioacuten

La plataforma consiste en tres elementos

bull Layout de la actividad de preferencias se trata de un archivo XML que definiraacute lainterfaz graacutefica de la actividad que muestre los controles para modificar los valores delas preferencias Permite especificar las vistas a mostrar los posibles valores que sepueden introducir y a queacute clave de Shared Preferences se corresponde cada vista

bull Actividad de preferencias una subclase de PreferenceActivity que secorresponderaacute con la pantalla de preferencias de nuestra aplicacioacuten

bull Shared Preference Change Listener una implementacioacuten de la claseonSharedPreferenceChangeListener que actuaraacute en el caso en el que cambie elvalor de alguna Shared Preference

314 Definiendo una pantalla de preferencias con un layout en XML

Persistencia

20Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esta es sin duda la parte maacutes importante de una actividad de preferencias Se trata de unarchivo XML utilizado para definir varios aspectos de dicha actividad Al contrario queen el caso de los archivos de layout para interfaces graacuteficas que vimos en la sesioacutencorrespondiente del moacutedulo de Android estos archivos de layout se guardan en la carpetaresxml de los recursos de la aplicacioacuten

Aunque conceptualmente estos layouts son similares a los utilizados para definirinterfaces graacuteficas los layouts de actividades de preferencias utilizan un conjuntoespecializado de componentes especiacuteficos para este tipo de actividades Estaacuten pensadospara proporcionar interfaces con un aspecto similar al del resto de actividades depreferencias del sistema Estos controles se describen en maacutes detalle en la siguienteseccioacuten

Los layout de preferencias contendraacuten un elemento PreferenceScreen

ltxml version=10 encoding=utf-8gtltPreferenceScreen

xmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceScreengt

Se pueden antildeadir maacutes elementos de este tipo si se desea Al hacerlo eacutestos serepresentaraacuten como un elemento seleccionable en un listado que mostraraacute una nuevaventana de preferencias en caso de que sea seleccionado

Dentro de cada elemento PreferenceScreen podemos incluir tantos elementos de tipoPreferenceCategory y elementos especiacuteficos para antildeadir controles como se desee Loselementos PreferenceCategory son utilizados para dividir cada pantalla de preferenciasen subcategoriacuteas mediante una barra de tiacutetulo Su sintaxis es la siguiente

ltPreferenceCategoryandroidtitle=My Preference Categorygt

ltPreferenceCategorygt

Una vez dividida la pantalla en subcategoriacuteas tan soacutelo quedariacutea antildeadir los elementoscorrespondientes a los controles especiacuteficos que veremos en la siguiente seccioacuten Losatributos de los elementos XML para estos controles pueden variar aunque existe unconjunto de atributos comunes

bull androidkey la clave de Shared Preference que se usaraacute para guardar el valor delcontrol

bull androidtitle texto a mostrar para describir la preferenciabull androidsummary una descripcioacuten maacutes larga a mostrar debajo del texto definido con

el campo anteriorbull androiddefaultValue el valor por defecto que se mostraraacute inicialmente y tambieacuten

el que finalmente se guardaraacute si ninguacuten otro valor fue asignado a este campo

El siguiente listado muestra una pantalla de preferencias muy simple con una uacutenicacategoriacutea y un uacutenico control (un check box)

ltxml version=10 encoding=utf-8gt

Persistencia

21Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltPreferenceScreenxmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceCategory

androidtitle=Mi categoriacuteagtltCheckBoxPreference

androidkey=MI_CHECK_BOXandroidtitle=Preferencia Check Boxandroidsummary=Descripcioacuten de la preferencia

Check BoxandroiddefaultValue=true

gtltPreferenceCategorygt

ltPreferenceScreengt

Esta pantalla de preferencias se mostrariacutea de la siguiente forma

Ejemplo sencillo de ventana de preferencias

315 Controles nativos para preferencias

Android incluye diferentes controles que podemos antildeadir a nuestras ventanas depreferencias por medio de los elementos XML que se indican a continuacioacuten

bull CheckBoxPreference un check box estaacutendar que puede ser utilizado parapreferencias de tipo booleano

bull EditTextPreference permite al usuario introducir una cadena como valor para unapreferencia Al seleccionar el texto se mostraraacute un diaacutelogo con el que introducir elnuevo valor

bull ListPreference el equivalente a un Spinner Seleccionar este elemento de lainterfaz mostraraacute un diaacutelogo con las diferentes opciones que es posible seleccionar Seutilizan arrays para asociar valores y textos a las opciones de la lista

bull RingtonePreference un tipo especiacutefico de preferencia de tipo listado que permiteseleccionar entre diferentes tonos de teleacutefono Esta opcioacuten es particularmente uacutetil en

Persistencia

22Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

el caso en el que se esteacute creando una pantalla de preferencias para configurar lasopciones de notificacioacuten de una aplicacioacuten o componente de la misma

Tambieacuten es posible crear nuestros propios controles personalizados definiendo unasubclase de Preference (o cualquiera de sus subclases) Se puede encontrar maacutesinformacioacuten en la documentacioacuten de AndroidhttpdeveloperandroidcomreferenceandroidpreferencePreferencehtml

316 Actividades de preferencias

Para mostrar una pantalla de preferencias debemos definir una subclase dePreferenceActivity

public class MisPreferencias extends PreferenceActivity

La interfaz graacutefica de la interfaz se puede crear a partir del layout en el meacutetodo onCreatemediante una llamada a addPreferencesFromResource

Overridepublic void onCreate(Bundle savedInstaceState)

superonCreate(savedInstanceState)addPreferencesFromResource(Rxmlmilayout)

Como en el caso de cualquier otra actividad la actividad de preferencias que hayamosdefinido debe ser incluida en el Manifest de la aplicacioacuten

ltactivity androidname=MisPreferenciasandroidlabel=Mis preferenciasgt

ltactivitygt

Eso es todo lo que necesitamos para crear este tipo de actividades y mostrar una ventanacon las preferencias definidas en el layout Esta actividad puede ser iniciada comocualquier otra mediante un Intent ya sea a traveacutes de una llamada a startActivity obien a startActivityForResult

Intent i = new Intent(this MisPreferenciasclass)startActivityForResult(i MOSTRAR_PREFERENCIAS)

Los valores para las diferentes preferencias de la actividad son almacenadas en elcontexto de la aplicacioacuten Esto permite a cualquier componente de dicha aplicacioacutenincluyendo a cualquier actividad y servicio de la misma acceder a los valores tal como semuestra en el siguiente coacutedigo de ejemplo

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto) PENDIENTE hacer uso de meacutetodos get para obtener los valores

317 Shared Preference Change Listeners

Persistencia

23Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El uacuteltimo elemento que nos queda por examinar en esta plataforma de Shared Preferenceses la interfaz onSharedPreferenceChangeListener que es utilizada para invocar unevento cada vez que una preferencia es antildeadida eliminada o modificada Para registrarListeners usamos un coacutedigo como el que se muestra a continuacioacuten en el que la parte maacutesimportante es sin duda el meacutetodo onSharedPreferenceChanged dentro del cualdeberemos determinar queacute preferencia ha cambiado de estado a partir de sus paraacutemetrospara llevar a cabo las acciones oportunas

public class MiActividad extends Activity implementsOnSharedPreferenceChangeListener

Overridepublic void onCreate(Bundle SavedInstanceState)

Registramos este objetoOnSharedPreferenceChangeListener

Context contexto = getApplicationContext()SharedPreferences prefs =

PreferenceManagergetDefaultSharedPreferences(contexto)prefsregisterOnSharedPreferenceChangeListener(this)

public void onSharedPreferenceChanged(SharedPreferences prefsString clave)

PENDIENTE comprobar queacute clave concreta ha cambiado ysu nuevo valor

para modificar la interfaz de una actividad o elcomportamiento de

alguacuten componente

Otra forma maacutes sencilla de controlar los cambios en las preferencias es registrar unlistener en el constructor de la actividad principal o aquella que haga uso de los valores depreferencias de la forma

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto)

mListener = new OnSharedPreferenceChangeListener() public void onSharedPreferenceChanged(SharedPreferences

sharedPreferences String key)

if( keyequals(MICLAVE) )boolean valor =

sharedPreferencesgetBoolean(MICLAVE false)

prefsregisterOnSharedPreferenceChangeListener(mListener)

NotaEn este segundo ejemplo mListener es una variable miembro de la clase Para este caso esnecesario registrar el listener de esta forma porque sino el garbage collector lo eliminariacutea (debidoa particularidades en la programacioacuten de los listeners asociados a las SharedPreferences)

Persistencia

24Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

32 Proveedores de contenidos

Los proveedores de contenidos o ContentProvider proporcionan una interfaz parapublicar y consumir datos identificando la fuente de datos con una direccioacuten URI queempieza por content Son una forma maacutes estaacutendar que los adaptadores (como el quevimos en la sesioacuten anterior en la seccioacuten dedicada a SQLite) de desacoplar la capa deaplicacioacuten de la capa de datos

Podemos encontrar dos tipos principales de proveedores de contenidos en Android Losproveedores nativos son proveedores que el propio sistema nos proporciona para poderacceder a datos del dispostivo incluyendo audio viacutedeo imaacutegenes informacioacuten personaletc Por otra parte tambieacuten es posible que creemos nuestros propios proveedores con talde permitir a nuestra aplicacioacuten compartir datos con el resto del sistema En esta sesioacutenhablaremos de ambos tipos

321 Proveedores nativos

Android incluye una serie de proveedores de contenidos que nos pueden proporcionarinformacioacuten de diferentes tipos informacioacuten sobre nuestros contactos informacioacuten delcalendario e incluso ficheros multimedia Ejemplos de estos proveedores de contenidosnativos son el Browser CallLog ContactsContract MediaStore Settings y elUserDictionary Para poder hacer uso de estos proveedores de contenidos y acceder alos datos que nos proporcionan debemos antildeadir los permisos adecuados al ficheroAndroidManifestxml Por ejemplo para acceder al listiacuten telefoacutenico podemos antildeadir elpermiso correspondiente de la siguiente forma

ltuses-sdk androidminSdkVersion=8 gtltuses-permission androidname=androidpermissionREAD_CONTACTSgt

ltmanifestgt

Para obtener informacioacuten de un ContentProvider debemos hacer uso del meacutetodo query

de la clase ContentResolver El primero de los paraacutemetros de dicho meacutetodo seraacute la URIdel proveedor de contenidos al que deseamos acceder Este meacutetodo devuelve un cursorque nos permitiraacute iterar entre los resultados tal como se vio en la sesioacuten anterior en elapartado de cursores

ContentResolverquery(Uri uriString[] projectionString selectionString[] selectionArgsString sortOrder)

Por ejemplo la siguiente llamada nos proporcionaraacute un cursor que nos permitiraacute acceder atodos los contactos de nuestro teleacutefono moacutevil Para ello hemos hecho uso de la constante

Persistencia

25Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ContactsContractContactsCONTENT_URI que almacena la URI del proveedor decontenidos correspondiente Al resto de paraacutemetros se le ha asignado el valor null

ContentResolver cr = getContentResolver()Cursor cursor = crquery(ContactsContractContactsCONTENT_URI

null null null null)

NotaEn versiones anteriores a Android 20 las estructuras de datos de los contactos son diferentes y nose accede a esta URI

Una vez obtenido el cursor es posible mapear sus campos con alguacuten componente de lainterfaz graacutefica de la actividad De esta forma cualquier cambio que se produzca en elcursor se reflejaraacute automaacuteticamente en el componente graacutefico sin que sea necesario queprogramemos el coacutedigo que lo refresque Para esto tenemos que llamar al meacutetodosetNotificationUri del cursor con lo cual nos aseguraremos de que los listeners seraacutennotificados cuando se produzca alguacuten cambio en los datos referenciados por dicho cursor

cursorsetNotificationUri(crContactsContractContactsCONTENT_URI)

Para asignar un adaptador a una lista de la interfaz graacutefica de la actividad maacutesconcretamente una ListView utilizamos el meacutetodo setAdapter(Adapter)

ListView lv = (ListView)findViewById(RidListView01)SimpleCursorAdapter adapter = new SimpleCursorAdapter(

getApplicationContext()Rlayouttextviewlayoutcursornew String[]

ContactsContractContacts_IDContactsContractContactsDISPLAY_NAME

new int[]RidTextView1RidTextView2)

lvsetAdapter(adapter)

En este ejemplo los identificadores RidTextView1 y RidTextView2 se correspondencon views del layout que define cada fila de la ListView como se puede ver en elarchivo textviewlayoutxml que tendriacuteamos que crear

ltxml version=10 encoding=utf-8gt

ltLinearLayout xmlnsandroid=httpschemasandroidcomapkresandroidandroidorientation=horizontalandroidlayout_width=fill_parentandroidlayout_height=wrap_contentgt

ltTextView androidid=+idTextView1androidtextStyle=bold

Persistencia

26Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

androidems=2androidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltTextView androidid=+idTextView2androidtextStyle=boldandroidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltLinearLayoutgt

La lista en siacute (identificada por RidListView01 en el presente ejemplo) estariacutea en otroXML layout por ejemplo en mainxml

Se debe tener en cuenta que en el caso de este ejemplo si quisieacuteramos obtener losnuacutemeros de teleacutefono de cada uno de nuestros contactos tendriacuteamos que recorrer el cursorobtenido a partir del proveedor de contenidos y por cada persona en nuestra agenda crearun nuevo cursor que recorriera los teleacutefonos ya que una persona puede tener asignadosvarios teleacutefonos

Puedes acceder a un listado completo de los proveedores de contenidos nativos existentesen Android consultando su manual de desarrollo Concretamente leyendo la seccioacutendedicada al paquete androidprovider enhttpdeveloperandroidcomreferenceandroidproviderpackage-summaryhtml

322 Proveedores propios crear un nuevo proveedor de contenidos

Para acceder a nuestras fuentes de datos propias de manera estaacutendar nos interesaimplementar nuestros propios ContentProvider Gracias a que proporcionamos nuestrosdatos a partir de una URI quien use nuestro proveedor de contenidos no deberaacutepreocuparse de doacutende provienen los datos indicados por dicha URI podriacutean provenir deficheros locales en la tarjeta de memoria de un servidor en Internet o de una base dedatos SQLite Dada una URI nuestro proveedor de contenidos deberaacute proporcionar en suinterfaz los meacutetodos necesarios para realizar las operaciones baacutesicas en una base de datosinsercioacuten lectura actualizacioacuten y borrado

Para crear un nuevo proveedor de contenidos definimos una subclase deContentProvider Utilizaremos una sobrecarga del meacutetodo onCreate para crear einicializar la fuente de datos que queramos hacer puacuteblica a traveacutes de dicho proveedor Unesqueleto de subclase de ContentProvide podriacutea ser el siguiente

public class MiProveedor extends ContentProvider

Overridepublic boolean onCreate()

Inicializar la base de datosreturn true

Dentro de la clase deberemos crear un atributo puacuteblico y estaacutetico de nombreCONTENT_URI en el cual almacenaremos el URI completo del proveedor que estamos

Persistencia

27Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creando Este URI debe ser uacutenico por lo que una buena idea puede ser tomar como baseel nombre de paquete de nuestra aplicacioacuten La forma general de un URI es la siguiente

content[NOMBRE_PAQUETE]provider[NOMBRE_APLICACION][RUTA_DATOS]

Por ejemplo

contentesuajtechandroidprovidermiaplicacionelementos

En general (y por simplicidad) no es necesario antildeadir la cadena provider al nombre delpaquete por lo que podemos utilizar directamente el nombre del paquete seguido de laruta de datos de la forma

contentesuajtechandroidmiaplicacionelementos

Estos URIs se pueden presentar en dos formas La URI anterior representa una consultadirigida a obtener todos los valores de ese tipo (en este caso todos los elementos) Si a laURI anterior antildeadimos un sufijo [NUMERO_FILA] estamos representando una consultadestinada a obtener un uacutenico elemento (por ejemplo el quinto elemento en el siguienteejemplo)

contentesuajtechandroidprovidermiaplicacionelementos5

NotaSe considera una buena praacutectica de programacioacuten el proporcionar acceso a nuestro proveedor decontenidos utilizando cualquiera de estas dos formas

La forma maacutes simple de determinar cuaacutel de las dos formas anteriores de URI se utilizoacutepara hacer una peticioacuten a nuestro proveedor de contenidos es mediante un Uri MatcherCrearemos y configuramos un elemento Uri Matcher para analizar una URI y determinara cuaacutel de las dos formas se corresponde En el siguiente coacutedigo se muestra el coacutedigobaacutesico que deberemos emplear para implementar este patroacuten

public class MiProveedor extends ContentProvider

private static final String miURI =contentesuajtechandroidprovidermiaplicacionelementos

public static final Uri CONTENT_URI = Uriparse(miUri)

Creamos las constantes que nos van a permitir diferenciar entre los dos tipos distintos de peticiones a partir de la URIprivate static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

Inicializamos el objeto UriMatcher Una URI que termine en elementos se corresponderaacute con una consulta para todas las filas mientras que una URI con el sufijo elementos[FILA] representaraacute una uacutenica filastatic

uriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidprovidermiaplicacion

elementos TODAS_FILAS)

Persistencia

28Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

uriMatcheraddURI(esuajtechandroidprovidermiaplicacionelementos UNA_FILA)

Overridepublic boolean onCreate()

PENDIENTE inicializar la base de datosreturn true

Esta misma teacutecnica se puede emplear para proporcionar diferentes alternativas de URIpara diferentes subconjuntos de datos como por ejemplo diferentes tablas en una base dedatos a traveacutes del mismo proveedor de contenidos

323 Proveedores propios crear la interfaz de consultas

Para permitir realizar operaciones con los datos publicados a traveacutes de nuestro proveedorde contenidos deberemos implementar los meacutetodos delete insert update y query

para el borrado insercioacuten actualizacioacuten y realizacioacuten de consultas respectivamenteEstos meacutetodos son la interfaz estaacutendar utilizada con proveedores de contenidos de formaque para compartir datos entre aplicaciones no se tendraacuten interfaces distintos paradiferentes fuentes de datos

Lo maacutes habitual suele ser implementar un proveedor de contenidos como un mecanismopara permitir el acceso a una base de datos SQLite privada a una aplicacioacuten aunque atraveacutes de estos meacutetodos indicados se podriacutea en principio acceder a cualquier fuente dedatos

El siguiente coacutedigo extiende el ejemplo anterior (MiProveedor) y muestra el esqueleto deestos meacutetodos dentro de una subclase de ContentProvider Obseacutervese que se hace usodel objeto UriMatcher para determinar que tipo de consulta se estaacute realizando

Overridepublic Cursor query(Uri uri

String[] projectionString selectionString[] selectionArgsString sort)

Si se trata de una consulta para obtener una uacutenica filalimitamos

los resultados a obtener de la fuente de datos por medio de una claacuteusula whereswitch(UriMatchermatch(uri))

case UNA_FILA PENDIENTE modifica la sentencia SELECT

mediante una claacuteusula where obteniendo el identificador de fila

como numeroFila = urigetPathSegments()get(1))

return null

Override

Persistencia

29Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public Uri insert(Uri _uri ContentValues _valores) long idFila = [ Antildeadir un nuevo elemento ]

Devolvemos la URI del elemento recieacuten antildeadidoif (idFila gt 0)

return ContentUriswithAppendendId(CONTENT_URI idFila)

throw new SQLException(No se pudo antildeadir un nuevo elemento a +_uri)

Overridepublic int delete(Uri uri String where String[] whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

Overridepublic int update(Uri uri ContentValues valores String where String[]whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

324 Proveedores propios tipo MIME

El uacuteltimo paso para crear un proveedor de contenidos es definir el tipo MIME queidentifica el tipo de datos que devuelve el proveedor Debemos sobrecargar el meacutetodogetType para que devuelva una cadena que identifique nuestro tipo de datos de manerauacutenica Se deberiacutean definir dos tipos posibles uno para el caso de una uacutenica fila y otro parael caso de devolver todos los resultados Se debe utilizar una sintaxis similar a la delsiguiente ejemplo

bull Un uacutenico elementovndesuajtechandroidmiaplicacioncursoritemmiproveedorcontent

bull Todos los elementosvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

Como se puede observar la cadena comienza siempre por vnd A continuacioacutentendriacuteamos el nombre del paquete de nuestra aplicacioacuten Lo siguiente es cursor ya quenuestro proveedor de contenidos estaacute devolviendo datos de tipo Cursor Justo antes de labarra pondremos item en el caso del tipo MIME para un uacutenico elemento y dir para elcaso del tipo MIME para todos los elementos Finalmente tras la barra pondremos elnombre de nuestra clase (en minuacutesculas) seguido de content En el siguiente coacutedigo deejemplo se muestra coacutemo sobrecargar getType seguacuten la URI

Persistencia

30Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Overridepublic String getType(Uri _uri)

switch (uriMatchermatch(_uri)) case TODAS_FILAS

returnvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

case UNA_FILAreturn

vndesuajtechandroidmiaplicacioncursoritemmiproveedorcontentdefault

throw new IllegalArgumentException(URI nosoportada + _uri)

325 Proveedores propios registrar el proveedor

No debemos olvidar incorporar nuestro proveedor de contenidos al Manifest de laaplicacioacuten una vez que lo hemos completado Esto se haraacute por medio del elementoprovider cuyo atributo authorities podremos utilizar para especificar la URI base talcomo se puede ver en el siguiente ejemplo

ltprovider androidname=MiProveedorandroidauthorities=esuajtechandroidmiaplicaciongt

326 Content Resolvers

Aunque en la seccioacuten de proveedores nativos ya se ha dado alguacuten ejemplo de coacutemo haceruso de un Content Resolver para realizar una consulta ahora que hemos podido observaren detalle la estructura de un proveedor de contenidos propio es un buen momento paraver en detalle coacutemo realizar cada una de las operaciones que eacutestos permiten

El contexto de una aplicacioacuten incluye siempre una instancia de la claseContentResolver a la cual se puede acceder mediante el meacutetodo getContentResolver

ContentResolver cr = getContentResolver()

Esta clase incorpora diversos meacutetodos para realizar consultas a proveedores decontenidos Cada uno de estos meacutetodos acepta como paraacutemetro una URI que indica conqueacute proveedor de contenidos se desea interactuar

Las consultas realizadas sobre un proveedor de contenidos recuerdan a las consultasrealizadas sobre bases de datos Los resultados de las consultas se devuelven comoobjetos de la clase Cursor Se pueden extraer valores del cursor de la misma forma en laque se explicoacute en el apartado de bases de datos de la sesioacuten anterior El meacutetodo query dela clase ContentResolver acepta los siguientes paraacutemetros

bull La URI del proveedor del contenidos al que se desea realizar la consultabull Una proyeccioacuten que enumera las columnas que se quieren incluir en los resultados

obtenidosbull Una claacuteusula where que define las filas a obtener Se pueden utilizar comodines que

Persistencia

31Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

se reemplazaraacuten por valores incluidos en el siguiente paraacutemetrobull Una matriz de cadenas que se pueden utilizar para reemplazar los comodines en la

claacuteusula where anteriorbull Una cadena que describa la ordenacioacuten de los elementos devueltos

El siguiente coacutedigo muestra un ejemplo

ContentResolver cr = getContentResolver() Devolver todas las filasCursor todasFilas = crquery(MiProveedorCONTENT_URI null null nullnull) Devolver todas columnas de las filas para las que la columna 3 tiene un valor determinado ordenadas seguacuten el valor de la columna 5String where = COL3 + = + valorString orden = COL5Cursor algunasFilas = crquery(MiProveedorCONTENT_URI null where nullorden)

327 Otras operaciones con Content Resolvers

Para realizar otro tipo de operaciones con los datos de un proveedor de contenidosharemos uso de los meacutetodos delete update e insert de un objeto ContentResolver

Con respecto a la insercioacuten la clase ContentResolver proporciona en su interfaz dosmeacutetodos diferentes insert y bulkInsert Ambos aceptan como paraacutemetro la URI delproveedor de contenidos pero mientras que el primer meacutetodo tan solo toma comoparaacutemetro un objeto de la clase ContentValues (ya vistos en la seccioacuten de bases dedatos) el segundo toma como paraacutemetro una matriz de elementos de este tipo El meacutetodoinsert devolveraacute la URI del elemento recieacuten antildeadido mientras que bulkInsert

devolveraacute el nuacutemero de filas correctamente insertadas

Obtener el Content ResolverContentResolver cr = getContentResolver()

Crear una nueva fila de valores a insertarContentValues nuevosValores = new ContentValues()

Asignar valores a cada columnanuevosValoresput(NOMBRE_COLUMNA nuevoValor)[ Repetir para cada columna ]

Uri miFilaUri = crinsert(MiProveedorCONTENT_URI nuevosValores)

Insertamos ahora una matriz de nuevas filasContentValues[] arrayValores = new ContentValues[5] PENDIENTE rellenar el array con los valores correspondientesint count = crbulkInsert(MiProveedorCONTENT_URI arrayValores)

Para el caso del borrado hacemos uso del meacutetodo delete del Content Resolver pasandocomo paraacutemetro la URI de la fila que deseamos eliminar de la fuente de datos Otraposibilidad es incluir una claacuteusula where para eliminar muacuteltiples filas Ambas teacutecnicas semuestran en el siguiente ejemplo

ContentResolver cr = getContentResolver()

Persistencia

32Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Eliminar una fila especiacuteficacrdelete(miFilaUri null null) Eliminar las primeras cinco filasString where = _id lt 5crdelete(MiProveedorCONTENT_URI where null)

Una actualizacioacuten se lleva a cabo mediante el meacutetodo update del objeto ContentResolver Este meacutetodo toma como paraacutemetro la URI del proveedor de contenidosobjetivo un objeto de la clase ContentValues que empareja nombres de columna convalores actualizados y una claacuteusula where que indica queacute filas deben ser actualizadas Elresultado de la actualizacioacuten seraacute que en cada fila en la que se cumpla la condicioacuten de laclaacuteusula where se cambiaraacuten los valores de las columnas especificados por el objeto de laclase ContentValues tambieacuten se devolveraacute el nuacutemero de filas correctamenteactualizadas A continuacioacuten se muestra un ejemplo

ContentValues nuevosValores = new ContentValues()

Utilizamos el objeto ContentValues para especificar en queacute columnas queremos que se produzca un cambio y cuaacutel debe ser el nuevo valor de dichas columnasnuevosValuesput(NOMBRE_COLUMNA nuevoValor)

Aplicamos los cambios a las primeras cinco filasString where = _id lt 5

getContentResolver()update(MiProveedorCONTENT_URI nuevosValores wherenull)

Persistencia

33Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

4 Ejercicios - Persistencia en Android proveedores de contenidos ySharedPreferences

41 Compartir datos entre actividades con Shared Preferences (075 puntos)

Descarga de las plantillas el proyecto Dni Dicho proyecto estaacute compuesto de dosactividades Formulario y Resumen El objetivo del ejercicio es conseguir que al pulsar elbotoacuten Siguiente en la actividad Formulario se muestre en la actividad Resumen cuaacutelesfueron los datos introducidos

En este ejercicio vamos a hacer uso de SharedPreferences para pasar los datos de unaactividad a la siguiente Al pulsar en Siguiente en Formulario deberemos guardar el valorde todos los campos mediante esta plataforma Al mostrarse la actividad Resumendeberemos leer estos datos tambieacuten a partir de SharedPreferences para mostrarlos porpantalla

Por uacuteltimo crea un meacutetodo que valide el DNI (debe tratarse de una secuencia de ochodiacutegitos entre 0 y 9 y una letra al final) Si se pulsa Siguiente y el DNI no tiene el formatocorrecto no se deberaacute pasar a la actividad Resumen En lugar de esto se mostraraacute unToast en pantalla con un mensaje de error

42 Actividad de preferencias (075 puntos)

En este ejercicio seguimos trabajando con el proyecto del ejercicio anterior Vamos aantildeadir un menuacute de opciones que permita escoger en queacute momento se comprobaraacute lavalidez del DNI Para ello antildeade en primer lugar un menuacute a la actividad Formulario conuna uacutenica opcioacuten cuyo nombre seraacute Opciones

Al seleccionar esta opcioacuten se deberaacute mostrar una actividad de preferencias cuyo aspectoseraacute el siguiente

Persistencia

34Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Preferencias de la aplicacioacuten Dni

El significado de las opciones seraacute el siguiente

bull En el campo con esta opcioacuten seleccionada al pulsar sobre el campo de DNI en elformulario deberaacute mostrarse un Toast indicando si la sintaxis del DNI es correcta ono

bull Al pulsar si se desactiva esta opcioacuten (que deberaacute estar activada por defecto) no seharaacute la comprobacioacuten del DNI al pulsar el botoacuten Siguiente por lo que siempre seraacuteposible pasar de la actividad Formulario a la actividad Resumen sea cual sea elformato del DNI introducido

bull El uacuteltimo check box se encontraraacute siempre deshabilitado iquestQueacute atributo debemosutilizar para conseguir esto

Crea dos variables booleanas llamadas enElCampo y alPulsar Su valor dependeraacute delestado de los checkboxes anteriores y se actualizaraacute por medio del eventoonSharedPreferenceChange Utiliza el valor de estas variables en el coacutedigo de tuactividad para que esta tenga el comportamiento indicado

43 Proveedor de contenidos propio (075 puntos)

Vamos a implementar otra forma de acceder a la base de datos de usuarios de laaplicacioacuten BaseDatos desarrollada durante los ejercicios de la sesioacuten anterior siguiendoesta vez el patroacuten de disentildeo ContentProvider de Android Seguiremos trabajando puescon dicho proyecto

bull Creamos una nueva clase llamada UsuariosProvider que herede deContentProvider Esto nos obligaraacute a sobrecargar una serie de meacutetodos abstractosAntes de implementar la query vamos a configurar el provider

bull Antildeadimos algunos campos tiacutepicos de los content provider

Persistencia

35Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public static final Uri CONTENT_URI =Uriparse(contentesuajtechandroidbasedatosusuarios)

private static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

staticuriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

TODAS_FILAS)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

UNA_FILA)

bull Vamos a acceder a la misma base de datos de usuarios utilizada en los ejercicios de lasesioacuten anterior pero no vamos a hacerlo a traveacutes del adaptador que tuvimos queimplementar sino que vamos a copiar de eacutel el coacutedigo que nos haga falta Copia loscampos que definen el nombre de la base de datos de la tabla de las columnas laversioacuten asiacute como la referencia al contexto y a la base de datos La sentenciacompilada del insert ya no va a hacer falta Inicializa los valores necesarios en elconstructor Para inicializar la referencia a la base de datos vamos a utilizar una vezmaacutes MiOpenHelper Podemos copiarlo del adaptador que ya implementamos en losejercicios de la sesioacuten anterior

bull Implementa de forma apropiada el getType para devolver un tipo MIME diferenteseguacuten si se trata de una URI de una fila o de todas las filas Para ello ayuacutedate deluriMatcher

bull Implementa el meacutetodo query Simplemente se trata de devolver el cursor queobtenemos al hacer una consulta a la base de datos SQLite Algunos de los paraacutemetrosque le pasaremos los recibimos como paraacutemetros del meacutetodo del provider Los que notengamos iraacuten con valor null

bull Aunque no tenemos todos los meacutetodos del UsuariosProvider implementadospodemos probarlo Para ello debemos registarlo en el AndroidManifestxml

ltprovider androidname=UsuariosProvider

androidauthorities=esuajtechandroidbasedatosgtltapplicationgt

ltmanifestgt

bull En la clase Main se antildeadioacute codigo para insertar una serie de valores en la base dedatos y mostrarlos en el campo de texto Manteniendo este coacutedigo vamos a antildeadir alcampo de texto el resultado obtenido con la consulta del UsuariosProvider paracomprobar que tanto el adaptador de la base de datos como el proveedor nosdevuelven el mismo resultado

44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)

Porque es la forma estaacutendar que establece Android de acceder a contenidos Ademaacutes elproveedor de contenidos nos permitiraacute notificar al ContentResolver de los cambiosocurridos Asiacute componentes en la pantalla podraacuten refrescarse de forma automaacutetica

Persistencia

36Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Utiliza el proyecto ProveedorContenidos de las plantillas Implementa la insercioacuten enel proveedor de contenidos Prueacutebala insertando algunos usuarios de ejemplo en laclase Main Implementa tambieacuten el OnClickListener del botoacuten que inserta nuevosusuarios El nombre del nuevo usuario iraacute indicado en el EditText

bull Comprueba que la insercioacuten funciona y que gracias a la siguiente liacutenea y al estarusando un proveedor de contenidos la lista se actualiza automaacuteticamente cuandoocurre alguacuten cambio sin necesidad de pedir expliacutecitamente la actualizacioacuten al pulsarel botoacuten

cursorsetNotificationUri(cr UsuariosProviderCONTENT_URI)

NotaEsto no va a funcionar si se notifica que se ha producido un cambio al insertar o borrar unelemento Para ello usamosgetContext()getContentResolver()notifyChange(UsuariosProviderCONTENT_URI null)

bull Implementa el meacutetodo delete del proveedor de contenidos Prueacutebalo en la claseMain Termina de implementar el onCreateContextMenuListener que se ejecutaraacutecada vez que se haga una pulsacioacuten larga sobre alguna entrada de la lista Compruebaque funciona (eliminando el usuario correspondiente y no otro)

Persistencia

37Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

5 Persistencia de datos en iOS Ficheros y SQLite

51 Introduccioacuten

La persistencia de datos en iOS se realiza en una pequentildea memoria flash que tienenuestro dispositivo la cual es equivalente a un disco duro de tamantildeo limitado Los datosque ahiacute se almacenen se conservaraacuten aunque el dispositivo se apague Por motivos deseguridad el SO de iOS no permite que las aplicaciones accedan directamente a lamemoria interna pero a cambio existe una API completa de acceso a la porcioacuten dememoria que le corresponde a la app que desarrollemos

En una aplicacioacuten iOS encontraremos baacutesicamente 4 modos de almacenamiento deinformacioacuten de forma persistente

bull Ficheros de propiedades (plist)bull Bases de datos embebidas SQLitebull Datos de usuario (User Defaults)bull Core Data

En esta primera sesioacuten veremos coacutemo usar los ficheros de texto (Property Lists) y elalmacenamiento en base de datos de tipo SQLite

52 Ficheros plist (Property Lists)

El almacenamiento de datos en ficheros de propiedades es uno de los maacutes usados en eldesarrollo de las aplicaciones para iOS Se usa principalmente para guardar datos deconfiguracioacuten de la aplicacioacuten u otra informacioacuten poco compleja y acceder a ella deforma sencilla Si estamos desarrollando un juego podemos usarlos para la definicioacuten delos niveles almacenar las puntuaciones del jugador etc

521 Leyendo datos desde ficheros plist

Los ficheros plist (Property Lists) tienen un formato XML aunque XCode nosproporciona una interfaz que nos permite acceder a ellos de forma muy sencilla Paraentender el funcionamiento de este tipo de ficheros vamos a realizar un ejemplo en el queprimero crearemos un fichero plist lo completaremos con datos de ejemplo y despueacutes loleeremos para mostrarlo por pantalla

Un ejemplo de un fichero plist podriacutea ser el siguiente

Persistencia

38Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Fichero plist

Antes de nada creamos un proyecto nuevo en XCode de tipo Window-based

Application al que llamaremos sesion03-ejemplo1 Para crear un fichero plistdebemos hacer click derecho sobre el directorio Supporting Files de nuestro proyectoy seleccionamos iOS gt Resource gt Property List Lo guardaremos con el nombreconfigUsuario Seguidamente ya podemos acceder al fichero recieacuten creado haciendoclick sobre el en este momento XCode nos mostraraacute un pequentildeo editor totalmente vacioen el que iremos rellenando con datos Podemos verlo tambieacuten en formato XML sihacemos click derecho sobre el y seleccionamos Open As gt Preview

Para empezar a rellenar el fichero que acabamos de crear hacemos click derecho dentrode su contenido (ahora vacio) y seleccionamos Add row Entonces nos apareceraacute unafila vaciacutea Dentro del campo Key escribimos la clave un nombre descriptivo quedeberemos utilizar maacutes adelante para acceder a los datos En nuestro caso vamos aescribir config como clave Dentro de la columna Type seleccionamosDictionary Veremos que aparece una flecha en el lado izquierdo que indica quepueden haber subniveles dentro de nuestro diccionario

Ahora antildeadimos un nuevo item que cuelgue del diccionario que acabamos de crear paraello hacemos click sobre la flecha de la izquierda esta se giraraacute hacia abajo entonceshacemos click ahora sobre el siacutembolo + (maacutes) Nos apareceraacute una nueva liacutenea que cuelgadel diccionario

En esta nueva liacutenea dentro del campo key escribimos nombre el campo type lodejamos como de tipo String ya que va a ser una cadena de texto y en la columna deValue escribimos nuestro nombre por ejemplo Javi

Ahora vamos a antildeadir otro campo nuevo en nuestra configuracioacuten para ello hacemosclick sobre el siacutembolo + y nos apareceraacute una nueva liacutenea justo al mismo nivel que laanterior y colgando del diccionario Dentro del campo de Key escribimos ciudad yen Value escribimos Alicante El campo de Type lo dejamos como String

Persistencia

39Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora dentro de nuestro fichero plist vamos a indicar los dispositivos que dispone elusuario para ello creamos una nueva linea dentro del diccionario y le pondremos comoclave dispositivos Seleccionamos el tipo Array y seguimos los mismos pasosque al crear el diccionario para ello hacemos click sobre la flecha de la izquierda ydespueacutes sobre el siacutembolo + (maacutes) Ahora nos apareceraacute una nueva fila con clave Item 0Seleccionamos el tipo String y en el campo Value iPhone

Ahora repetimos el paso 3 veces indicando como values iPad Android y

Blackberry

Una vez seguidos todos estos pasos tendremos nuestro fichero de propiedades listo Debede quedar como se muestra a continuacioacuten

Fichero plist terminado

Este fichero se almacenaraacute dentro del directorio de bundle cuando compilemos laaplicacioacuten En el caso de que queramos escribir en el debermos almacenar el ficherodentro del directorio de Documents del binario

Ahora desde nuestro coacutedigo vamos a acceder a eacutel y a mostrarlo por pantalla para elloescribimos el siguiente coacutedigo dentro del meacutetodo didFinishLaunchingWithOptions dela clase delegada

Cargamos el fichero PLISTNSString mainBundlePath = [[NSBundle mainBundle] bundlePath]NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]NSDictionary diccionario = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionario delraiz

NSString nombre = [dicConfig objectForKeynombre]

Persistencia

40Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

[selfwindow makeKeyAndVisible]return YES

Si todo ha ido correctamente deberemos obtener la siguiente salida por la consola

Lista de dispositivos

522 Escribiendo datos en ficheros plist

Partiendo del ejemplo anterior vamos a escribir ahora en el fichero para ello primerodebemos comprobar que el fichero plist se encuentre dentro del directorio de Documents

del dispositivo en el caso de que no lo esteacute (por ejemplo si es la primera vez que arrancala aplicacioacuten) debemos copiarlo a ese directorio Una vez que tenemos el fichero dentrodel directorio de documentos ya podemos escribir en el

Para realizar todo el proceso de comprobacioacuten de la existencia del fichero dentro deldirectorio de documentos copiarlo en ese directorio y leer los datos debemos de sustituirel coacutedigo que hay dentro del meacutetodo didFinishLaunchingWithOptions de la clasedelegada por este otro

AtencioacutenPara que funcione la escritura en un fichero plist este debe de estar dentro del directorio deDocuments de nuestro dispositivo iOS no en el boundle

NSDictionary diccionarioRaiz

Ruta del directorio de documentos de nuestro dispositivoNSArray paths =

NSSearchPathForDirectoriesInDomains(NSDocumentDirectoryNSUserDomainMask YES)

if ([paths count] gt 0)

Persistencia

41Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

Primero comprobamos si existe el fichero en eldirectorio de documentos

BOOL fileExists = [[NSFileManager defaultManager]fileExistsAtPathdocumentsFilename]if (fileExists)

NSLog(Fichero encontrado en directorio de documentosOK

documentsFilename)

Si existe ya cargamos los datos desde aquidirectamente

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

else

NSLog(No se encuentra el fichero configUsuarioplisten

el directorio de documentos-gtLo creamos)

Si no existe primero cargamos el fichero PLISTdesde el Boundle

NSString mainBundlePath = [[NSBundle mainBundle]bundlePath]

NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Y despues escribimos los datos cargados (eldiccionario completo)

a un fichero nuevo de la carpeta de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

NSLog(plist de configUsuario creado)

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionariodel raiz

NSString nombre = [dicConfig objectForKeynombre]NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

Persistencia

42Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Por ultimo liberamos la memoria del diccionario raiz[diccionarioRaiz release]

[selfwindow makeKeyAndVisible]return YES

Para comprobar el funcionamiento arrancamos la aplicacioacuten y veremos que la primera vezse crearaacute el fichero dentro de la carpeta de Documents de nuestro dispositivo y en lassiguientes ya no se crearaacute y simplemente lo leeraacute desde la carpeta de Documents

Ahora que ya tenemos la gestioacuten de ficheros baacutesica completada vamos a escribir sobre elalguacuten dato Para ello simplemente escribimos el siguiente fragmento de coacutedigo justo antesde la liacutenea [selfwindow makeKeyAndVisible]

Cambiamos el nombre y lo guardamos en el fichero Ruta del directorio de documentos de nuestro dispositivo

if ([paths count] gt 0)

Cargamos el fichero desde el directorio dedocumentos del dispositivo

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cambiamos el valor del nombre en el diccionario[dicConfig setValueOtro nombre forKeynombre]

Escribimos todo el diccionario de nuevo al fichero del directorio de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

Por ultimo liberamos el diccionario de lamemoria

[diccionarioRaiz release]

Lo que hemos hecho en el coacutedigo anterior es cargar todo el diccionario de nuevo desde eldirectorio de documentos modificar los datos que queremos cambiar o incluso antildeadir ydespueacutes guardar el diccionario en el fichero

Volvemos a arrancar la aplicacioacuten y veremos que ha cambiado el campo de nombre

Nota

Persistencia

43Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

La lectura y escritura de datos en un fichero plist se debe de hacer de una soacutela vez nunca porpartes Siempre se debe de cargar o escribir un diccionario NSDictionary como elementoprincipal del fichero

53 SQLite

SQLite es una base de datos relacional ligera con la que se puede trabajar de formasencilla mediante sentencias SQL La base de datos se almacenaraacute en un fichero dentrode nuestra aplicacioacuten Este meacutetodo de persistencia puede ser el adecuado cuando los datosa almacenar esteacuten en un formato de tablas filas y columnas y se necesite que los datossean accesibles de forma raacutepida

531 Herramientas disponibles

Para explicar el funcionamiento de las librerias de SQLite de Cocoa Touch vamos a seguirun ejemplo completo Comenzamos creando la base de datos para ello podemos utilizaruno de los frontends que facilitan la gestioacuten del SQLite uno bastante sencillo yrecomendado es el SQLite Database Browser y otro es fmdb

Nosotros utilizaremos el primero (SQLite Database Browser) Abrimos la aplicacioacuten yhacemos click sobre el botoacuten de crear nueva base de datos que llamaremospersonasDBsqlite Seguidamente creamos las tablas para simplificar en nuestroejemplo crearemos una soacutela tabla llamada personas Esta tabla tendraacute las siguientescolumnas id (NUMERIC) nombre (TEXT) apellidos (TEXT) dni (TEXT) edad

(NUMERIC) y localidad (TEXT)

Persistencia

44Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Estructura de la Base de Datos

Una vez creada la tabla pasamos a insertar datos dentro de ella para ello abrimos lapestantildea de Browse Data y hacemos click en New Record Cuando tengamos al menos 3registros metidos dentro de la tabla ya podemos guardar la base de datos Hacemos clicksobre guardar le ponemos el nombre que queramos y elegimos cualquier directorio delMAC

Datos de la Base de Datos

532 Lectura de datos

Una vez que tenemos la base de datos SQLite creada ya podemos empezar a escribir elcoacutedigo para leer y mostrar los datos dentro de nuestra aplicacioacuten

Primero empezamos creando un proyecto nuevo en XCode usando la plantilla basada envistas View-based application y lo llamaremos sesion03-ejemplo2 Ahora antildeadimosel framework de sqlite a nuestro proyecto para ello hacemos click sobre el raiz delproyecto y seleccionamos la pestantildea Build Phases Desplegamos el listado de Link

Binary With Libraries y hacemos click sobre el botoacuten (+) El el listadoseleccionamos el framework libsqlite3dylib y hacemos click en Add

Persistencia

45Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Antildeadiendo el framework libsqlite3dylib

Ahora antildeadimos a nuestro proyecto la base de datos que hemos creado anteriormentepara ello arrastramos el fichero personasDBsqlite a nuestro directorio Supporting

Files Asegurate de seleccionar la opcioacuten de Copy items to destination groups

folder (if needed) para que se copie el fichero dentro de la carpeta del proyecto

Para acelerar el proceso de lectura de la base de datos y ahorrar memoria vamos a cargarprimero todos los datos en objetos para ello vamos a crear una clase que se encargue dealmacenar los datos Hacemos click derecho sobre el raiz del proyecto New file gtObjective-C class seleccionamos NSObject como clase principal y le damos a NextEscribimos como nombre de la clase Persona y hacemos click en save

Personah

imports iniciales

interface Persona NSObject int _uIdNSString _nombreNSString _apellidos

Persistencia

46Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSInteger _edadNSString _localidad

property (nonatomic assign) int uIdproperty (nonatomic copy) NSString nombreproperty (nonatomic copy) NSString apellidosproperty (nonatomic) NSInteger edadproperty (nonatomic copy) NSString localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidos edad(NSInteger)edadlocalidad(NSString )localidad

end

Personam

import Personah

implementation Persona

synthesize uId = _uIdsynthesize nombre = _nombresynthesize apellidos = _apellidossynthesize edad = _edadsynthesize localidad = _localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidosedad(NSInteger)edad localidad(NSString )localidad

if ((self = [super init])) selfuId = uIdselfnombre = nombreselfapellidos = apellidosselfedad = edadselflocalidad = localidad

return self

- (void) dealloc selfnombre = nilselfapellidos = nilselflocalidad = nil[super dealloc]

end

Una vez creada la clase Persona vamos a crear la clase encargada de manejar la basede datos sqlite3 Otra opcioacuten seriacutea cargar todos los datos directamente desde la clasedelegada al arrancar la aplicacioacuten pero no es recomendable ya que si en alguacuten momentotenemos que cambiar de motor de base de datos lo tendremos maacutes complicado

Persistencia

47Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creamos entonces una clase dentro de nuestro proyecto llamada BDPersistencia queherede de NSObject y escribimos el siguiente coacutedigo

BDPersistenciah

imports iniciales

interface BDPersistencia NSObject sqlite3 _database

+ (BDPersistencia)database- (NSArray )arrayPersonas

end

BDPersistenciam

import BDPersistenciahimport Personah

implementation BDPersistencia

static BDPersistencia _database

+ (BDPersistencia)database if (_database == nil)

_database = [[BDPersistencia alloc] init]return _database

- (id)init if ((self = [super init]))

NSString sqLiteDb = [[NSBundle mainBundle]pathForResourcepersonasDB

ofTypesqlite]

if (sqlite3_open([sqLiteDb UTF8String]amp_database) = SQLITE_OK)

NSLog(Fallo al abrir la BD)

return self

- (void)dealloc sqlite3_close(_database)[super dealloc]

- (NSArray )arrayPersonas

NSMutableArray arraySalida = [[[NSMutableArray alloc]init]

autorelease]

Persistencia

48Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString query = SELECT id nombre apellidoslocalidad edad

FROM personas ORDER BY apellidos DESC

sqlite3_stmt statementif (sqlite3_prepare_v2(_database [query UTF8String]

-1ampstatement nil)

== SQLITE_OK) while (sqlite3_step(statement) == SQLITE_ROW)

int uniqueId = sqlite3_column_int(statement0)

char nombreChar = (char )sqlite3_column_text(statement 1)

char apellidosChar = (char )sqlite3_column_text(statement 2)

char localidadChar = (char )sqlite3_column_text(statement 3)

int edad = sqlite3_column_int(statement 4)

NSString nombre = [[NSString alloc]initWithUTF8StringnombreChar]NSString apellidos = [[NSString alloc]initWithUTF8StringapellidosChar]NSString localidad = [[NSString alloc]initWithUTF8StringlocalidadChar]Persona persona = [[Persona alloc]initWithUiduniqueId nombrenombreapellidosapellidos edadedad

localidadlocalidad]

[arraySalida addObjectpersona][nombre release][apellidos release][localidad release][persona release]

sqlite3_finalize(statement)

return arraySalida

end

Con esto ya podemos leer de nuestra base de datos las personas Para ver que funcionatodo a la perfeccioacuten escribimos el siguiente coacutedigo dentro de la clase delegada en elmeacutetodo applicationDidFinishLaunching Antes debemos incluir en la parte superiorlas clases creadas

import BDPersistenciahimport Personah

NSArray personas = [BDPersistenciadatabase]arrayPersonas

for (Persona persona in personas) NSLog(d d personauId

personanombrepersonaapellidos personalocalidad personaedad)

Persistencia

49Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Si ejecutamos el proyecto obtendremos la siguiente salida por consola

Listado de personas

533 Mostrando los datos en una tabla

Una vez que tenemos el coacutedigo para obtener todas las filas de la tabla de personas de labase de datos queda lo maacutes faacutecil mostrarlas dentro de una vista de la aplicacioacuten En estecaso utilizaremos la vista de tabla UITableView

Comenzamos creando una nueva vista en nuestro proyecto para ello hacemos clickderecho sobre el raiz y seleccionamos New File gt UIVIewController SubclassAsegurate de marcar Subclass of UITableViewController y la opcoacuten de With

XIB for user interface Hacemos click en Next y guardamos el fichero comoPersonasViewController por ejemplo

Ahora abrimos el fichero PersonasViewControllerh y antildeadimos un NSArray queseraacute el que almacene las personas de la base de datos

imports iniciales

interface PersonasViewController UITableViewController

NSArray _listadoPersonas

property (nonatomic retain) NSArray listadoPersonas

end

Ahora abrimos el fichero PersonasViewControllerm antildeadimos el syntetizeimportamos los ficheros de gestioacuten de la base de datos la clase Persona y todo el coacutedigobaacutesico para la gestioacuten de la vista de la tabla

import PersonasViewControllerhimport Personahimport BDPersistenciah

implementation PersonasViewController

synthesize listadoPersonas = _listadoPersonas

Persistencia

50Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

- (id)initWithStyle(UITableViewStyle)style

self = [super initWithStylestyle]if (self)

Custom initializationreturn self

- (void)dealloc

[super dealloc]

selflistadoPersonas = nil

- (void)didReceiveMemoryWarning

Releases the view if it doesnt have a superview[super didReceiveMemoryWarning]

Release any cached data images etc that arent inuse

pragma mark - View lifecycle

- (void)viewDidLoad

[super viewDidLoad]

selftitle = Listado de personas

selflistadoPersonas = [BDPersistenciadatabase]arrayPersonas

- (void)viewDidUnload

[super viewDidUnload] Release any retained subviews of the main view eg selfmyOutlet = nil

- (void)viewWillAppear(BOOL)animated

[super viewWillAppearanimated]

- (void)viewDidAppear(BOOL)animated

[super viewDidAppearanimated]

- (void)viewWillDisappear(BOOL)animated

[super viewWillDisappearanimated]

- (void)viewDidDisappear(BOOL)animated

[super viewDidDisappearanimated]

- (BOOL)shouldAutorotateToInterfaceOrientation

Persistencia

51Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

(UIInterfaceOrientation)interfaceOrientation

Return YES for supported orientationsreturn (interfaceOrientation ==

UIInterfaceOrientationPortrait)

pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView(UITableView)tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection

(NSInteger)section

Return the number of rows in the sectionreturn [selflistadoPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath

(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]if (cell == nil)

cell = [[[UITableViewCell alloc]initWithStyleUITableViewCellStyleSubtitle

reuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona persona = (Persona )[selflistadoPersonas

objectAtIndexindexPathrow]celltextLabeltext = [NSString stringWithFormat

(d) personanombrepersonaapellidos personaedad]celldetailTextLabeltext = personalocalidad

return cell

pragma mark - Table view delegate

- (void)tableView(UITableView )tableViewdidSelectRowAtIndexPath

(NSIndexPath )indexPath

Navigation logic may go here Create and pushanother view controller

end

Persistencia

52Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora soacutelo nos queda incorporar la vista que acabamos de crear dentro de nuestraaplicacioacuten Para ello vamos a llamarla desde el Delegate y a meterla dentro de unNavigation Controller Primero debemos de antildeadir un Outlet en la clasedelegada hacia un Navigation Controller

imports iniciales

interface sesion03_ejemplo1AppDelegate NSObjectltUIApplicationDelegategt

UIWindow _windowUINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet

UINavigationController navController

end

Ahora hacemos click sobre la vista MainWindowxib y arrastramos un Navigation

Controller desde el listado de la columna de la derecha hacia la columna de laizquierda donde pone Objects Nos apareceraacute el Navigation Controller dibujadoen pantalla Ahora desplegamos el Navigation Controller y hacemos click sobre elView Controller que hay dentro Nos vamos a la pestantildea de Identity Inspector

de la columna de la derecha y escribimos dentro de Class el nombre de la vista dellistado de personas PersonasViewController

Finalmente hacemos click sobre el objeto Delegate vamos a la pestantildea de Show

Connections Inspector y arrastramos desde navController hasta el objetoNavigation Controller de la columna de la izquierda Ya tenemos los controladoresy las vistas conectadas

Una vez hecho esto nos queda indicar dentro de nuestro coacutedigo que la aplicacioacuten arranquecon el Navigation Controller para ello escribimos lo siguiente al final del meacutetododidFinishLaunchingWithOptions de la implementacioacuten de la clase delegada

selfwindowrootViewController = selfnavController[selfwindow makeKeyAndVisible]return YES

Ya estaacute listo todo y preparado para compilar Si ejecutamos el proyecto veremos que nosaparece el listado de personas en la vista de tabla

Persistencia

53Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios

61 Creando y leyendo un fichero plist (15 puntos)

En este primer ejercicio vamos a crear nuestra primera aplicacioacuten usando el meacutetodo depersistencia de ficheros La aplicacioacuten mostraraacute en una vista de tabla TableView unlistado de series en la que si seleccionamos una veremos su informacioacuten maacutes importanteen otra vista Comenzaremos creando un fichero de propiedades (plist) siguiendo unaestructura determinada y posteriormente mostraremos en una vista de tabla todos susdatos iexclComenzamos

1) Crear un proyecto usando la plantilla Master-Detail application Lo llamaremosejercicio-plist-ios como identificador de empresa escribiremos esuajtech y como prefijode clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremos UseAutomatic Reference Counting para olvidarnos de la gestioacuten de memoria El resto deopciones las dejaremos sin marcar

2) Crear un archivo de propiedades (plist) con el nombre de series

3) Editar el archivo plist creando la siguiente estructura de datos (5 series miacutenimo)

Plist series

4) Rellenar el archivo plist con datos de series reales

5) Escribir el coacutedigo necesario en UAMasterViewControllerm que recorra el archivoplist que acabamos de crear y muestre por consola los titulos de las series

6) Mostrar en la tabla que hay implementada los tiacutetulos de la serie Para ello deberemosde completar todos los meacutetodos del protocolo del TableView

7) Ejecutar la aplicacioacuten y comprobar que funciona correctamente

Persistencia

54Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

62 Implementar vista de detalle de la serie (05 puntos)

Dentro del proyecto anterior (no hace falta crear uno nuevo) vamos a mostrar toda lainformacioacuten adicional de cada una de las series en una vista de detalle Cuandoseleccionemos una fila de la tabla nos deberaacute aparecer una nueva vista con el resto deinformacioacuten (antildeo director principal sinopsis y duracioacuten)

63 Uso baacutesico de una base de datos SQLite (1 punto)

En este ejercicio vamos a crear la misma aplicacioacuten que en el ejercicio anterior perousando el meacutetodo de persistencia de SQLite Para completar el ejercicio deberemos seguirlos siguientes pasos

1) Crear un proyecto usando la plantilla Master-Detail application lo llamaremosejercicio-sqlite-ios como identificador de empresa escribiremos esuajtech y comoprefijo de clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremosUse Automatic Reference Counting para olvidarnos de la gestioacuten de memoria El restode opciones las dejaremos sin marcar

2) Nos descargamos e instalamos el programa SQLite Database Browser desde estadireccioacuten

3) Disentildear la estructura de la base de datos usando SQLite Database Browser tal y comose muestra en la imagen siguiente

Sqlite series

4) Insertar al menos 5 series en la base de datos usando el programa SQLite DatabaseBrowser

5) Cargar todos los datos de la base de datos desde el meacutetododidFinishLaunchingWithOptions de la clase UAAppDelegate Mostrar por consola losdatos cargados

6) Mostrar los datos cargados en el paso anterior en la vista de tabla UITableView quehay creada en la clase UAMasterViewController

Persistencia

55Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7) Arrancar la aplicacioacuten y comprobar que se muestran las series en la tabla de formacorrecta

Persistencia

56Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7 Persistencia de datos en iOS User Defaults y Core Data

71 Introduccioacuten

En esta sesioacuten veremos como utilizar dos nuevos meacutetodos de persistencia en iOS ambosmuy interesantes y usados muy frecuentemente

bull User Defaults Muy uacutetil para almacenar datos simples y de uso repetido dentro denuestras aplicaciones iOS

bull Core Data Meacutetodo avanzado de almacenamiento de objetos enteros dentro de lamemoria del dispositivo iOS Muy uacutetil para almacenar datos complejos conrelaciones etc

72 User Defaults

Este meacutetodo es muy uacutetil cuando queremos almacenar pequentildeas cantidades de datos comopuntuaciones informacioacuten de usuario el estado de nuestra aplicacioacuten configuracionesetc

Este meacutetodo es el maacutes raacutepido de usar ya que no requiere de una base de datos ni ficherosni ninguna otra capa intermedia Los datos se almacenan directamente en la memoria deldispositivo dentro de un objeto de la clase NSUserDefaults

Para aprender a usar este meacutetodo de almacenamiento de datos vamos a crear unaaplicacioacuten muy simple que lea un nombre y una edad de pantalla y las guarde en unobjeto NSUserDefaults Para ello seguimos los siguientes pasos

bull 1) Comenzamos creando un nuevo proyecto llamado sesion04-ejemplo1 dentrode XCode

bull 2) Abrimos la vista sesion04_ejemplo1ViewController y arrastramos dos TextField un botoacuten y dos labels quedando la vista de la siguiente manera

Persistencia

57Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista de la aplicacioacuten

bull 3) Abrimos el fichero sesion04_ejemplo1ViewControllerh y definimos el Outletdel evento onclick del botoacuten El fichero quedaraacute de la siguiente manera

imports iniciales

interface sesion04_ejemplo1ViewController UIViewController UITextField tfNombreUITextField tfEdad

property(nonatomic retain) IBOutlet UITextField tfNombreproperty(nonatomic retain) IBOutlet UITextField tfEdad

-(IBAction) guardaDatos

end

bull 4) Ahora implementamos el coacutedigo para el meacutetodo del botoacuten dentro desesion04_ejemplo1ViewControllerm

synthesize tfEdadsynthesize tfNombre

-(IBAction) guardaDatos NSLog(Guardamos los datos)

Persistencia

58Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creamos el objeto NSUserDefaultsNSUserDefaults datos = [NSUserDefaults standardUserDefaults]

guardamos el nombre (NSString)[datos setObjecttfNombretext forKeynombre]

guardamos la edad (Integer)[datos setInteger[tfEdadtext integerValue] forKeyedad]

almacenamos los datos en la memoria[datos synchronize]

bull 5) Tenemos que enlazar el botoacuten con el evento que hemos programado en el pasoanterior para ello abrimos la vista sesion04_ejemplo1ViewControllerxib y conla tecla ctlr apretada arrastramos desde el botoacuten hasta Files Owner (columna de laizquierda parte superior) y seleccionamos el meacutetodo que hemos creado(guardaDatos) De esta forma ya tenemos conectado el botoacuten

bull 6) Ahora hacemos lo mismo con los Text Field Tenemos que conectarlos alcontrolador para ello seleccionamos el objeto Files Owner y abrimos la pestantildeade conexiones (la que estaacute maacutes a la derecha en la columna de la derecha) Hacemosclick en tfEdad y arrastramos hasta el Text Field de edad Hacemos lo mismo para eltext field de nombre Ya tenemos todos los elementos de la vista conectados

Conexioacuten de Outlets

bull 7) Arrancamos la aplicacioacuten y la probamos

Persistencia

59Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Aplicacioacuten

bull 8) Ahora vamos a hacer que la aplicacioacuten cargue los datos que se han guardado alarrancar la vista para ello simplemente tenemos que escribir el siguiente coacutedigo en elmeacutetodo (void)viewDidLoad de la clase sesion04_ejemplo1ViewControllerm

- (void)viewDidLoad

[super viewDidLoad]

NSUserDefaults datos = [NSUserDefaults standardUserDefaults]

obtenemos un NSStringNSString nombre = [datos stringForKeynombre]

obtenemos un NSIntegerNSInteger edad = [datos integerForKeyedad]

if (nombre=nil ampamp edad=0)NSLog(Nombre cargado edad dnombreedad)

else

NSLog(Sin datos)

bull 9) Ya podemos arrancar de nuevo la aplicacioacuten y veremos que si escribimos cualquier

Persistencia

60Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

texto en los Text Fields estos se almacenaraacuten dentro del objeto NSUserDefaults Alapagar la aplicacioacuten y volver a arrancarla estos datos se cargaraacuten (apareceraacuten enconsola)

Consola

NotaLos datos permaneceraacuten en nuestro dispositivo hasta que se elimine la aplicacioacuten en ese casotodos los datos guardados en NSUserDefaults se borraraacuten

bull 10) Probar eliminar la aplicacioacuten del dispositivosimulador y volver arrancarComprobamos que los datos se han borrado

Consola

73 Core Data

De todas las formas de persistencia de datos que existen en iOS Core Data es la maacutesapropiada para usar en caso de manejar datos no triviales con relaciones algo maacutescomplejas entre ellos El uso de Core Data dentro de nuestra aplicacioacuten optimiza almaacuteximo el consumo de memoria mejora el tiempo de respuesta y reduce la cantidad decoacutedigo redundante a escribir

Core Data utiliza de fondo el meacutetodo de almacenamiento SQLite visto en la sesioacuten 3 deeste moacutedulo por tanto para explicar el funcionamiento de este meacutetodo de persistenciavamos a utilizar el mismo modelo de datos que usamos en esa sesioacuten Persona -gtDetallePersona

El el siguiente ejemplo haremos la misma aplicacioacuten que hicimos con SQLite perousando este nuevo meacutetodo de persistencia

731 Creando el proyecto

Para empezar tenemos que crearnos un nuevo proyecto en XCode Para ello abrimosXCode y seleccionamos crear una nueva aplicacioacuten basada en ventanas (Window-basedapplication) Hacemos click en Next escribimos como nombre del proyectosesion04-ejemplo2 y seleccionamos Use Core Data

Persistencia

61Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creando un proyecto nuevo

Con esto ya tenemos la estructura baacutesica para empezar a programar Antes de nadahechamos un vistazo raacutepido a toda la estructura de directorios creada y vemos que dentrodel directorio principal tenemos un archivo que se llamasesion04_ejemplo2xcdatamodeld Al hacer click sobre el archivo se nos abre uneditor especial que en principio estaacute vacio y que maacutes adelante utilizaremos para disentildearnuestro modelo de datos

Ahora hechamos un vistazo a la clase delegada sesion04_ejemplo2AppDelegatemvemos que hay unos cuantos meacutetodos nuevos que serviraacuten para configurar el Core DataVamos a explicar los maacutes importantes

bull - (NSManagedObjectModel )managedObjectModel NSManagedObjectModel esla clase que contiene la definicioacuten de cada uno de los objetos o entidades quealmacenamos en la base de datos Normalmente este meacutetodo no lo utilizaremos ya quenosotros vamos a utilizar el editor que hemos visto antes con el que podremos crearnuevas entidades crear sus atributos y relaciones

bull - (NSPersistentStoreCoordinator )persistentStoreCoordinator Aquiacute esdonde configuramos los nombres y las ubicaciones de las bases de datos que se usaraacutenpara almacenar los objetos Cuando un managed object necesite guardar algo pasaraacutepor este meacutetodo

bull - (NSManagedObjectContext )managedObjectContext Esta claseNSManagedObjectContext seraacute la maacutes usada con diferencia de las tres y por tanto la

Persistencia

62Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

maacutes importante La utilizaremos baacutesicamente para obtener objetos insertarlos oborrarlos

732 Definiendo el modelo de datos

En el ejemplo que hicimos en la sesioacuten anterior de SQLite creamos dos tablas una paralos datos baacutesicos de la persona y otra para el detalle Ahora haremos lo mismo perousando Core Data la diferencia principal es que mientras que con SQLite obteniamos soacutelolos datos que necesitaacutebamos de la base de datos aquiacute obtenemos el objeto completo

Para definir nuestro modelo de datos basado en dos objetos (Persona y DetallePersona)abrimos el editor visual haciendo click sobre el ficherosesion04_ejemplo2xcdatamodeld Empezaremos creando una nueva entidad para ellohacemos click sobre el botoacuten Add Entity (+) que se encuentra en la parte inferior dela pantalla A la nueva entidad le pondremos como nombre Persona

Dentro de la seccioacuten de Attributes hacemos click sobre (+) y creamos un nuevoatributo para nuestra entidad le llamamos nombre y le asignamos como tipo StringRealizamos este mismo paso 2 veces maacutes para antildeadir apellidos y edad este uacutetlimode tipo Integer 16

Atributos Persona

Ahora creamos la entidad de DetallePersona de la misma manera que la anterior perocreando los atributos localidad pais direccion cp y telefono Todasde tipo String

Atributos DetallePersona

Finalmente nos queda relacionar de alguacuten modo las dos entidades para ello antildeadimos una

Persistencia

63Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

relacioacuten dentro de la entidad de Persona hacia DetallePersona simplementehaciendo click sobre el botoacuten (+) de la seccioacuten de Relationships A la relacioacuten leponemos como nombre detalle seleccionamos como destino la entidadDetallePersona y seraacute No inverse Este tipo de relacioacuten es uno a uno Por dentroCore Data realizaraacute crearaacute una clave ajena en la tabla de persona que apunte aDetallePersona pero eso a nosotros no nos interesa

Vista general modelo de datos (1)

Apple recomienda que siempre que hagamos una relacioacuten de una entidad a otra hagamostambieacuten la relacioacuten inversa por lo tanto hacemos justo lo anterior pero al reveacutes (deDetallePersona a Persona) con la diferencia que en la columna de Inverse

seleccionamos datosPersona

Ya tenemos las entidades y las relaciones creadas Si hacemos click en el botoacuten deEditor Style en la parte de abajo a la derecha del editor podemos ver de una formamaacutes clara la estructura que hemos disentildeado

Persistencia

64Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista general modelo de datos (2)

733 Probando el modelo

Una vez que tenemos el modelo creado pasamos a probarlo para comprobar que estaacute todocorrecto para ello abrimos la clase delegada (sesion04_ejemplo2AppDelegatem) yantildeadimos el siguiente fragmento de coacutedigo arriba del meacutetodoapplicationDidFinishLaunching

NSManagedObjectContext context = [self managedObjectContext]NSManagedObject persona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

[persona setValueJuan forKeynombre][persona setValueMartinez forKeyapellidos][persona setValue[NSNumber numberWithInt28] forKeyedad]

NSManagedObject detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersonainManagedObjectContextcontext]

[detallePersona setValueCalle Falsa 123 forKeydireccion][detallePersona setValueAlicante forKeylocalidad][detallePersona setValueEspantildea forKeypais][detallePersona setValue965656565 forKeytelefono]

[detallePersona setValuepersona forKeydatosPersona][persona setValuedetallePersona forKeydetalle]

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Persistencia

65Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Con este coacutedigo hemos creado un objeto Persona enlazado con otro objetoDetallePersona iexclComo puedes ver no hemos necesitado nada de coacutedigo SQLPrimero creamos un objeto del tipo NSManagedObjectContext que almacena el conextode Core Data Despueacutes creamos el objeto Persona en siacute con NSManagedObject A esteobjeto le pondremos los valores para cada una de las propiedades definidas en el modeloHacemos lo mismo con DetallePersona Finalmente relacionamos el objetoPersona con el objeto DetallePersona

Compilamos el proyecto comprobamos que todo funciona bien y no salen errores

Para comprobar que los datos se han almacenado correctamente en nuestro dispositivo losmostramos en la consola de la siguiente manera

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

intValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(------------------)

[fetchRequest release]

El coacutedigo de arriba lo escribiremos dentro del meacutetododidFinishLaunchingWithOptions de la clase delegada Lo que hace este coacutedigo esrecorrer todos los objetos almacenados en memoria y mostrarlos por consola Siarrancamos la aplicacioacuten veremos que se muestran todos los datos de los objetosalmacenados Cada vez que arranquemos la aplicacioacuten se mostraraacuten maacutes objetos ya quetambieacuten los creamos

Para listar los objetos usamos la clase NSFetchRequest Esta clase es equivalente aejecutar una sentencia Select de SQL Al objeto NSFetchRequest le asignamos laentidad que queremos listar (SELECT FROM ) en nuestro caso seraacute PersonaEl listado de los objetos se obtiene mediante el meacutetodo executeFetchRequest de laclase NSManagedObjectContext que hemos definido en el bloque anterior de coacutedigoEste meacutetodo devolveraacute un NSArray con todos los objetos NSManagedObject

Persistencia

66Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Salida de consola

NotaSi queremos ver queacute sentencias SQL estaacute ejecutando Core Data por debajo tenemos que activarel flag de debug para ello seleccionamos la opcioacuten Product gt Edit Scheme Enla columna de la izquierda seleccionamos el esquema que estamos utilizando para Debug y en laparte de la derecha en el cuadro de Arguments Passed on Launch antildeadimos 2paraacutemetros -comappleCoreDataSQLDebug y 1

Ahora arrancamos de nuevo la aplicacioacuten y veremos que en la consola apareceraacuten lassentencias SQL utilizadas por Core Data

()

INSERT INTO ZDETALLEPERSONA(Z_PK Z_ENT Z_OPT ZDATOSPERSONA ZTELEFONOZLOCALIDAD ZDIRECCION ZPAIS)VALUES( )CoreData sql SELECT 0 t0Z_PK t0Z_OPT t0ZTELEFONO t0ZLOCALIDADt0ZDIRECCION t0ZPAIS t0ZDATOSPERSONAFROM ZDETALLEPERSONA t0 WHERE t0Z_PK =

()

734 Generando los modelos automaacuteticamente

Generar los modelos a mano directamente desde el editor no es lo maacutes recomendado yaque podemos cometer errores de escritura errores de tipo etc La mejor manera dehacerlo es creando un archivo para cada entidad Esto se puede hacer desde 0 peroXCode tiene un generador de clases que es muy faacutecil de usar y ahorremos bastantetiempo iexclVamos a usarlo

Hacemos click sobre el raiz de nuestro proyecto y seleccionamos New File En lacolumna de la izquierda seleccionamos iOS gt Core Data y en el cuadro de laderecha NSManagedObject subclass Click en next En la siguiente pantallaseleccionamos nuestro modelo de datos sesion04_ejemplo2 y click en next Ahoranos aseguramos de seleccionar las dos entidades que hemos creado anteriormentePersona y DetallePersona click en next Seleccionamos el raiz de nuestroproyecto para guardar los ficheros que se generen y aceptamos

Persistencia

67Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ventana de seleccioacuten de entidad

Ahora veremos que xsource ha creado automaacuteticamente 4 ficheros nuevos dentro denuestro proyecto Personamh y DetallePersonamh con todos los atributosespecificados Estas nuevas clases que heredan de NSManagedObject seraacuten las queutilicemos a partir de ahora

Cambiamos el coacutedigo de la clase delegada por el siguiente

NSManagedObjectContext context = [self managedObjectContext]Persona datosPersona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

datosPersonanombre = JuandatosPersonaapellidos = MartinezdatosPersonaedad = [NSNumber numberWithInt28]

DetallePersona detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersona

inManagedObjectContextcontext]detallePersonadireccion = Calle Falsa 123

Persistencia

68Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

detallePersonalocalidad = AlicantedetallePersonapais = EspantildeadetallePersonatelefono = 965656565

detallePersonadatosPersona = datosPersonadatosPersonadetalle = detallePersona

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Listamos los datosNSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

integerValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(-----------------)[fetchRequest release]

Ejecutamos el proyecto y comprobamos que en la consola aparece lo mismo que antespero ahora tenemos el coacutedigo bastante maacutes claro

735 Creando la vista de tabla

Ahora vamos a mostrar los objetos que antes hemos listado por consola en una vista detabla UITableView Para ello creamos un UITableViewController

Click en New file gt UIViewController subclass En la siguiente pantallanos aseguramos de seleccionar Subclass of UITableViewController de esta formanos apareceraacuten ya todos los meacutetodos de la tabla creados por defecto Guardamos elarchivo con el nombre que queramos por ejemplo PersonasViewController

Abrimos ahora PersonasViewControllerh y antildeadimos 2 atributos a la clase

bull Un NSArray que se seraacute el listado de personas (objetos de la clase Persona)bull Una referencia a NSManagedObjectContext

El fichero PersonasViewControllerh quedaraacute de la siguiente manera

Persistencia

69Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

imports iniciales

interface PersonasViewController UITableViewController NSArray _listaPersonasNSManagedObjectContext _context

property (nonatomic retain) NSArray listaPersonasproperty (nonatomic retain) NSManagedObjectContext context

end

Ahora en el m importamos en el encabezado las clases Personah yDetallePersonah y en el meacutetodo viewDidLoad creamos el array listaPersonas contodos los objetos Persona de forma similar a cuando lo hicimos en el apartado anterior

import PersonasViewControllerh

import Personahimport DetallePersonah

implementation PersonasViewController

synthesize listaPersonas = _listaPersonassynthesize context = _context

En viewDidLoad

- (void)viewDidLoad

[super viewDidLoad]

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContext_context]

[fetchRequest setEntityentity]NSError errorselflistaPersonas = [_context executeFetchRequestfetchRequest

erroramperror]selftitle = Listado Personas[fetchRequest release]

Ahora completamos el resto de meacutetodos heredados de UITableViewController de lamisma forma que hicimos en sesiones anteriores

- (NSInteger)numberOfSectionsInTableView(UITableView )tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection(NSInteger)section

Persistencia

70Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Return the number of rows in the sectionreturn [_listaPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]

if (cell == nil) cell = [[[UITableViewCell alloc]

initWithStyleUITableViewCellStyleSubtitlereuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona p = (Persona )[_listaPersonas objectAtIndexindexPathrow]celltextLabeltext = pnombrecelldetailTextLabeltext = [NSString stringWithFormatd antildeos

[pedad intValue]]

return cell

Ahora para poder mostrar la tabla dentro de nuestra aplicacioacuten debemos asignarla dentrode la clase delegada para ello abrimos la vista MainWindowxib y arrastramos unacontroladora de navegacioacuten Navigation Controller a la lista de objetos de laizquierda La desplegamos y dentro de View Controller en la pestantildea de la derechade Identity Inspector escribimos el nombre de nuestra clase que tiene la vista de latabla PersonasViewController

Pantalla del disentildeador

Ahora abrimos el fichero sesion04_ejemplo2AppDelegateh y creamos un

Persistencia

71Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

UINavigationController dentro de la declaracioacuten de variables dejando el ficherocomo sigue

imports iniciales

interface sesion04_ejemplo2AppDelegate NSObject ltUIApplicationDelegategt

UINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet UINavigationControllernavController

property (nonatomic retain readonly) NSManagedObjectContextmanagedObjectContextproperty (nonatomic retain readonly) NSManagedObjectModelmanagedObjectModelproperty (nonatomic retain readonly) NSPersistentStoreCoordinatorpersistentStoreCoordinator

- (void)saveContext- (NSURL )applicationDocumentsDirectory

end

Dentro del m importamos la clase PersonasViewControllerh y completamos elsynthetize

synthesize navController = _navController

Y por uacuteltimo justo antes de la llamada al meacutetodo makeKeyAndVisible escribimos elsiguiente coacutedigo para asignar el contexto de la clase delegada alPersonasViewController

PersonasViewController root = (PersonasViewController ) [_navControllertopViewController]

rootcontext = [self managedObjectContext][selfwindow addSubview_navControllerview]

Ahora compilamos y ejecutamos nuestro proyecto y si todo ha ido bien deberiacutea de salirla tabla con los datos de las personas

Persistencia

72Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Listado de personas

736 Migraciones de bases de datos con Core Data

Una vez que tenemos disentildeada nuestra base de datos inicial iquestqueacute pasariacutea si queremosmodificarla para antildeadirle un campo maacutes a una tabla iquestQueacute haraacute Core Data para realizar lamigracioacuten de la versioacuten anterior a la actual

Siguiendo con el ejemplo anterior si despueacutes de ejecutar la aplicacioacuten en nuestrodispositivo o simulador modificamos la estructura de la base de datos antildeadiendo unatributo nuevo en la entidad DetallePersona por ejemplo codigoPostal con el tipoInteger 16 veremos que al volver a arrancar la aplicacioacuten nos apareceraacute un error entiempo de ejecucioacuten similar al siguiente

Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 Theoperationcouldnrsquot be completed(Cocoa error 134100) UserInfo=0x6b6c680 metadata=ltCFBasicHash 0x6b68380[0x1337b38]gttype = immutable dict count = 7entries =gt

2 ltCFString 0x6b70320 [0x1337b38]gtcontents =NSStoreModelVersionIdentifiers

=ltCFArray 0x6b706e0 [0x1337b38]gttype = immutable count = 1

values = (0 ltCFString 0x1332cd8 [0x1337b38]gtcontents =

)4 ltCFString 0x6b70350 [0x1337b38]gtcontents =

NSPersistenceFrameworkVersion=

Persistencia

73Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltCFNumber 0x6b6f2c0 [0x1337b38]gtvalue = +386 type =kCFNumberSInt64Type

6 ltCFString 0x6b70380 [0x1337b38]gtcontents =NSStoreModelVersionHashes =

ltCFBasicHash 0x6b707e0 [0x1337b38]gttype = immutable dict count =2entries =gt

1 ltCFString 0x6b70700 [0x1337b38]gtcontents = Persona =ltCFData 0x6b70740

[0x1337b38]gtlength = 32 capacity = 32 bytes =0x1c50e5a379ff0ddc3cda7b82a3934c5e 75649ac3c919beea2 ltCFString 0x6b70720 [0x1337b38]gtcontents = DetallePersona

=ltCFData 0x6b70790[0x1337b38]gtlength = 32 capacity = 32 bytes =0xb7ba2eb498f9a1acdd6630d56f6cd12c e8963dd3e488ba95

7 ltCFString 0x10e3aa8 [0x1337b38]gtcontents = NSStoreUUID =ltCFString 0x6b70510[0x1337b38]gtcontents = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E8 ltCFString 0x10e3948 [0x1337b38]gtcontents = NSStoreType =ltCFString 0x10e3958[0x1337b38]gtcontents = SQLite9 ltCFString 0x6b703b0 [0x1337b38]gtcontents =

_NSAutoVacuumLevel = ltCFString0x6b70830 [0x1337b38]gtcontents = 210 ltFString 0x6b703d0 [0x1337b38]gtcontents =

NSStoreModelVersionHashesVersion=ltCFNumber 0x6b61950 [0x1337b38]gtvalue = +3 type =

kCFNumberSInt32Type reason=The model used to open the store is incompatible with the oneused tocreate the store

metadata = NSPersistenceFrameworkVersion = 386NSStoreModelVersionHashes =

DetallePersona = ltb7ba2eb4 98f9a1ac dd6630d5 6f6cd12c f890007746b16f00

e8963dd3 e488ba95gtPersona = lt1c50e5a3 79ff0ddc 3cda7b82 a3934c5e 5cb4ee4b

4ac98838 75649ac3c919beeagt

NSStoreModelVersionHashesVersion = 3NSStoreModelVersionIdentifiers = (

)NSStoreType = SQLiteNSStoreUUID = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E_NSAutoVacuumLevel = 2

reason = The model used to open the store is incompatible with the

one usedto create the store

Este error es muy comuacuten cuando estamos desarrollando aplicaciones con Core DataBaacutesicamente nos dice que el modelo de base de datos que estaacute intentando cargar dememoria es distinto al que se indica en la aplicacioacuten esto es porque no hemos indicadoninguacuten meacutetodo de migracioacuten Realmente existen dos formas de solucionar este error

Persistencia

74Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Por un lado y soacutelo cuando estemos desarrollando y no tengamos ninguna versioacutende nuestra aplicacioacuten lanzada en la App Store podremos eliminar la base de datosdel dispositivo simulador simplemente borrando la aplicacioacuten de el Tambieacutenpodemos ejecutar el siguiente coacutedigo una soacutela vez [[NSFileManagerdefaultManager] removeItemAtURLstoreURL errornil]

bull En el caso de que estemos realizando una migracioacuten sobre una versioacuten ya publicadaen la App Store el meacutetodo anterior no nos serviriacutea ya que obligariacuteamos al usuario aborrar su aplicacioacuten del dispositivo con lo que todos los datos que tuviera seperderiacutean Esto no es viable por lo que necesitaremos usar otro meacutetodo para realizarla migracioacuten y solucionar el error de una forma maacutes limpia este meacutetodo es el quevamos a comentar a continuacioacuten

XCode soporta Versioning para Core Data esto es que podemos crear versiones de labase de datos de una forma sencilla si seguimos una serie de pasos

1) Antildeadimos las siguientes opciones en forma de NSDictionary a nuestro coacutedigo delpersistentStoreCoordinator

NSDictionary options = [NSDictionary dictionaryWithObjectsAndKeys[NSNumber numberWithBoolYES]NSMigratePersistentStoresAutomaticallyOption[NSNumber numberWithBoolYES]NSInferMappingModelAutomaticallyOptionnil]

Y modificamos el meacutetodo addPersistentStoreWithType pasaacutendole como paraacutemetro eldiccionario options

if ([__persistentStoreCoordinatoraddPersistentStoreWithTypeNSSQLiteStoreTypeconfigurationnil URLstoreURL optionsoptions erroramperror])

Con el coacutedigo anterior indicamos al persistentStoreCoordinator que la migracioacuten enel caso de que se deba de realizar sea de forma automaacutetica

AtencioacutenDeberemos de tener en cuenta que este tipo de migracioacuten que estamos estudiando en este puntosoacutelo es vaacutelida para cambios simples en Core Data lo que Apple llama como lightweightmigration Este tipo de migracioacuten admite una serie de cambios que se indican dentro de ladocumentacioacuten de Apple para cambios maacutes complejos deberemos realizar migracionespersonalizadas y mucho maacutes complejas El documento en donde Apple explica el tipo demigraciones que existen para Core Data y su forma de implementarlas lo podemos consultardesde esta web

2) Ahora deberemos de crear la versioacuten del esquema de Core Data dentro de XCode para

Persistencia

75Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ello seleccionamos el fichero del esquema sesion04_ejemplo2xcdatamodeld yseleccionamos en el menuacute principal Editor gt Add Model Version A la nueva versioacuten lallamaremos sesion04_ejemplo_2 (versioacuten 2) Una vez que hayamos pulsado sobreFinish se nos habraacute creado la nueva versioacuten del esquema dentro de la principal y estaraacutemarcada con un tick la versioacuten anterior

3) Una vez que tenemos creada la nueva versioacuten deberemos modificarla antildeadiendole elnuevo atributo Seleccionamos la versioacuten recieacuten creadasesion04_ejemplo_2xcdatamodel y antildeadimos a la entidad DetallePersona elatributo codigoPostal Atencioacuten Si este atributo lo hemos antildeadido anteriormentedeberemos de borrarlo de la versioacuten anterior y antildeadirlo en la segunda versioacuten

4) Ahora nos queda indicar a Core Data que queremos usar la versioacuten 2 de la base dedatos para nuestra aplicacioacuten Para ello seleccionamos el esquema principal (el raiz)sesion04_ejemplo2xcdatamodeld y en la ventana de la derecha en la pestantildea de FileInspector seleccionamos como Versioacuten actual la versioacuten 2 esto lo hacemos dentro delapartado de Versioned Core Data Model Automaacuteticamente veremos que el siacutembolo detick cambia a la versioacuten 2

5) Ahora ya podemos volver a compilar y veremos que no nos aparece el error y funcionatodo seguacuten lo esperado

Magical RecordExisten varias librerias de coacutedigo libre que facilitan enormemente el uso de Core Data ennuestras aplicaciones iOS Una de ellas es Magical Record Mediante esta libreriacutea podremos usarCore Data sin tener que preocuparnos de todo el proceso de configuracioacuten en las controladorasde la configuracioacuten de migraciones etc Su instalacioacuten en un proyecto de XCode es muy faacutecil yaltamente recomendable ya que proporciona una API muy clara y sencilla de usar

Persistencia

76Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios

81 Usando User Defaults (1 punto)

En este ejercicio vamos a usar las variables de usuario para almacenar y mostrar los datosdel dispositivo que estamos usando y las veces que hemos arrancado la aplicacioacuten

Crear un proyecto nuevo usando la plantilla Single View Application con el nombreejercicio-userdefaults-ios Se debe de ejecutar en iPhone e iPad (Universal) Comoidentificador de empresa escribiremos esuajtech y como prefijo de clase UA Debe detener el ARC activado (desmarcar el resto de opciones)

Ayuda meacutetodos de UIDevicePara obtener los datos del dispositivo usaremos el singleton UIDevice estos son algunos de losmeacutetodos que podemos usar [[UIDevice currentDevice] name] [[UIDevicecurrentDevice] systemName] [[UIDevice currentDevice]systemVersion] [[UIDevice currentDevice] model] [[UIDevicecurrentDevice] localizedModel]

82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)

En este ejercicio disentildearemos un modelo de datos de dos tablas relacionadas entre ellasantildeadiremos datos por coacutedigo y los mostraremos en una vista de tabla La base de datosque vamos a implementar constaraacute de dos tablas por un lado una que contendraacute los tiacutetulosde series y otra que tendraacute los detalles A la primera la llamaremos Serie y la segundaDetalleSerie Para realizar correctamente el ejercicio deberemos seguir los siguientespasos

1) Crear un proyecto nuevo usando la plantilla Master-Detail Application con elnombre ejercicio-coredata-ios Se debe de ejecutar en iPhone como identificador deempresa escribiremos esuajtech y como prefijo de clase UA Debe de tener el ARCactivado y la opcioacuten de Use Core Data (desmarcar el resto de opciones)

2) Disentildear el siguiente modelo de datos

Persistencia

77Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esquema modelo de datos

3) Revisar el fichero UAAppDelegatem y comprobar que tenemos todos los meacutetodosnecesarios para que funcione Core Data en nuestra aplicacioacuten

4) Modificar el fichero UAMasterViewControllerm para que se muestren los tiacutetulos delas series en la tabla

5) Probar la aplicacioacuten antildeadiendo unos tiacutetulos de prueba

83 Migrando datos con Core Data (05 puntos)

Una vez que hemos disentildeado la Base de Datos con Core Data vamos a modificarlaantildeadiendo un atributo maacutes a la tabla de DetalleSerie valoracion

a) iquestQueacute pasa si cambiamos el modelo de datos y arrancamos la aplicacioacuten iquestQueacute errornos da y por queacute

b) iquestCoacutemo podemos evitar este tipo de errores cuando estemos desarrollando iquestY siestamos en produccioacuten con la aplicacioacuten ya a la venta

c) Crea una nueva versioacuten del modelo

d) Modifica el coacutedigo necesario dentro de la clase UAAppDelegate para evitar este tipo deerrores cuando estemos en un entorno de produccioacuten

e) Vuelve a arrancar la aplicacioacuten y comprueba que funciona bien

Persistencia

78Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Persistencia

79Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

  • 1 Persistencia en Android ficheros y SQLite
    • 11 Introduccioacuten
    • 12 Manejo de ficheros tradicionales en Android
      • 121 Apertura de ficheros
      • 122 Ficheros como recursos
      • 123 Operar con ficheros
      • 124 Almacenar datos en la tarjeta de memoria
        • 13 Base de datos SQLite
          • 131 Content Values
          • 132 Cursores
          • 133 Trabajar con bases de datos SQLite
          • 134 La clase SQLiteOpenHelper
          • 135 Crear una base de datos sin SQLiteHelper
          • 136 Realizar una consulta
          • 137 Extraer resultados de un cursor
          • 138 Antildeadir actualizar y borrar filas
              • 2 Ejercicios - Persistencia en Android ficheros y SQLite
                • 21 Uso de ficheros (05 puntos)
                • 22 Persistencia con ficheros (05 puntos)
                • 23 Base de datos SQLiteOpenHelper (05 puntos)
                • 24 Base de datos insercioacuten y borrado (05 puntos)
                • 25 Base de datos probar nuestro adaptador (05 puntos)
                • 26 Base de datos cambios en la base de datos (05 puntos)
                  • 3 Persistencia en Android proveedores de contenidos y SharedPreferences
                    • 31 Shared Preferences
                      • 311 Guardar Shared Preferences
                      • 312 Leer Shared Preferences
                      • 313 Interfaces para Shared Preferences
                      • 314 Definiendo una pantalla de preferencias con un layout en XML
                      • 315 Controles nativos para preferencias
                      • 316 Actividades de preferencias
                      • 317 Shared Preference Change Listeners
                        • 32 Proveedores de contenidos
                          • 321 Proveedores nativos
                          • 322 Proveedores propios crear un nuevo proveedor de contenidos
                          • 323 Proveedores propios crear la interfaz de consultas
                          • 324 Proveedores propios tipo MIME
                          • 325 Proveedores propios registrar el proveedor
                          • 326 Content Resolvers
                          • 327 Otras operaciones con Content Resolvers
                              • 4 Ejercicios - Persistencia en Android proveedores de contenidos y SharedPreferences
                                • 41 Compartir datos entre actividades con Shared Preferences (075 puntos)
                                • 42 Actividad de preferencias (075 puntos)
                                • 43 Proveedor de contenidos propio (075 puntos)
                                • 44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)
                                  • 5 Persistencia de datos en iOS Ficheros y SQLite
                                    • 51 Introduccioacuten
                                    • 52 Ficheros plist (Property Lists)
                                      • 521 Leyendo datos desde ficheros plist
                                      • 522 Escribiendo datos en ficheros plist
                                        • 53 SQLite
                                          • 531 Herramientas disponibles
                                          • 532 Lectura de datos
                                          • 533 Mostrando los datos en una tabla
                                              • 6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios
                                                • 61 Creando y leyendo un fichero plist (15 puntos)
                                                • 62 Implementar vista de detalle de la serie (05 puntos)
                                                • 63 Uso baacutesico de una base de datos SQLite (1 punto)
                                                  • 7 Persistencia de datos en iOS User Defaults y Core Data
                                                    • 71 Introduccioacuten
                                                    • 72 User Defaults
                                                    • 73 Core Data
                                                      • 731 Creando el proyecto
                                                      • 732 Definiendo el modelo de datos
                                                      • 733 Probando el modelo
                                                      • 734 Generando los modelos automaacuteticamente
                                                      • 735 Creando la vista de tabla
                                                      • 736 Migraciones de bases de datos con Core Data
                                                          • 8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios
                                                            • 81 Usando User Defaults (1 punto)
                                                            • 82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)
                                                            • 83 Migrando datos con Core Data (05 puntos)
Page 4: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de

Por otra parte el paraacutemetro ContextMODE_APPEND permite antildeadir contenido a un ficheroque ya se hubiera creado previamente (o crear uno nuevo en el caso en el que eacuteste noexistiera)

Estos meacutetodos tan soacutelo permiten abrir ficheros en la carpeta de la aplicacioacuten por lo que sise intenta antildeadir alguacuten separador de ruta se produciraacute una excepcioacuten El fichero abiertoutilizando OpenFileOutput (usando cualquiera de los dos paraacutemetros anteriores) seguardariacutea dentro del aacuterbol de directorios del dispositivo en la carpetadatadata[paquete]files donde [paquete] seriacutea el nombre del paquete de laaplicacioacuten Se trata de ficheros de texto normales por los que se puede acceder a ellosutilizando el shell de adb y utilizando el comando cat Para acceder al shell podemoshacer lo siguiente

adb shell

Por supuesto no debemos olvidarnos de cerrar el fichero una vez vayamos a dejar deutilizarlo mediante la correspondiente llamada al meacutetodo close()

fosclose()fisclose()

122 Ficheros como recursos

Es posible antildeadir ficheros como recursos de la aplicacioacuten Para ello los almacenamos enla carpeta resraw de nuestro proyecto Estos ficheros seraacuten de soacutelo lectura y paraacceder a ellos llamaremos al meacutetodo openRawResource del objeto Resource de nuestraaplicacioacuten Como resultado obtendremos un objeto de tipo InputStream Un ejemploseriacutea el que se muestra a continuacioacuten

Resources myResources = getResources()InputStream myFile = myResourcesopenRawResource(Rrawfichero)

Antildeadir ficheros a los recursos de la aplicacioacuten es un mecanismo para disponer de fuentesde datos estaacuteticas de gran tamantildeo como podriacutea ser por ejemplo un diccionario Esto seraacuteasiacute en aquellos casos en los que no sea aconsejable (o deseable) almacenar estainformacioacuten en una base de datos

Como en el caso de cualquier otro recurso seriacutea posible guardar diferentes versiones deun mismo archivo para diferentes configuraciones del dispositivo Podriacuteamos tener porejemplo un fichero de diccionario para diferentes idiomas

123 Operar con ficheros

Una alternativa simple para escribir en un fichero o leer de eacutel es utilizar las clasesDataInputStream y DataOutputStream que nos permiten leer o escribir a partir dediferentes tipos de datos como enteros o cadenas En el siguiente ejemplo vemos coacutemo esposible guardar una cadena en un fichero y a continuacioacuten leerla

Persistencia

4Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

FileOutputStream fos = openFileOutput(ficherotxtContextMODE_PRIVATE)String cadenaOutput = new String(Contenido del ficheron)DataOutputStream dos = new DataOutputStream(fos)doswriteBytes(cadenaOutput)fosclose()

FileInputStream fin = openFileInput(ficherotxt)DataInputStream dis = new DataInputStream(fin)String string = disreadLine() Hacer algo con la cadenafinclose()

124 Almacenar datos en la tarjeta de memoria

Veamos ahora queacute pasos debemos seguir para poder escribir datos en la tarjeta dememoria (SD card) Para ello se requiere el uso del meacutetodoEnvironmentgetExternalStorageDirectory() y una instancia de la claseFileWriter tal como se muestra en el siguiente ejemplo

try File raiz = EnvironmentgetExternalStorageDirectory()if (raizcanWrite())

File file = new File(raiz ficherotxt)BufferedWriter out = new BufferedWriter(new

FileWriter(file))outwrite(Mi texto escrito desde Androidn)outclose()

catch (IOException e)

Loge(FILE IO Error en la escritura de fichero +egetMessage())

Para probarlo en el emulador necesitamos tener creada una tarjeta SD lo cual se puedeconseguir mediante la herramienta del Android SDK mksdcard

mksdcard 512M sdcardiso

Podremos cargar esta tarjeta SD en nuestro emulador seleccionando el archivo recieacutencreado dentro del apartado SD card de la ventana de edicioacuten de las propiedades delemulador Para copiar fuera del emulador el archivo que hemos grabado en la tarjeta dememoria podemos utilizar el comando

adb pull sdcardficherotxt ficherotxt

Por uacuteltimo no debemos olvidar antildeadir el permiso correspondiente en el archivo Manifestde nuestra aplicacioacuten para poder escribir datos en un dispositivo de almacenamientoexterno Para ello antildeadimos lo siguiente al elemento manifest del AndroidManifestxml(recuerda que esto no va ni dentro del elemento application ni dentro del elementoactivity)

ltuses-permission androidname=androidpermissionWRITE_EXTERNAL_STORAGEgt

Persistencia

5Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

13 Base de datos SQLite

SQLite es un gestor de bases de datos relacional y es de coacutedigo abierto cumple con losestaacutendares y es extremadamente ligero Ademaacutes guarda toda la base de datos en un uacutenicofichero Su utilidad es evidente en el caso de aplicaciones de pequentildeo tamantildeo pues eacutestasno requeriraacuten la instalacioacuten adicional de ninguacuten gestor de bases de datos Esto tambieacutenseraacute asiacute en el caso de dispositivos empotrados con recursos limitados Android incluyesoporte a SQLite y en esta seccioacuten veremos coacutemo usarlo

Por medio de SQLite podremos crear bases de datos relacionales independientes paranuestras aplicaciones Con ellas podremos almacenar datos complejos y estructuradosAunque el disentildeo de bases de datos quedaraacute fuera del contenido de este curso ya quenecesitariacuteamos de maacutes tiempo para poder tratarlo es conveniente resaltar el hecho de quelas mejores praacutecticas en este campo siguen siendo uacutetiles en el caso de aplicacionesAndroid En particular la normalizacioacuten de datos para evitar redundancia es un paso muyimportante en el caso en el que estemos disentildeando una base de datos para dispositivoscon recursos limitados

En el caso del sistema Android SQLite se implementa como una libreriacutea C compacta enlugar de ejecutarse en un proceso propio Debido a ello cualquier base de datos quecreemos seraacute integrada como parte de su correspondiente aplicacioacuten Esto reducedependencias externas minimiza la latencia y simplifica ciertos procesos como lasincronizacioacuten

NotaLas bases de datos de Android se almacenan en el directoriodatadata[PAQUETE]databases donde [PAQUETE] es el paquetecorrespondiente a la aplicacioacuten Por defecto todas las bases de datos son privadas y tan soacuteloaccesibles por la aplicacioacuten que las creoacute

131 Content Values

Para insertar nuevas filas en tablas haremos uso de objetos de la clase ContentValuesCada objeto de este tipo representa una uacutenica fila en una tabla y se encarga de emparejarnombres de columnas con valores concretos Hay que tener en cuenta que SQLite difierede muchos otros motores de bases de datos en cuanto al tipado de las columnas La ideabaacutesicamente es que los valores para las columnas no tienen por queacute ser de un uacutenico tipoes decir los datos en SQLite son deacutebilmente tipados La consecuencia de esto es que noes necesario comprobar tipos cuando se asignan o extraen valores de cada columna de unafila con lo que se mejora la eficiencia

132 Cursores

Persistencia

6Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Los cursores son una herramienta fundamental en el tratamiento de informacioacuten enAndroid y tambieacuten cuando se trabaja con bases de datos Por lo tanto en esta seccioacutenintroducimos este concepto tan importante

Cualquier consulta a una base de datos en Android devolveraacute un objeto de la claseCursor Los cursores no contienen una copia de todos los resultados de la consulta sinoque maacutes bien se trata de punteros al conjunto de resultados Estos objetos proporcionanun mecanismo para controlar nuestra posicioacuten (fila) en el conjunto de resultadosobtenidos tras la consulta

La clase Cursor incluye entre otras las siguientes funciones de navegacioacuten entreresultados

bull moveToFirst desplaza el cursor a la primera fila de los resultados de la consultabull moveToNext desplaza el cursor a la siguiente filabull moveToPrevious desplaza el cursor a la fila anteriorbull getCount devuelve el nuacutemero de filas del conjunto de resultados de la consultabull getColumnName devuelve el nombre de la columna especificada mediante un iacutendice

que se pasa como paraacutemetrobull getColumnNames devuelve un array de cadenas conteniendo el nombre de todas las

columnas en el cursorbull moveToPosition mueve el cursor a la fila especificadabull getPosition devuelve el iacutendice de la posicioacuten apuntada actualmente por el cursor

A lo largo de esta sesioacuten aprenderemos coacutemo realizar consultas a una base de datos ycomo extraer valores o nombres de columnas concretos a partir de los cursores obtenidospor medio de dichas consultas

133 Trabajar con bases de datos SQLite

Suele considerarse una buena praacutectica el crear una clase auxiliar que nos facilite lainteraccioacuten con la base de datos Se trataraacute de una clase de tipo adaptador que crearaacute unacapa de abstraccioacuten que encapsularaacute dichas interacciones De esta forma podremosantildeadir eliminar o actualizar elementos en la base de datos sin necesidad de introducir niuna uacutenica instruccioacuten en SQL en el resto del coacutedigo permitiendo ademaacutes que se hagan lascomprobaciones de tipos correspondientes

Una clase de este tipo deberiacutea incluir meacutetodos para crear abrir o cerrar la base de datosTambieacuten puede ser un lugar conveniente para publicar constantes estaacuteticas relacionadascon la base de datos como por ejemplo constantes que almacenen el nombre de la tabla olas diferentes columnas

A continuacioacuten se muestra el coacutedigo de lo que podriacutea ser un adaptador para una base dedatos estaacutendar Incluye una extensioacuten de la clase SQLiteOpenHelper que seraacute tratada enmaacutes detalle en la siguiente seccioacuten y que se utiliza para simplificar la apertura lacreacioacuten y la actualizacioacuten de la base de datos

Persistencia

7Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

import androidcontentContextimport androiddatabaseimport androiddatabasesqliteimport androiddatabasesqliteSQLiteDatabaseCursorFactoryimport androidutilLog

public class MiAdaptadorBD private static final String NOMBRE_BASE_DATOS =

mibasededatosdbprivate static final String TABLA_BASE_DATOS = mainTableprivate static final int VERSION_BASE_DATOS = 1

Nombre de la columna de clave primariapublic static final String CP_ID=_id El nombre e iacutendice de cada columna en la base de datospublic static final String COLUMNA_NOMBRE=nombrepublic static final int COLUMNA_INDICE = 1 PENDIENTE crear campos adicionales para el resto de columnas de la base de datos

Sentencia SQL para crear una nueva base de datosprivate static final String CREAR_BASE_DATOS = create table +

TABLA_BASE_DATOS + ( + CP_ID + integer primary key autoincrement +COLUMNA_NOMBRE + text not null)

Variable para almacenar la instancia de la base de datosprivate SQLiteDatabase db Contexto de la aplicacioacuten que estaacute usando la base de datosprivate final Context contexto Clase helper usada para crear o actualizar la base de datos (hablamos de ella en la siguiente seccioacuten)private miHelperBD dbHelper

Crea la base de datos con ayuda del helperpublic MiAdaptadorBD(Context _contexto)

contexto = _contextodbHelper = new miHelperBD(contexto NOMBRE_BASE_DATOS

nullVERSION_BASE_DATOS)

Abre una base de datos ya existentepublic MiAdaptadorBD open() throws SQLException

db = dbHelpergetWritableDatabase()return this

Cierra la base de datos cuando eacutesta ya no va a ser utilizadapublic void close()

dbclose()

Meacutetodo para insertar un elemento en la tabla de la base dedatos

public int insertar(MiObjeto _miObjeto) PENDIENTE crear nuevos elementos ContentValues para

representar al objeto e insertarlos en la base de datos

Devolvemos el iacutendice del nuevo elemento en la base dedatos

return index

Persistencia

8Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Meacutetodo para eliminar un elemento de la tabla de la base dedatos

Devuelve un valor booleano indicando el eacutexito o no de la operacioacutenpublic boolean eliminar(long _indiceFila)

return dbdelete(TABLA_BASE_DATOS CP_ID + = +_indiceFila null) gt 0

Meacutetodo para obtener un Cursor que apunte a todas las filascontenidas

en la tabla de la base de datospublic Cursor obtenerTodos ()

return dbquery(TABLA_BASE_DATOS new String[] CP_IDCOLUMNA_NOMBRE

null null null nullnull)

Meacutetodo para obtener un elemento determinado de la base de datos a partir de su iacutendice de filapublic MiObjeto getObjeto(long _indiceFila)

PENDIENTE obtener un cursor a una fila de la base de datos y usar sus valores para inicializar una instancia de MiObjeto

return objectInstance

Meacutetodo para actualizar un elemento de la tabla de la base dedatos

public boolean actualizar(long _indiceFila MiObjeto _miObjeto) PENDIENTE crear nuevos ContentValues a partir del

objeto y usarlos para actualizar una fila en la tabla

correspondiente

return true

Clase privada auxiliar para ayudar en la creacioacuten yactualizacioacuten

de la base de datosprivate static class miHelperBD extends SQLiteOpenHelper

public miHelperBD(Context contexto String nombreCursorFactory factory int version)

super(contexto nombre factory version)

Meacutetodo llamado cuando la base de datos no existe en eldisco

y la clase Helper necesita crearla desde ceroOverridepublic void onCreate(SQLiteDatabase _db)

_dbexecSQL(CREAR_BASE_DATOS)

Meacutetodo llamado cuando la versioacuten de la base de datos en disco es diferente a la indicada en este caso la base de datos en disco necesita ser actualizadaOverridepublic void onUpgrade(SQLiteDatabase _db int

_versionAnterior

Persistencia

9Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

int _versionNueva) Mostramos en LogCat la operacioacutenLogw(AdaptadorBD Actualizando desde la

versioacuten +_versionAnterior + a la versioacuten +_versionNueva + lo cual borraraacute los

datospreviamente almacenados)

Actualizamos la base de datos para que seadapte a la nueva versioacuten

La forma maacutes sencilla de llevar a cabo dichaactualizacioacuten es

eliminar la tabla antigua y crear una nueva_dbexecSQL(DROP TABLE IF EXISTS +

TABLA_BASE_DATOS)onCreate(_db)

134 La clase SQLiteOpenHelper

La clase SQLiteOpenHelper es una clase abstracta utilizada como medio paraimplementar un patroacuten de creacioacuten apertura y actualizacioacuten de una determinada base dedatos Al implementar una subclase de SQLiteOpenHelper podemos abstraernos de laloacutegica subyacente a la decisioacuten de crear o actualizar una base de datos de manera previa aque eacutesta deba ser abierta

En el ejemplo de coacutedigo anterior se vio coacutemo crear una subclase de SQLiteOpenHelper

por medio de la sobrecarga de sus meacutetodos onCreate y onUpgrade los cuales permitencrear una nueva base de datos o actualizar a una nueva versioacuten respectivamente

NotaEn el ejemplo anterior el meacutetodo onUpgrade simplemente eliminaba la tabla existente y lareemplazaba con la nueva definicioacuten de la misma En la praacutectica la mejor solucioacuten seriacutea migrarlos datos ya existentes a la nueva tabla

Mediante los meacutetodos getReadableDatabase y getWritableDatabase podemos abrir yobtener una instancia de la base de datos de soacutelo lectura o de lectura y escriturarespectivamente La llamada a getWritableDatabase podriacutea fallar debido a falta deespacio en disco o cuestiones relativas a permisos por lo que es una buena praacutectica teneren cuenta esta posibilidad por medio del mecanismo de manejo de excepciones de JavaEn caso de fallo la solucioacuten podriacutea pasar por al menos proporcionar una copia de soacutelolectura tal como se muestra en el siguiente ejemplo

dbHelper = new miHelperBD(contexto NOMBRE_BASE_DATOS nullVERSION_BASE_DATOS)

SQLiteDatabase dbtry

db = dbHelpergetWritableDatabase() catch (SQLiteException ex)

db = dbHelpergetReadableDatabase()

Persistencia

10Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Al hacer una llamada a cualquiera de los dos meacutetodos anteriores se invocaraacuten losmanejadores de evento adecuados Asiacute pues si por ejemplo la base de datos no existieraen disco se ejecutariacutea el coacutedigo de onCreate Si por otra parte la versioacuten de la base dedatos se hubiera modificado se disparariacutea el manejador onUpgrade Lo que sucedeentonces es que al hacer una llamada a getWritableDatabase o getReadableDatabase

se devolveraacute una base de datos nueva actualizada o ya existente seguacuten el caso

135 Crear una base de datos sin SQLiteHelper

Tambieacuten es posible crear y abrir bases de datos sin necesidad de utilizar una subclase deSQLiteHelper Para ello haremos uso del meacutetodo openOrCreateDatabase En este casoseraacute necesario tambieacuten hacer uso del meacutetodo execSQL sobre la instancia de la base dedatos devuelta por el meacutetodo anterior para ejecutar los comandos SQL que permitiraacutencrear las tablas de la base de datos y establecer las relaciones entre ellas El siguientecoacutedigo muestra un ejemplo

private static final String NOMBRE_BASE_DATOS = mibasededatosdbprivate static final String TABLA_BASE_DATOS = tablaPrincipal

private static final String CREAR_BASE_DATOS =create table + TABLA_BASE_DATOS + _id integer primare key

autoincrement +columna_uno text not null)

SQLiteDatabase db

private void crearBaseDatos() db = openOrCreateDatabase(NOMBRE_BASE_DATOS ContextMODE_PRIVATE

null)dbexecSQL(CREAR_BASE_DATOS)

NotaAunque no es necesariamente obligatorio es recomendable que cada tabla incluya un campo detipo clave primaria con autoincremento a modo de iacutendice para cada fila En el caso en el que sedesee compartir la base de datos mediante un proveedor de contenidos un campo uacutenico de estetipo es obligatorio

136 Realizar una consulta

El resultado de cualquier consulta a una base de datos seraacute un objeto de la clase CursorEsto permite a Android manejar los recursos de manera maacutes eficiente de tal forma que enlugar de devolver todos los resultados eacutestos se van proporcionando conforme senecesitan

Para realizar una consulta a una base de datos haremos uso del meacutetodo query de lainstancia correspondiente de la clase SQLiteDatabase Los paraacutemetros que requiere este

Persistencia

11Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

meacutetodo son los siguientes

bull Un booleano opcional que permite indicar si el resultado deberiacutea contener tan soacutelovalores uacutenicos

bull El nombre de la tabla sobre la que realizar la consultabull Un array de cadenas que contenta un listado de las columnas que deben incluirse en el

resultadobull Una claacuteusula where que defina las filas que se deben obtener de la tabla Se puede

utilizar el comodiacuten el cual seraacute reemplazado por los valores que se pasen a traveacutesdel siguiente paraacutemetro

bull Un array de cadenas correspondientes a argumentos de seleccioacuten que reemplazaraacutenlos siacutembolos en la claacuteusula where

bull Una claacuteusula group by que define coacutemo se agruparaacuten las filas obtenidas comoresultado

bull Un filtro having que indique que grupos incluir en el resultado en el caso en el que sehaya hecho uso del paraacutemetro anterior

bull Una cadena que describa la ordenacioacuten que se llevaraacute a cabo sobre las filas obtenidascomo resultado

bull Una cadena opcional para definir un liacutemite en el nuacutemero de resultados a devolver

NotaComo se puede observar la mayoriacutea de estos paraacutemetros hacen referencia al lenguaje SQLTratar este tema queda fuera de los objetivos de este curso por lo que en nuestros ejemplos ledaremos en la mayoriacutea de las ocasiones un valor null a estos paraacutemetros

El siguiente coacutedigo muestra un ejemplo de consultas utilizando diversos paraacutemetros

Devolver los valores de las columnas uno y tres para todas las filas sin duplicadosString[] columnas = new String[] CLAVE_ID CLAVE_COL1 CLAVE_COL3

Cursor todasFilas = dbquery(true TABLA_BASE_DATOS columnas null nullnull null

null null)

Devolvemos los valores de todas las columnas para aquellas filas cuyovalor de la columna 3 sea igual al de una determinada variable Ordenamos las filasseguacuten el valor de la columna 5 Como queremos los valores de todas las columnas le damos al paraacutemetro correspondiente a las columnas a devolver el valor null En este caso no se ha hecho uso del primer paraacutemetro booleano que esoptativoString where = CLAVE_COL3 + = + valorString orden = CLAVE_COL5Cursor miResultado = dbquery(TABLA_BASE_DATOS null where null nullnull orden)

137 Extraer resultados de un cursor

Persistencia

12Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El primer paso para obtener resultados de un cursor seraacute desplazarnos a la posicioacuten apartir de la cual queremos obtener los datos usando cualquiera de los meacutetodosespecificados anteriormente (como por ejemplo moveToFirst o moveToPosition) Unavez hecho esto hacemos uso de meacutetodos get[TIPO] pasando como paraacutemetro el iacutendice dela columna Esto tendraacute como resultado la devolucioacuten del valor para dicha columna de lafila actualmente apuntada por el cursor

String valor = miResultadogetString(indiceColumna)

A continuacioacuten podemos ver un ejemplo maacutes completo en el que se va iterando a lo largode los resultados apuntados por un cursor extrayendo y sumando los valores de unacolumna de flotantes

int COLUMNA_VALORES_FLOTANTES = 2Cursor miResultado = dbquery(Tabla null null null null nullnull)float total = 0

Nos aseguramos de que se haya devuelto al menos una filaif (miResultadomoveToFirst())

Iteramos sobre el cursordo

float valor =miResultadogetFloat(COLUMNA_VALORES_FLOTANTES)

total += valor while (miResultadomoveToNext())

float media = total miResultadogetCount()

NotaDebido a que las columnas de las bases de datos en SQLite son debilmente tipadas podemosrealizar castings cuando sea necesario Por ejemplo los valores guardados como flotantes en labase de datos podriacutean leerse maacutes adelante como cadenas

138 Antildeadir actualizar y borrar filas

La clase SQLiteDatabase proporciona los meacutetodos insert delete y update queencapsulan las instrucciones SQL requeridas para llevar a cabo estas acciones Ademaacutesdisponemos de la posibilidad de utilizar el meacutetodo execSQL el cual nos permite ejecutarcualquier sentencia SQL vaacutelida en nuestras tablas de la base de datos en el caso en el quequeramos llevar a cabo estas (u otras) operaciones manualmente

NotaCada vez que modifiques los valores de la base de datos puedes actualizar un cursor que se puedaver afectado por medio del meacutetodo refreshQuery de la clase Cursor

Para insertar una nueva fila es necesario construir un objeto de la clase ContentValues

y usar su meacutetodo put para proporcionar valor a cada una de sus columnas Para insertar la

Persistencia

13Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

nueva fila pasaremos como paraacutemetro este objeto del tipo ContentValues al meacutetodoinsert de la instancia de la base de datos junto por supuesto con el nombre de la tablaA continuacioacuten se muestra un ejemplo

Crear la nueva filaContentValues nuevaFila = new ContentValues()

Asignamos valores a cada columnanuevaFilaput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas ]

Insertar la nueva fila en la tabladbinsert(TABLA_BASE_DATOS null nuevaFila)

NotaLos ficheros como imaacutegenes o ficheros de audio no se suelen almacenar dentro de tablas en unabase de datos En estos casos se suele optar por almacenar en la base de datos una cadena con laruta al fichero o una URI

La operacioacuten de actualizar una fila tambieacuten puede ser llevada a cabo por medio del usode la clase ContentValues En este caso deberemos llamar al meacutetodo update de lainstancia de la base de datos pasando como paraacutemetro el nombre de la tabla el objetoContentValues y una claacuteusula where que especifique la fila o filas a actualizar tal comose puede ver en el siguiente ejemplo

Creamos el objeto utilizado para definir el contenido a actualizarContentValues valoresActualizar = new ContentValues()

Asignamos valores a las columnas correspondientesvaloresActualizarput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas a actualizar ]

String where = CLAVE_ID + = + idFila

Actualizamos la fila con el iacutendice especificado en la instruccioacutenanteriordbupdate(TABLA_BASE_DATOS valoresActualizar where null)

Por uacuteltimo para borrar una fila simplemente llamaremos al meacutetodo delete de lainstancia de la base de datos especificando en este caso el nombre de la tabla que se veraacuteafectada por la operacioacuten y una claacuteusula where que utilizaremos para especificar las filasque queremos borrar Un ejemplo podriacutea ser el siguiente

dbdelete(TABLA_BASE_DATOS CLAVE_ID + = + idFila null)

Persistencia

14Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

2 Ejercicios - Persistencia en Android ficheros y SQLite

21 Uso de ficheros (05 puntos)

En este ejercicio vamos a crear una aplicacioacuten que muestre un listado de cadenas porpantalla Estas cadenas se almacenaraacuten en un fichero de texto privado para la aplicacioacutenPodremos antildeadir nuevas cadenas a partir de un cuadro de edicioacuten presente en la interfazPartiremos del proyecto Ficheros proporcionado en las plantillas de la aplicacioacuten Debesseguir los siguientes pasos

bull Antildeadir un manejador al botoacuten de la actividad principal para que cada vez que seapulsado se guarde en un fichero de texto El fichero se llamaraacute ficherotxt Al abrirel fichero para escritura se utilizaraacute el paraacutemetro ContextMODE_APPEND con lo quecada nueva cadena se antildeadiraacute al final del fichero en el caso en el que eacuteste ya existieraPara escribir en el fichero utilizaremos el meacutetodo writeBytes del objetoDataOutputStream correspondiente

bull Al pulsar el botoacuten tambieacuten se deberaacute borrar el contenido del elemento EditText (leasignamos a la vista una cadena vaciacutea con el meacutetodo setText)

bull Ejecutamos la aplicacioacuten e introducimos algunas liacuteneas en el fichero Vamos acomprobar ahora que todo ha funcionado correctamente Para ello accedemos alsistema de ficheros de nuestro dispositivo ejecutando el comando adb shell

dentro de la carpeta platform-tools de nuestra carpeta de instalacioacuten del SDK deAndroid

bull Comprueba que se ha creado el fichero ficherotxt en la carpetadatadataesuajtechandroidficherosfiles Examina su contenido ejecutando cat

ficherotxt Si algo no ha salido bien siempre podraacutes eliminar el fichero con rm

ficherotxtbull En la interfaz de la actividad se ha incluido una vista de tipo TextView debajo del

botoacuten Antildeade un manejador para dicha vista de tal forma que cada vez que se hagaclick sobre ella se muestre el contenido del fichero ficherotxt Para ello leeremos elfichero liacutenea a liacutenea antildeadiendo cada una al TextView por medio del meacutetodo append

Persistencia

15Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Interfaz de la aplicacioacuten Ficheros

22 Persistencia con ficheros (05 puntos)

Seguramente habraacutes observado que si abandonas la aplicacioacuten anterior (pulsando el botoacutenBACK del dispositivo) al volver a ejecutarla el contenido del TextView ha desaparecidoDebes volver a pulsar sobre la vista para que se vuelva a mostrar el contenido del ficheroHaz las modificaciones pertinentes para que esto no sea asiacute es decir para que cuandovuelva a mostrarse la actividad tras haber sido eliminada de la memoria se muestre elTextView tal cual se veiacutea antes de abandonar la aplicacioacuten

AvisoEn este ejercicio no se estaacute pidiendo que cargues el contenido del fichero en el TextView nadamaacutes arrancar la aplicacioacuten Puede darse el caso de que lo que esteacute mostrando el TextViewcuando la actividad sea eliminada de memoria no se corresponda con el contenido actualizado delfichero

NotaPara poder completar el ejercicio deberaacutes repasar los manejadores de evento de la primera sesioacutendel moacutedulo de Android relacionados con el ciclo de vida de ejecucioacuten de actividades

23 Base de datos SQLiteOpenHelper (05 puntos)

En las plantillas de la aplicacioacuten se incluye la aplicacioacuten BaseDatos En el proyecto de laaplicacioacuten se incluye el esqueleto de una clase MiAdaptadorBD que tendremos quecompletar Se trata de un patroacuten que nos va a permitir acceder a una base de datos de

Persistencia

16Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

usuarios dentro de la aplicacioacuten sin necesidad de hacer uso de coacutedigo SQL

La clase MiAdaptadorBD incluye a su vez otro patroacuten en este caso implementado comouna subclase de SQLiteOpenHelper Eacuteste nos obliga a definir queacute ocurre cuando la basede datos todaviacutea no existe y debe ser creada y queacute ocurre si ya existe pero debe seractualizada porque ha cambiado de versioacuten Asiacute el SQLiteOpenHelper queimplementemos en este caso MiOpenHelper nos devolveraacute siempre una base de datosseparaacutendonos de la loacutegica encargada de comprobar si la base de datos existe o no

En este primer ejercicio se pide hacer lo siguiente

bull Ejecutar la sentencia de creacioacuten de bases de datos (la tenemos declarada comoconstante de la clase) en el meacutetodo MiOpenHelperonCreate

bull Implementar tambieacuten el meacutetodo onUpgrade Idealmente eacuteste deberiacutea portar las tablasde la versioacuten antigua a la versioacuten nueva copiando todos los datos Nosotros vamos aeliminar directamente la tabla que tenemos con la sentencia SQL DROP TABLE IF

EXISTS + NOMBRE_TABLA y volveremos a crearlabull En el constructor de MiAdaptadorBD debemos obtener en el campo db la base de

datos utilizando MiOpenHelper

24 Base de datos insercioacuten y borrado (05 puntos)

Continuamos trabajando con el proyecto anterior En este ejercicio completaremos elcoacutedigo relacionado con las sentencias de insercioacuten y borrado Para realizar la insercioacutenvamos a hacer uso de un mecanismo que no hemos visto en la sesioacuten de teoriacutea y queconsiste en utilizar una sentencia de insercioacuten SQL precompilada que se completaraacute conlos valores concretos a insertar en la base de datos justo antes de realizar dicha insercioacutenLa ventaja de las sentencias compiladas es que evitan que se produzcan ataques deinyeccioacuten de coacutedigo

Como se puede observar se ha incluido un atributo a la clase que representa la insercioacutenSQL

private static final String INSERT = insert into + NOMBRE_TABLA +(+COLUMNAS[1]+) values ()

En esta sentencia no se indica ninguacuten valor concreto para la columna nombre En el lugaren el que deberiacutean aparecer los valores de dicho campo se ha escrito simplemente unsiacutembolo de interrogacioacuten Tambieacuten se ha antildeadido una instancia de la claseSQLiteStatement como parte de los atributos de la clase

private SQLiteStatement insertStatement

Realizamos los siguientes pasos

bull En el constructor de la clase MiAdaptadorBD llevamos a cabo la compilacioacuten de lasentencia

thisinsertStatement = thisdbcompileStatement(INSERT)

Persistencia

17Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Una vez hecho esto cada vez que deseemos insertar un nuevo usuario en la base dedatos deberemos dar valores concretos a la columna nombre y ejecutar la sentenciaPara ello debemos antildeadir el siguiente coacutedigo dentro del meacutetodo insert de la claseMiAdaptadorBD

thisinsertStatementbindString(1 nombreUsuario)return thisinsertStatementexecuteInsert()

bull Por uacuteltimo completamos el coacutedigo del meacutetodo deleteAll cuyo contenido es eliminara todos los usuarios de la base de datos Para que ademaacutes se devuelva el nuacutemero defilas afectadas podemos insertar la siguiente liacutenea en dicho meacutetodo

return dbdelete(NOMBRE_TABLA null null)

Una vez hechos todos estos cambios podremos utilizar la clase MiAdaptadorBD parahacer operaciones con la base de datos de usuarios de manera transparente

25 Base de datos probar nuestro adaptador (05 puntos)

Antildeade coacutedigo en la actividad Main para eliminar todos los usuarios de la base de datosantildeadir dos cualesquiera y listarlos por medio de la vista de tipo TextView que seencuentra en el layout de dicha actividad

Podemos comprobar mediante la liacutenea de comandos (comando adb shell) que la basede datos ha sido creada Para ello puede ser uacutetil hacer uso de los siguientes comandos

cd datadataesuajtechandroidbasedatosdatabasessqlite3 misusuariosdbsqlitegt schemasqlitegt tablessqlitegt select from usuarios

26 Base de datos cambios en la base de datos (05 puntos)

Ahora vamos a cambiar en la clase MiAdaptadorBD el nombre de la segunda columnaque en lugar de nombre se va a llamar nombres Ejecutamos la aplicacioacuten y comprobamosque sale con una excepcioacuten Comprueba por medio de LogCat cuaacutel ha sido el erroriquestCoacutemo lo podemos solucionar

NotaPista conforme hemos programado la clase MiAdaptadorBD y siguiendo el patroacuten de disentildeode SQLiteOpenHelper es posible arreglar el problema simplemente modificando el valor deun campo

Persistencia

18Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

3 Persistencia en Android proveedores de contenidos ySharedPreferences

31 Shared Preferences

Comenzamos esta sesioacuten hablando de una de las teacutecnicas maacutes simples de persistencia enAndroid junto al uso de ficheros Shared Preferences Se trata de un mecanismo ligeropara almacenar datos basado en pares clave-valor utilizado normalmente para guardarpreferencias u opciones de la aplicacioacuten Tambieacuten puede ser utilizado para almacenar elestado de la interfaz graacutefica para contemplar el caso de que nuestra actividad debafinalizar abruptamente su ejecucioacuten Otro posible uso es el de compartir informacioacutenentre componentes de una misma aplicacioacuten

NotaLos Shared Preferences nunca son compartidos entre diferentes aplicaciones

Este meacutetodo se basa en el uso de la clase SharedPreferences Es posible utilizarla paraguardar datos en muy diversos formatos booleanos cadenas flotantes y enteros

311 Guardar Shared Preferences

Para crear o modificar un Shared Preference llamamos al meacutetodogetSharedPreferences del contexto de la aplicacioacuten pasando como paraacutemetro elidentificador del conjunto de valores de preferencias con el que queremos trabajar Pararealizar la modificacioacuten del valor de un Shared Preference hacemos uso de la claseSharedPreferencesEditor Para obtener el objeto editor invocamos el meacutetodo edit

del objeto Shared Preferences que queremos modificar Los cambios se guardan medianteel meacutetodo commit Veamos un ejemplo

int modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences = getSharedPreferences(Mispreferencias modo)

Obtenemos un editor para modificar las preferenciasSharedPreferencesEditor editor = misSharedPreferencesedit()

Guardamos nuevos valores en el objeto SharedPreferenceseditorputBoolean(isTruetrue)editorputFloat(unFloat10)editorputInt(numeroEntero12)editorputLong(otroNumero2)editorputString(unaCadenavalor)

Guardamos los cambioseditorcommit()

Persistencia

19Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

312 Leer Shared Preferences

Para leer Shared Preferences utilizamos el meacutetodo getSharedPreferences tal como seha comentado anteriormente Pasamos como paraacutemetro el identificador del conjunto deShared Preferences al que deseamos acceder Una vez hecho esto ya podemos hacer usode meacutetodos de tipo get para acceder a preferencias individuales Hay diferentes meacutetodosget para diferentes tipos de datos Cada uno de ellos requiere como paraacutemetro la claveque identifica la preferencia cuyo valor deseamos obtener y un valor por defecto el cualse usaraacute en el caso concreto en el que no existiera la preferencia con la clave pasada comoprimer paraacutemetro Podemos ver un ejemplo a continuacioacuten

public static String MIS_PREFS = MIS_PREFS

public void cargarPreferences() Obtener las preferencias almacenadasint modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences =

getSharedPreferences(MIS_PREFS modo)

boolean isTrue = misSharedPreferencesgetBoolean(isTruefalse)float unFloat = misSharedPreferencesgetFloat(unFloat0)int numeroEntero = misSharedPreferencesgetInt(numeroEntero0)long otroNumero = misSharedPreferencesgetLong(otroNumero0)String unaCadena = misSharedPreferencesgetString(unaCadena)

313 Interfaces para Shared Preferences

Android proporciona una plataforma basada en XML para la creacioacuten de interfacesgraacuteficas para el manejo de preferencias Estas interfaces tendraacuten un aspecto similar al delas del resto de aplicaciones del sistema Al utilizarla nos estaremos asegurando de quenuestras actividades para el manejo de preferencias seraacuten consistentes con las del resto deaplicaciones del sistema De esta forma los usuarios estaraacuten familiarizados con el manejode esta parte de nuestra aplicacioacuten

La plataforma consiste en tres elementos

bull Layout de la actividad de preferencias se trata de un archivo XML que definiraacute lainterfaz graacutefica de la actividad que muestre los controles para modificar los valores delas preferencias Permite especificar las vistas a mostrar los posibles valores que sepueden introducir y a queacute clave de Shared Preferences se corresponde cada vista

bull Actividad de preferencias una subclase de PreferenceActivity que secorresponderaacute con la pantalla de preferencias de nuestra aplicacioacuten

bull Shared Preference Change Listener una implementacioacuten de la claseonSharedPreferenceChangeListener que actuaraacute en el caso en el que cambie elvalor de alguna Shared Preference

314 Definiendo una pantalla de preferencias con un layout en XML

Persistencia

20Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esta es sin duda la parte maacutes importante de una actividad de preferencias Se trata de unarchivo XML utilizado para definir varios aspectos de dicha actividad Al contrario queen el caso de los archivos de layout para interfaces graacuteficas que vimos en la sesioacutencorrespondiente del moacutedulo de Android estos archivos de layout se guardan en la carpetaresxml de los recursos de la aplicacioacuten

Aunque conceptualmente estos layouts son similares a los utilizados para definirinterfaces graacuteficas los layouts de actividades de preferencias utilizan un conjuntoespecializado de componentes especiacuteficos para este tipo de actividades Estaacuten pensadospara proporcionar interfaces con un aspecto similar al del resto de actividades depreferencias del sistema Estos controles se describen en maacutes detalle en la siguienteseccioacuten

Los layout de preferencias contendraacuten un elemento PreferenceScreen

ltxml version=10 encoding=utf-8gtltPreferenceScreen

xmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceScreengt

Se pueden antildeadir maacutes elementos de este tipo si se desea Al hacerlo eacutestos serepresentaraacuten como un elemento seleccionable en un listado que mostraraacute una nuevaventana de preferencias en caso de que sea seleccionado

Dentro de cada elemento PreferenceScreen podemos incluir tantos elementos de tipoPreferenceCategory y elementos especiacuteficos para antildeadir controles como se desee Loselementos PreferenceCategory son utilizados para dividir cada pantalla de preferenciasen subcategoriacuteas mediante una barra de tiacutetulo Su sintaxis es la siguiente

ltPreferenceCategoryandroidtitle=My Preference Categorygt

ltPreferenceCategorygt

Una vez dividida la pantalla en subcategoriacuteas tan soacutelo quedariacutea antildeadir los elementoscorrespondientes a los controles especiacuteficos que veremos en la siguiente seccioacuten Losatributos de los elementos XML para estos controles pueden variar aunque existe unconjunto de atributos comunes

bull androidkey la clave de Shared Preference que se usaraacute para guardar el valor delcontrol

bull androidtitle texto a mostrar para describir la preferenciabull androidsummary una descripcioacuten maacutes larga a mostrar debajo del texto definido con

el campo anteriorbull androiddefaultValue el valor por defecto que se mostraraacute inicialmente y tambieacuten

el que finalmente se guardaraacute si ninguacuten otro valor fue asignado a este campo

El siguiente listado muestra una pantalla de preferencias muy simple con una uacutenicacategoriacutea y un uacutenico control (un check box)

ltxml version=10 encoding=utf-8gt

Persistencia

21Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltPreferenceScreenxmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceCategory

androidtitle=Mi categoriacuteagtltCheckBoxPreference

androidkey=MI_CHECK_BOXandroidtitle=Preferencia Check Boxandroidsummary=Descripcioacuten de la preferencia

Check BoxandroiddefaultValue=true

gtltPreferenceCategorygt

ltPreferenceScreengt

Esta pantalla de preferencias se mostrariacutea de la siguiente forma

Ejemplo sencillo de ventana de preferencias

315 Controles nativos para preferencias

Android incluye diferentes controles que podemos antildeadir a nuestras ventanas depreferencias por medio de los elementos XML que se indican a continuacioacuten

bull CheckBoxPreference un check box estaacutendar que puede ser utilizado parapreferencias de tipo booleano

bull EditTextPreference permite al usuario introducir una cadena como valor para unapreferencia Al seleccionar el texto se mostraraacute un diaacutelogo con el que introducir elnuevo valor

bull ListPreference el equivalente a un Spinner Seleccionar este elemento de lainterfaz mostraraacute un diaacutelogo con las diferentes opciones que es posible seleccionar Seutilizan arrays para asociar valores y textos a las opciones de la lista

bull RingtonePreference un tipo especiacutefico de preferencia de tipo listado que permiteseleccionar entre diferentes tonos de teleacutefono Esta opcioacuten es particularmente uacutetil en

Persistencia

22Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

el caso en el que se esteacute creando una pantalla de preferencias para configurar lasopciones de notificacioacuten de una aplicacioacuten o componente de la misma

Tambieacuten es posible crear nuestros propios controles personalizados definiendo unasubclase de Preference (o cualquiera de sus subclases) Se puede encontrar maacutesinformacioacuten en la documentacioacuten de AndroidhttpdeveloperandroidcomreferenceandroidpreferencePreferencehtml

316 Actividades de preferencias

Para mostrar una pantalla de preferencias debemos definir una subclase dePreferenceActivity

public class MisPreferencias extends PreferenceActivity

La interfaz graacutefica de la interfaz se puede crear a partir del layout en el meacutetodo onCreatemediante una llamada a addPreferencesFromResource

Overridepublic void onCreate(Bundle savedInstaceState)

superonCreate(savedInstanceState)addPreferencesFromResource(Rxmlmilayout)

Como en el caso de cualquier otra actividad la actividad de preferencias que hayamosdefinido debe ser incluida en el Manifest de la aplicacioacuten

ltactivity androidname=MisPreferenciasandroidlabel=Mis preferenciasgt

ltactivitygt

Eso es todo lo que necesitamos para crear este tipo de actividades y mostrar una ventanacon las preferencias definidas en el layout Esta actividad puede ser iniciada comocualquier otra mediante un Intent ya sea a traveacutes de una llamada a startActivity obien a startActivityForResult

Intent i = new Intent(this MisPreferenciasclass)startActivityForResult(i MOSTRAR_PREFERENCIAS)

Los valores para las diferentes preferencias de la actividad son almacenadas en elcontexto de la aplicacioacuten Esto permite a cualquier componente de dicha aplicacioacutenincluyendo a cualquier actividad y servicio de la misma acceder a los valores tal como semuestra en el siguiente coacutedigo de ejemplo

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto) PENDIENTE hacer uso de meacutetodos get para obtener los valores

317 Shared Preference Change Listeners

Persistencia

23Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El uacuteltimo elemento que nos queda por examinar en esta plataforma de Shared Preferenceses la interfaz onSharedPreferenceChangeListener que es utilizada para invocar unevento cada vez que una preferencia es antildeadida eliminada o modificada Para registrarListeners usamos un coacutedigo como el que se muestra a continuacioacuten en el que la parte maacutesimportante es sin duda el meacutetodo onSharedPreferenceChanged dentro del cualdeberemos determinar queacute preferencia ha cambiado de estado a partir de sus paraacutemetrospara llevar a cabo las acciones oportunas

public class MiActividad extends Activity implementsOnSharedPreferenceChangeListener

Overridepublic void onCreate(Bundle SavedInstanceState)

Registramos este objetoOnSharedPreferenceChangeListener

Context contexto = getApplicationContext()SharedPreferences prefs =

PreferenceManagergetDefaultSharedPreferences(contexto)prefsregisterOnSharedPreferenceChangeListener(this)

public void onSharedPreferenceChanged(SharedPreferences prefsString clave)

PENDIENTE comprobar queacute clave concreta ha cambiado ysu nuevo valor

para modificar la interfaz de una actividad o elcomportamiento de

alguacuten componente

Otra forma maacutes sencilla de controlar los cambios en las preferencias es registrar unlistener en el constructor de la actividad principal o aquella que haga uso de los valores depreferencias de la forma

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto)

mListener = new OnSharedPreferenceChangeListener() public void onSharedPreferenceChanged(SharedPreferences

sharedPreferences String key)

if( keyequals(MICLAVE) )boolean valor =

sharedPreferencesgetBoolean(MICLAVE false)

prefsregisterOnSharedPreferenceChangeListener(mListener)

NotaEn este segundo ejemplo mListener es una variable miembro de la clase Para este caso esnecesario registrar el listener de esta forma porque sino el garbage collector lo eliminariacutea (debidoa particularidades en la programacioacuten de los listeners asociados a las SharedPreferences)

Persistencia

24Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

32 Proveedores de contenidos

Los proveedores de contenidos o ContentProvider proporcionan una interfaz parapublicar y consumir datos identificando la fuente de datos con una direccioacuten URI queempieza por content Son una forma maacutes estaacutendar que los adaptadores (como el quevimos en la sesioacuten anterior en la seccioacuten dedicada a SQLite) de desacoplar la capa deaplicacioacuten de la capa de datos

Podemos encontrar dos tipos principales de proveedores de contenidos en Android Losproveedores nativos son proveedores que el propio sistema nos proporciona para poderacceder a datos del dispostivo incluyendo audio viacutedeo imaacutegenes informacioacuten personaletc Por otra parte tambieacuten es posible que creemos nuestros propios proveedores con talde permitir a nuestra aplicacioacuten compartir datos con el resto del sistema En esta sesioacutenhablaremos de ambos tipos

321 Proveedores nativos

Android incluye una serie de proveedores de contenidos que nos pueden proporcionarinformacioacuten de diferentes tipos informacioacuten sobre nuestros contactos informacioacuten delcalendario e incluso ficheros multimedia Ejemplos de estos proveedores de contenidosnativos son el Browser CallLog ContactsContract MediaStore Settings y elUserDictionary Para poder hacer uso de estos proveedores de contenidos y acceder alos datos que nos proporcionan debemos antildeadir los permisos adecuados al ficheroAndroidManifestxml Por ejemplo para acceder al listiacuten telefoacutenico podemos antildeadir elpermiso correspondiente de la siguiente forma

ltuses-sdk androidminSdkVersion=8 gtltuses-permission androidname=androidpermissionREAD_CONTACTSgt

ltmanifestgt

Para obtener informacioacuten de un ContentProvider debemos hacer uso del meacutetodo query

de la clase ContentResolver El primero de los paraacutemetros de dicho meacutetodo seraacute la URIdel proveedor de contenidos al que deseamos acceder Este meacutetodo devuelve un cursorque nos permitiraacute iterar entre los resultados tal como se vio en la sesioacuten anterior en elapartado de cursores

ContentResolverquery(Uri uriString[] projectionString selectionString[] selectionArgsString sortOrder)

Por ejemplo la siguiente llamada nos proporcionaraacute un cursor que nos permitiraacute acceder atodos los contactos de nuestro teleacutefono moacutevil Para ello hemos hecho uso de la constante

Persistencia

25Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ContactsContractContactsCONTENT_URI que almacena la URI del proveedor decontenidos correspondiente Al resto de paraacutemetros se le ha asignado el valor null

ContentResolver cr = getContentResolver()Cursor cursor = crquery(ContactsContractContactsCONTENT_URI

null null null null)

NotaEn versiones anteriores a Android 20 las estructuras de datos de los contactos son diferentes y nose accede a esta URI

Una vez obtenido el cursor es posible mapear sus campos con alguacuten componente de lainterfaz graacutefica de la actividad De esta forma cualquier cambio que se produzca en elcursor se reflejaraacute automaacuteticamente en el componente graacutefico sin que sea necesario queprogramemos el coacutedigo que lo refresque Para esto tenemos que llamar al meacutetodosetNotificationUri del cursor con lo cual nos aseguraremos de que los listeners seraacutennotificados cuando se produzca alguacuten cambio en los datos referenciados por dicho cursor

cursorsetNotificationUri(crContactsContractContactsCONTENT_URI)

Para asignar un adaptador a una lista de la interfaz graacutefica de la actividad maacutesconcretamente una ListView utilizamos el meacutetodo setAdapter(Adapter)

ListView lv = (ListView)findViewById(RidListView01)SimpleCursorAdapter adapter = new SimpleCursorAdapter(

getApplicationContext()Rlayouttextviewlayoutcursornew String[]

ContactsContractContacts_IDContactsContractContactsDISPLAY_NAME

new int[]RidTextView1RidTextView2)

lvsetAdapter(adapter)

En este ejemplo los identificadores RidTextView1 y RidTextView2 se correspondencon views del layout que define cada fila de la ListView como se puede ver en elarchivo textviewlayoutxml que tendriacuteamos que crear

ltxml version=10 encoding=utf-8gt

ltLinearLayout xmlnsandroid=httpschemasandroidcomapkresandroidandroidorientation=horizontalandroidlayout_width=fill_parentandroidlayout_height=wrap_contentgt

ltTextView androidid=+idTextView1androidtextStyle=bold

Persistencia

26Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

androidems=2androidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltTextView androidid=+idTextView2androidtextStyle=boldandroidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltLinearLayoutgt

La lista en siacute (identificada por RidListView01 en el presente ejemplo) estariacutea en otroXML layout por ejemplo en mainxml

Se debe tener en cuenta que en el caso de este ejemplo si quisieacuteramos obtener losnuacutemeros de teleacutefono de cada uno de nuestros contactos tendriacuteamos que recorrer el cursorobtenido a partir del proveedor de contenidos y por cada persona en nuestra agenda crearun nuevo cursor que recorriera los teleacutefonos ya que una persona puede tener asignadosvarios teleacutefonos

Puedes acceder a un listado completo de los proveedores de contenidos nativos existentesen Android consultando su manual de desarrollo Concretamente leyendo la seccioacutendedicada al paquete androidprovider enhttpdeveloperandroidcomreferenceandroidproviderpackage-summaryhtml

322 Proveedores propios crear un nuevo proveedor de contenidos

Para acceder a nuestras fuentes de datos propias de manera estaacutendar nos interesaimplementar nuestros propios ContentProvider Gracias a que proporcionamos nuestrosdatos a partir de una URI quien use nuestro proveedor de contenidos no deberaacutepreocuparse de doacutende provienen los datos indicados por dicha URI podriacutean provenir deficheros locales en la tarjeta de memoria de un servidor en Internet o de una base dedatos SQLite Dada una URI nuestro proveedor de contenidos deberaacute proporcionar en suinterfaz los meacutetodos necesarios para realizar las operaciones baacutesicas en una base de datosinsercioacuten lectura actualizacioacuten y borrado

Para crear un nuevo proveedor de contenidos definimos una subclase deContentProvider Utilizaremos una sobrecarga del meacutetodo onCreate para crear einicializar la fuente de datos que queramos hacer puacuteblica a traveacutes de dicho proveedor Unesqueleto de subclase de ContentProvide podriacutea ser el siguiente

public class MiProveedor extends ContentProvider

Overridepublic boolean onCreate()

Inicializar la base de datosreturn true

Dentro de la clase deberemos crear un atributo puacuteblico y estaacutetico de nombreCONTENT_URI en el cual almacenaremos el URI completo del proveedor que estamos

Persistencia

27Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creando Este URI debe ser uacutenico por lo que una buena idea puede ser tomar como baseel nombre de paquete de nuestra aplicacioacuten La forma general de un URI es la siguiente

content[NOMBRE_PAQUETE]provider[NOMBRE_APLICACION][RUTA_DATOS]

Por ejemplo

contentesuajtechandroidprovidermiaplicacionelementos

En general (y por simplicidad) no es necesario antildeadir la cadena provider al nombre delpaquete por lo que podemos utilizar directamente el nombre del paquete seguido de laruta de datos de la forma

contentesuajtechandroidmiaplicacionelementos

Estos URIs se pueden presentar en dos formas La URI anterior representa una consultadirigida a obtener todos los valores de ese tipo (en este caso todos los elementos) Si a laURI anterior antildeadimos un sufijo [NUMERO_FILA] estamos representando una consultadestinada a obtener un uacutenico elemento (por ejemplo el quinto elemento en el siguienteejemplo)

contentesuajtechandroidprovidermiaplicacionelementos5

NotaSe considera una buena praacutectica de programacioacuten el proporcionar acceso a nuestro proveedor decontenidos utilizando cualquiera de estas dos formas

La forma maacutes simple de determinar cuaacutel de las dos formas anteriores de URI se utilizoacutepara hacer una peticioacuten a nuestro proveedor de contenidos es mediante un Uri MatcherCrearemos y configuramos un elemento Uri Matcher para analizar una URI y determinara cuaacutel de las dos formas se corresponde En el siguiente coacutedigo se muestra el coacutedigobaacutesico que deberemos emplear para implementar este patroacuten

public class MiProveedor extends ContentProvider

private static final String miURI =contentesuajtechandroidprovidermiaplicacionelementos

public static final Uri CONTENT_URI = Uriparse(miUri)

Creamos las constantes que nos van a permitir diferenciar entre los dos tipos distintos de peticiones a partir de la URIprivate static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

Inicializamos el objeto UriMatcher Una URI que termine en elementos se corresponderaacute con una consulta para todas las filas mientras que una URI con el sufijo elementos[FILA] representaraacute una uacutenica filastatic

uriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidprovidermiaplicacion

elementos TODAS_FILAS)

Persistencia

28Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

uriMatcheraddURI(esuajtechandroidprovidermiaplicacionelementos UNA_FILA)

Overridepublic boolean onCreate()

PENDIENTE inicializar la base de datosreturn true

Esta misma teacutecnica se puede emplear para proporcionar diferentes alternativas de URIpara diferentes subconjuntos de datos como por ejemplo diferentes tablas en una base dedatos a traveacutes del mismo proveedor de contenidos

323 Proveedores propios crear la interfaz de consultas

Para permitir realizar operaciones con los datos publicados a traveacutes de nuestro proveedorde contenidos deberemos implementar los meacutetodos delete insert update y query

para el borrado insercioacuten actualizacioacuten y realizacioacuten de consultas respectivamenteEstos meacutetodos son la interfaz estaacutendar utilizada con proveedores de contenidos de formaque para compartir datos entre aplicaciones no se tendraacuten interfaces distintos paradiferentes fuentes de datos

Lo maacutes habitual suele ser implementar un proveedor de contenidos como un mecanismopara permitir el acceso a una base de datos SQLite privada a una aplicacioacuten aunque atraveacutes de estos meacutetodos indicados se podriacutea en principio acceder a cualquier fuente dedatos

El siguiente coacutedigo extiende el ejemplo anterior (MiProveedor) y muestra el esqueleto deestos meacutetodos dentro de una subclase de ContentProvider Obseacutervese que se hace usodel objeto UriMatcher para determinar que tipo de consulta se estaacute realizando

Overridepublic Cursor query(Uri uri

String[] projectionString selectionString[] selectionArgsString sort)

Si se trata de una consulta para obtener una uacutenica filalimitamos

los resultados a obtener de la fuente de datos por medio de una claacuteusula whereswitch(UriMatchermatch(uri))

case UNA_FILA PENDIENTE modifica la sentencia SELECT

mediante una claacuteusula where obteniendo el identificador de fila

como numeroFila = urigetPathSegments()get(1))

return null

Override

Persistencia

29Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public Uri insert(Uri _uri ContentValues _valores) long idFila = [ Antildeadir un nuevo elemento ]

Devolvemos la URI del elemento recieacuten antildeadidoif (idFila gt 0)

return ContentUriswithAppendendId(CONTENT_URI idFila)

throw new SQLException(No se pudo antildeadir un nuevo elemento a +_uri)

Overridepublic int delete(Uri uri String where String[] whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

Overridepublic int update(Uri uri ContentValues valores String where String[]whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

324 Proveedores propios tipo MIME

El uacuteltimo paso para crear un proveedor de contenidos es definir el tipo MIME queidentifica el tipo de datos que devuelve el proveedor Debemos sobrecargar el meacutetodogetType para que devuelva una cadena que identifique nuestro tipo de datos de manerauacutenica Se deberiacutean definir dos tipos posibles uno para el caso de una uacutenica fila y otro parael caso de devolver todos los resultados Se debe utilizar una sintaxis similar a la delsiguiente ejemplo

bull Un uacutenico elementovndesuajtechandroidmiaplicacioncursoritemmiproveedorcontent

bull Todos los elementosvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

Como se puede observar la cadena comienza siempre por vnd A continuacioacutentendriacuteamos el nombre del paquete de nuestra aplicacioacuten Lo siguiente es cursor ya quenuestro proveedor de contenidos estaacute devolviendo datos de tipo Cursor Justo antes de labarra pondremos item en el caso del tipo MIME para un uacutenico elemento y dir para elcaso del tipo MIME para todos los elementos Finalmente tras la barra pondremos elnombre de nuestra clase (en minuacutesculas) seguido de content En el siguiente coacutedigo deejemplo se muestra coacutemo sobrecargar getType seguacuten la URI

Persistencia

30Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Overridepublic String getType(Uri _uri)

switch (uriMatchermatch(_uri)) case TODAS_FILAS

returnvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

case UNA_FILAreturn

vndesuajtechandroidmiaplicacioncursoritemmiproveedorcontentdefault

throw new IllegalArgumentException(URI nosoportada + _uri)

325 Proveedores propios registrar el proveedor

No debemos olvidar incorporar nuestro proveedor de contenidos al Manifest de laaplicacioacuten una vez que lo hemos completado Esto se haraacute por medio del elementoprovider cuyo atributo authorities podremos utilizar para especificar la URI base talcomo se puede ver en el siguiente ejemplo

ltprovider androidname=MiProveedorandroidauthorities=esuajtechandroidmiaplicaciongt

326 Content Resolvers

Aunque en la seccioacuten de proveedores nativos ya se ha dado alguacuten ejemplo de coacutemo haceruso de un Content Resolver para realizar una consulta ahora que hemos podido observaren detalle la estructura de un proveedor de contenidos propio es un buen momento paraver en detalle coacutemo realizar cada una de las operaciones que eacutestos permiten

El contexto de una aplicacioacuten incluye siempre una instancia de la claseContentResolver a la cual se puede acceder mediante el meacutetodo getContentResolver

ContentResolver cr = getContentResolver()

Esta clase incorpora diversos meacutetodos para realizar consultas a proveedores decontenidos Cada uno de estos meacutetodos acepta como paraacutemetro una URI que indica conqueacute proveedor de contenidos se desea interactuar

Las consultas realizadas sobre un proveedor de contenidos recuerdan a las consultasrealizadas sobre bases de datos Los resultados de las consultas se devuelven comoobjetos de la clase Cursor Se pueden extraer valores del cursor de la misma forma en laque se explicoacute en el apartado de bases de datos de la sesioacuten anterior El meacutetodo query dela clase ContentResolver acepta los siguientes paraacutemetros

bull La URI del proveedor del contenidos al que se desea realizar la consultabull Una proyeccioacuten que enumera las columnas que se quieren incluir en los resultados

obtenidosbull Una claacuteusula where que define las filas a obtener Se pueden utilizar comodines que

Persistencia

31Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

se reemplazaraacuten por valores incluidos en el siguiente paraacutemetrobull Una matriz de cadenas que se pueden utilizar para reemplazar los comodines en la

claacuteusula where anteriorbull Una cadena que describa la ordenacioacuten de los elementos devueltos

El siguiente coacutedigo muestra un ejemplo

ContentResolver cr = getContentResolver() Devolver todas las filasCursor todasFilas = crquery(MiProveedorCONTENT_URI null null nullnull) Devolver todas columnas de las filas para las que la columna 3 tiene un valor determinado ordenadas seguacuten el valor de la columna 5String where = COL3 + = + valorString orden = COL5Cursor algunasFilas = crquery(MiProveedorCONTENT_URI null where nullorden)

327 Otras operaciones con Content Resolvers

Para realizar otro tipo de operaciones con los datos de un proveedor de contenidosharemos uso de los meacutetodos delete update e insert de un objeto ContentResolver

Con respecto a la insercioacuten la clase ContentResolver proporciona en su interfaz dosmeacutetodos diferentes insert y bulkInsert Ambos aceptan como paraacutemetro la URI delproveedor de contenidos pero mientras que el primer meacutetodo tan solo toma comoparaacutemetro un objeto de la clase ContentValues (ya vistos en la seccioacuten de bases dedatos) el segundo toma como paraacutemetro una matriz de elementos de este tipo El meacutetodoinsert devolveraacute la URI del elemento recieacuten antildeadido mientras que bulkInsert

devolveraacute el nuacutemero de filas correctamente insertadas

Obtener el Content ResolverContentResolver cr = getContentResolver()

Crear una nueva fila de valores a insertarContentValues nuevosValores = new ContentValues()

Asignar valores a cada columnanuevosValoresput(NOMBRE_COLUMNA nuevoValor)[ Repetir para cada columna ]

Uri miFilaUri = crinsert(MiProveedorCONTENT_URI nuevosValores)

Insertamos ahora una matriz de nuevas filasContentValues[] arrayValores = new ContentValues[5] PENDIENTE rellenar el array con los valores correspondientesint count = crbulkInsert(MiProveedorCONTENT_URI arrayValores)

Para el caso del borrado hacemos uso del meacutetodo delete del Content Resolver pasandocomo paraacutemetro la URI de la fila que deseamos eliminar de la fuente de datos Otraposibilidad es incluir una claacuteusula where para eliminar muacuteltiples filas Ambas teacutecnicas semuestran en el siguiente ejemplo

ContentResolver cr = getContentResolver()

Persistencia

32Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Eliminar una fila especiacuteficacrdelete(miFilaUri null null) Eliminar las primeras cinco filasString where = _id lt 5crdelete(MiProveedorCONTENT_URI where null)

Una actualizacioacuten se lleva a cabo mediante el meacutetodo update del objeto ContentResolver Este meacutetodo toma como paraacutemetro la URI del proveedor de contenidosobjetivo un objeto de la clase ContentValues que empareja nombres de columna convalores actualizados y una claacuteusula where que indica queacute filas deben ser actualizadas Elresultado de la actualizacioacuten seraacute que en cada fila en la que se cumpla la condicioacuten de laclaacuteusula where se cambiaraacuten los valores de las columnas especificados por el objeto de laclase ContentValues tambieacuten se devolveraacute el nuacutemero de filas correctamenteactualizadas A continuacioacuten se muestra un ejemplo

ContentValues nuevosValores = new ContentValues()

Utilizamos el objeto ContentValues para especificar en queacute columnas queremos que se produzca un cambio y cuaacutel debe ser el nuevo valor de dichas columnasnuevosValuesput(NOMBRE_COLUMNA nuevoValor)

Aplicamos los cambios a las primeras cinco filasString where = _id lt 5

getContentResolver()update(MiProveedorCONTENT_URI nuevosValores wherenull)

Persistencia

33Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

4 Ejercicios - Persistencia en Android proveedores de contenidos ySharedPreferences

41 Compartir datos entre actividades con Shared Preferences (075 puntos)

Descarga de las plantillas el proyecto Dni Dicho proyecto estaacute compuesto de dosactividades Formulario y Resumen El objetivo del ejercicio es conseguir que al pulsar elbotoacuten Siguiente en la actividad Formulario se muestre en la actividad Resumen cuaacutelesfueron los datos introducidos

En este ejercicio vamos a hacer uso de SharedPreferences para pasar los datos de unaactividad a la siguiente Al pulsar en Siguiente en Formulario deberemos guardar el valorde todos los campos mediante esta plataforma Al mostrarse la actividad Resumendeberemos leer estos datos tambieacuten a partir de SharedPreferences para mostrarlos porpantalla

Por uacuteltimo crea un meacutetodo que valide el DNI (debe tratarse de una secuencia de ochodiacutegitos entre 0 y 9 y una letra al final) Si se pulsa Siguiente y el DNI no tiene el formatocorrecto no se deberaacute pasar a la actividad Resumen En lugar de esto se mostraraacute unToast en pantalla con un mensaje de error

42 Actividad de preferencias (075 puntos)

En este ejercicio seguimos trabajando con el proyecto del ejercicio anterior Vamos aantildeadir un menuacute de opciones que permita escoger en queacute momento se comprobaraacute lavalidez del DNI Para ello antildeade en primer lugar un menuacute a la actividad Formulario conuna uacutenica opcioacuten cuyo nombre seraacute Opciones

Al seleccionar esta opcioacuten se deberaacute mostrar una actividad de preferencias cuyo aspectoseraacute el siguiente

Persistencia

34Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Preferencias de la aplicacioacuten Dni

El significado de las opciones seraacute el siguiente

bull En el campo con esta opcioacuten seleccionada al pulsar sobre el campo de DNI en elformulario deberaacute mostrarse un Toast indicando si la sintaxis del DNI es correcta ono

bull Al pulsar si se desactiva esta opcioacuten (que deberaacute estar activada por defecto) no seharaacute la comprobacioacuten del DNI al pulsar el botoacuten Siguiente por lo que siempre seraacuteposible pasar de la actividad Formulario a la actividad Resumen sea cual sea elformato del DNI introducido

bull El uacuteltimo check box se encontraraacute siempre deshabilitado iquestQueacute atributo debemosutilizar para conseguir esto

Crea dos variables booleanas llamadas enElCampo y alPulsar Su valor dependeraacute delestado de los checkboxes anteriores y se actualizaraacute por medio del eventoonSharedPreferenceChange Utiliza el valor de estas variables en el coacutedigo de tuactividad para que esta tenga el comportamiento indicado

43 Proveedor de contenidos propio (075 puntos)

Vamos a implementar otra forma de acceder a la base de datos de usuarios de laaplicacioacuten BaseDatos desarrollada durante los ejercicios de la sesioacuten anterior siguiendoesta vez el patroacuten de disentildeo ContentProvider de Android Seguiremos trabajando puescon dicho proyecto

bull Creamos una nueva clase llamada UsuariosProvider que herede deContentProvider Esto nos obligaraacute a sobrecargar una serie de meacutetodos abstractosAntes de implementar la query vamos a configurar el provider

bull Antildeadimos algunos campos tiacutepicos de los content provider

Persistencia

35Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public static final Uri CONTENT_URI =Uriparse(contentesuajtechandroidbasedatosusuarios)

private static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

staticuriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

TODAS_FILAS)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

UNA_FILA)

bull Vamos a acceder a la misma base de datos de usuarios utilizada en los ejercicios de lasesioacuten anterior pero no vamos a hacerlo a traveacutes del adaptador que tuvimos queimplementar sino que vamos a copiar de eacutel el coacutedigo que nos haga falta Copia loscampos que definen el nombre de la base de datos de la tabla de las columnas laversioacuten asiacute como la referencia al contexto y a la base de datos La sentenciacompilada del insert ya no va a hacer falta Inicializa los valores necesarios en elconstructor Para inicializar la referencia a la base de datos vamos a utilizar una vezmaacutes MiOpenHelper Podemos copiarlo del adaptador que ya implementamos en losejercicios de la sesioacuten anterior

bull Implementa de forma apropiada el getType para devolver un tipo MIME diferenteseguacuten si se trata de una URI de una fila o de todas las filas Para ello ayuacutedate deluriMatcher

bull Implementa el meacutetodo query Simplemente se trata de devolver el cursor queobtenemos al hacer una consulta a la base de datos SQLite Algunos de los paraacutemetrosque le pasaremos los recibimos como paraacutemetros del meacutetodo del provider Los que notengamos iraacuten con valor null

bull Aunque no tenemos todos los meacutetodos del UsuariosProvider implementadospodemos probarlo Para ello debemos registarlo en el AndroidManifestxml

ltprovider androidname=UsuariosProvider

androidauthorities=esuajtechandroidbasedatosgtltapplicationgt

ltmanifestgt

bull En la clase Main se antildeadioacute codigo para insertar una serie de valores en la base dedatos y mostrarlos en el campo de texto Manteniendo este coacutedigo vamos a antildeadir alcampo de texto el resultado obtenido con la consulta del UsuariosProvider paracomprobar que tanto el adaptador de la base de datos como el proveedor nosdevuelven el mismo resultado

44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)

Porque es la forma estaacutendar que establece Android de acceder a contenidos Ademaacutes elproveedor de contenidos nos permitiraacute notificar al ContentResolver de los cambiosocurridos Asiacute componentes en la pantalla podraacuten refrescarse de forma automaacutetica

Persistencia

36Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Utiliza el proyecto ProveedorContenidos de las plantillas Implementa la insercioacuten enel proveedor de contenidos Prueacutebala insertando algunos usuarios de ejemplo en laclase Main Implementa tambieacuten el OnClickListener del botoacuten que inserta nuevosusuarios El nombre del nuevo usuario iraacute indicado en el EditText

bull Comprueba que la insercioacuten funciona y que gracias a la siguiente liacutenea y al estarusando un proveedor de contenidos la lista se actualiza automaacuteticamente cuandoocurre alguacuten cambio sin necesidad de pedir expliacutecitamente la actualizacioacuten al pulsarel botoacuten

cursorsetNotificationUri(cr UsuariosProviderCONTENT_URI)

NotaEsto no va a funcionar si se notifica que se ha producido un cambio al insertar o borrar unelemento Para ello usamosgetContext()getContentResolver()notifyChange(UsuariosProviderCONTENT_URI null)

bull Implementa el meacutetodo delete del proveedor de contenidos Prueacutebalo en la claseMain Termina de implementar el onCreateContextMenuListener que se ejecutaraacutecada vez que se haga una pulsacioacuten larga sobre alguna entrada de la lista Compruebaque funciona (eliminando el usuario correspondiente y no otro)

Persistencia

37Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

5 Persistencia de datos en iOS Ficheros y SQLite

51 Introduccioacuten

La persistencia de datos en iOS se realiza en una pequentildea memoria flash que tienenuestro dispositivo la cual es equivalente a un disco duro de tamantildeo limitado Los datosque ahiacute se almacenen se conservaraacuten aunque el dispositivo se apague Por motivos deseguridad el SO de iOS no permite que las aplicaciones accedan directamente a lamemoria interna pero a cambio existe una API completa de acceso a la porcioacuten dememoria que le corresponde a la app que desarrollemos

En una aplicacioacuten iOS encontraremos baacutesicamente 4 modos de almacenamiento deinformacioacuten de forma persistente

bull Ficheros de propiedades (plist)bull Bases de datos embebidas SQLitebull Datos de usuario (User Defaults)bull Core Data

En esta primera sesioacuten veremos coacutemo usar los ficheros de texto (Property Lists) y elalmacenamiento en base de datos de tipo SQLite

52 Ficheros plist (Property Lists)

El almacenamiento de datos en ficheros de propiedades es uno de los maacutes usados en eldesarrollo de las aplicaciones para iOS Se usa principalmente para guardar datos deconfiguracioacuten de la aplicacioacuten u otra informacioacuten poco compleja y acceder a ella deforma sencilla Si estamos desarrollando un juego podemos usarlos para la definicioacuten delos niveles almacenar las puntuaciones del jugador etc

521 Leyendo datos desde ficheros plist

Los ficheros plist (Property Lists) tienen un formato XML aunque XCode nosproporciona una interfaz que nos permite acceder a ellos de forma muy sencilla Paraentender el funcionamiento de este tipo de ficheros vamos a realizar un ejemplo en el queprimero crearemos un fichero plist lo completaremos con datos de ejemplo y despueacutes loleeremos para mostrarlo por pantalla

Un ejemplo de un fichero plist podriacutea ser el siguiente

Persistencia

38Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Fichero plist

Antes de nada creamos un proyecto nuevo en XCode de tipo Window-based

Application al que llamaremos sesion03-ejemplo1 Para crear un fichero plistdebemos hacer click derecho sobre el directorio Supporting Files de nuestro proyectoy seleccionamos iOS gt Resource gt Property List Lo guardaremos con el nombreconfigUsuario Seguidamente ya podemos acceder al fichero recieacuten creado haciendoclick sobre el en este momento XCode nos mostraraacute un pequentildeo editor totalmente vacioen el que iremos rellenando con datos Podemos verlo tambieacuten en formato XML sihacemos click derecho sobre el y seleccionamos Open As gt Preview

Para empezar a rellenar el fichero que acabamos de crear hacemos click derecho dentrode su contenido (ahora vacio) y seleccionamos Add row Entonces nos apareceraacute unafila vaciacutea Dentro del campo Key escribimos la clave un nombre descriptivo quedeberemos utilizar maacutes adelante para acceder a los datos En nuestro caso vamos aescribir config como clave Dentro de la columna Type seleccionamosDictionary Veremos que aparece una flecha en el lado izquierdo que indica quepueden haber subniveles dentro de nuestro diccionario

Ahora antildeadimos un nuevo item que cuelgue del diccionario que acabamos de crear paraello hacemos click sobre la flecha de la izquierda esta se giraraacute hacia abajo entonceshacemos click ahora sobre el siacutembolo + (maacutes) Nos apareceraacute una nueva liacutenea que cuelgadel diccionario

En esta nueva liacutenea dentro del campo key escribimos nombre el campo type lodejamos como de tipo String ya que va a ser una cadena de texto y en la columna deValue escribimos nuestro nombre por ejemplo Javi

Ahora vamos a antildeadir otro campo nuevo en nuestra configuracioacuten para ello hacemosclick sobre el siacutembolo + y nos apareceraacute una nueva liacutenea justo al mismo nivel que laanterior y colgando del diccionario Dentro del campo de Key escribimos ciudad yen Value escribimos Alicante El campo de Type lo dejamos como String

Persistencia

39Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora dentro de nuestro fichero plist vamos a indicar los dispositivos que dispone elusuario para ello creamos una nueva linea dentro del diccionario y le pondremos comoclave dispositivos Seleccionamos el tipo Array y seguimos los mismos pasosque al crear el diccionario para ello hacemos click sobre la flecha de la izquierda ydespueacutes sobre el siacutembolo + (maacutes) Ahora nos apareceraacute una nueva fila con clave Item 0Seleccionamos el tipo String y en el campo Value iPhone

Ahora repetimos el paso 3 veces indicando como values iPad Android y

Blackberry

Una vez seguidos todos estos pasos tendremos nuestro fichero de propiedades listo Debede quedar como se muestra a continuacioacuten

Fichero plist terminado

Este fichero se almacenaraacute dentro del directorio de bundle cuando compilemos laaplicacioacuten En el caso de que queramos escribir en el debermos almacenar el ficherodentro del directorio de Documents del binario

Ahora desde nuestro coacutedigo vamos a acceder a eacutel y a mostrarlo por pantalla para elloescribimos el siguiente coacutedigo dentro del meacutetodo didFinishLaunchingWithOptions dela clase delegada

Cargamos el fichero PLISTNSString mainBundlePath = [[NSBundle mainBundle] bundlePath]NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]NSDictionary diccionario = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionario delraiz

NSString nombre = [dicConfig objectForKeynombre]

Persistencia

40Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

[selfwindow makeKeyAndVisible]return YES

Si todo ha ido correctamente deberemos obtener la siguiente salida por la consola

Lista de dispositivos

522 Escribiendo datos en ficheros plist

Partiendo del ejemplo anterior vamos a escribir ahora en el fichero para ello primerodebemos comprobar que el fichero plist se encuentre dentro del directorio de Documents

del dispositivo en el caso de que no lo esteacute (por ejemplo si es la primera vez que arrancala aplicacioacuten) debemos copiarlo a ese directorio Una vez que tenemos el fichero dentrodel directorio de documentos ya podemos escribir en el

Para realizar todo el proceso de comprobacioacuten de la existencia del fichero dentro deldirectorio de documentos copiarlo en ese directorio y leer los datos debemos de sustituirel coacutedigo que hay dentro del meacutetodo didFinishLaunchingWithOptions de la clasedelegada por este otro

AtencioacutenPara que funcione la escritura en un fichero plist este debe de estar dentro del directorio deDocuments de nuestro dispositivo iOS no en el boundle

NSDictionary diccionarioRaiz

Ruta del directorio de documentos de nuestro dispositivoNSArray paths =

NSSearchPathForDirectoriesInDomains(NSDocumentDirectoryNSUserDomainMask YES)

if ([paths count] gt 0)

Persistencia

41Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

Primero comprobamos si existe el fichero en eldirectorio de documentos

BOOL fileExists = [[NSFileManager defaultManager]fileExistsAtPathdocumentsFilename]if (fileExists)

NSLog(Fichero encontrado en directorio de documentosOK

documentsFilename)

Si existe ya cargamos los datos desde aquidirectamente

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

else

NSLog(No se encuentra el fichero configUsuarioplisten

el directorio de documentos-gtLo creamos)

Si no existe primero cargamos el fichero PLISTdesde el Boundle

NSString mainBundlePath = [[NSBundle mainBundle]bundlePath]

NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Y despues escribimos los datos cargados (eldiccionario completo)

a un fichero nuevo de la carpeta de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

NSLog(plist de configUsuario creado)

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionariodel raiz

NSString nombre = [dicConfig objectForKeynombre]NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

Persistencia

42Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Por ultimo liberamos la memoria del diccionario raiz[diccionarioRaiz release]

[selfwindow makeKeyAndVisible]return YES

Para comprobar el funcionamiento arrancamos la aplicacioacuten y veremos que la primera vezse crearaacute el fichero dentro de la carpeta de Documents de nuestro dispositivo y en lassiguientes ya no se crearaacute y simplemente lo leeraacute desde la carpeta de Documents

Ahora que ya tenemos la gestioacuten de ficheros baacutesica completada vamos a escribir sobre elalguacuten dato Para ello simplemente escribimos el siguiente fragmento de coacutedigo justo antesde la liacutenea [selfwindow makeKeyAndVisible]

Cambiamos el nombre y lo guardamos en el fichero Ruta del directorio de documentos de nuestro dispositivo

if ([paths count] gt 0)

Cargamos el fichero desde el directorio dedocumentos del dispositivo

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cambiamos el valor del nombre en el diccionario[dicConfig setValueOtro nombre forKeynombre]

Escribimos todo el diccionario de nuevo al fichero del directorio de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

Por ultimo liberamos el diccionario de lamemoria

[diccionarioRaiz release]

Lo que hemos hecho en el coacutedigo anterior es cargar todo el diccionario de nuevo desde eldirectorio de documentos modificar los datos que queremos cambiar o incluso antildeadir ydespueacutes guardar el diccionario en el fichero

Volvemos a arrancar la aplicacioacuten y veremos que ha cambiado el campo de nombre

Nota

Persistencia

43Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

La lectura y escritura de datos en un fichero plist se debe de hacer de una soacutela vez nunca porpartes Siempre se debe de cargar o escribir un diccionario NSDictionary como elementoprincipal del fichero

53 SQLite

SQLite es una base de datos relacional ligera con la que se puede trabajar de formasencilla mediante sentencias SQL La base de datos se almacenaraacute en un fichero dentrode nuestra aplicacioacuten Este meacutetodo de persistencia puede ser el adecuado cuando los datosa almacenar esteacuten en un formato de tablas filas y columnas y se necesite que los datossean accesibles de forma raacutepida

531 Herramientas disponibles

Para explicar el funcionamiento de las librerias de SQLite de Cocoa Touch vamos a seguirun ejemplo completo Comenzamos creando la base de datos para ello podemos utilizaruno de los frontends que facilitan la gestioacuten del SQLite uno bastante sencillo yrecomendado es el SQLite Database Browser y otro es fmdb

Nosotros utilizaremos el primero (SQLite Database Browser) Abrimos la aplicacioacuten yhacemos click sobre el botoacuten de crear nueva base de datos que llamaremospersonasDBsqlite Seguidamente creamos las tablas para simplificar en nuestroejemplo crearemos una soacutela tabla llamada personas Esta tabla tendraacute las siguientescolumnas id (NUMERIC) nombre (TEXT) apellidos (TEXT) dni (TEXT) edad

(NUMERIC) y localidad (TEXT)

Persistencia

44Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Estructura de la Base de Datos

Una vez creada la tabla pasamos a insertar datos dentro de ella para ello abrimos lapestantildea de Browse Data y hacemos click en New Record Cuando tengamos al menos 3registros metidos dentro de la tabla ya podemos guardar la base de datos Hacemos clicksobre guardar le ponemos el nombre que queramos y elegimos cualquier directorio delMAC

Datos de la Base de Datos

532 Lectura de datos

Una vez que tenemos la base de datos SQLite creada ya podemos empezar a escribir elcoacutedigo para leer y mostrar los datos dentro de nuestra aplicacioacuten

Primero empezamos creando un proyecto nuevo en XCode usando la plantilla basada envistas View-based application y lo llamaremos sesion03-ejemplo2 Ahora antildeadimosel framework de sqlite a nuestro proyecto para ello hacemos click sobre el raiz delproyecto y seleccionamos la pestantildea Build Phases Desplegamos el listado de Link

Binary With Libraries y hacemos click sobre el botoacuten (+) El el listadoseleccionamos el framework libsqlite3dylib y hacemos click en Add

Persistencia

45Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Antildeadiendo el framework libsqlite3dylib

Ahora antildeadimos a nuestro proyecto la base de datos que hemos creado anteriormentepara ello arrastramos el fichero personasDBsqlite a nuestro directorio Supporting

Files Asegurate de seleccionar la opcioacuten de Copy items to destination groups

folder (if needed) para que se copie el fichero dentro de la carpeta del proyecto

Para acelerar el proceso de lectura de la base de datos y ahorrar memoria vamos a cargarprimero todos los datos en objetos para ello vamos a crear una clase que se encargue dealmacenar los datos Hacemos click derecho sobre el raiz del proyecto New file gtObjective-C class seleccionamos NSObject como clase principal y le damos a NextEscribimos como nombre de la clase Persona y hacemos click en save

Personah

imports iniciales

interface Persona NSObject int _uIdNSString _nombreNSString _apellidos

Persistencia

46Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSInteger _edadNSString _localidad

property (nonatomic assign) int uIdproperty (nonatomic copy) NSString nombreproperty (nonatomic copy) NSString apellidosproperty (nonatomic) NSInteger edadproperty (nonatomic copy) NSString localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidos edad(NSInteger)edadlocalidad(NSString )localidad

end

Personam

import Personah

implementation Persona

synthesize uId = _uIdsynthesize nombre = _nombresynthesize apellidos = _apellidossynthesize edad = _edadsynthesize localidad = _localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidosedad(NSInteger)edad localidad(NSString )localidad

if ((self = [super init])) selfuId = uIdselfnombre = nombreselfapellidos = apellidosselfedad = edadselflocalidad = localidad

return self

- (void) dealloc selfnombre = nilselfapellidos = nilselflocalidad = nil[super dealloc]

end

Una vez creada la clase Persona vamos a crear la clase encargada de manejar la basede datos sqlite3 Otra opcioacuten seriacutea cargar todos los datos directamente desde la clasedelegada al arrancar la aplicacioacuten pero no es recomendable ya que si en alguacuten momentotenemos que cambiar de motor de base de datos lo tendremos maacutes complicado

Persistencia

47Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creamos entonces una clase dentro de nuestro proyecto llamada BDPersistencia queherede de NSObject y escribimos el siguiente coacutedigo

BDPersistenciah

imports iniciales

interface BDPersistencia NSObject sqlite3 _database

+ (BDPersistencia)database- (NSArray )arrayPersonas

end

BDPersistenciam

import BDPersistenciahimport Personah

implementation BDPersistencia

static BDPersistencia _database

+ (BDPersistencia)database if (_database == nil)

_database = [[BDPersistencia alloc] init]return _database

- (id)init if ((self = [super init]))

NSString sqLiteDb = [[NSBundle mainBundle]pathForResourcepersonasDB

ofTypesqlite]

if (sqlite3_open([sqLiteDb UTF8String]amp_database) = SQLITE_OK)

NSLog(Fallo al abrir la BD)

return self

- (void)dealloc sqlite3_close(_database)[super dealloc]

- (NSArray )arrayPersonas

NSMutableArray arraySalida = [[[NSMutableArray alloc]init]

autorelease]

Persistencia

48Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString query = SELECT id nombre apellidoslocalidad edad

FROM personas ORDER BY apellidos DESC

sqlite3_stmt statementif (sqlite3_prepare_v2(_database [query UTF8String]

-1ampstatement nil)

== SQLITE_OK) while (sqlite3_step(statement) == SQLITE_ROW)

int uniqueId = sqlite3_column_int(statement0)

char nombreChar = (char )sqlite3_column_text(statement 1)

char apellidosChar = (char )sqlite3_column_text(statement 2)

char localidadChar = (char )sqlite3_column_text(statement 3)

int edad = sqlite3_column_int(statement 4)

NSString nombre = [[NSString alloc]initWithUTF8StringnombreChar]NSString apellidos = [[NSString alloc]initWithUTF8StringapellidosChar]NSString localidad = [[NSString alloc]initWithUTF8StringlocalidadChar]Persona persona = [[Persona alloc]initWithUiduniqueId nombrenombreapellidosapellidos edadedad

localidadlocalidad]

[arraySalida addObjectpersona][nombre release][apellidos release][localidad release][persona release]

sqlite3_finalize(statement)

return arraySalida

end

Con esto ya podemos leer de nuestra base de datos las personas Para ver que funcionatodo a la perfeccioacuten escribimos el siguiente coacutedigo dentro de la clase delegada en elmeacutetodo applicationDidFinishLaunching Antes debemos incluir en la parte superiorlas clases creadas

import BDPersistenciahimport Personah

NSArray personas = [BDPersistenciadatabase]arrayPersonas

for (Persona persona in personas) NSLog(d d personauId

personanombrepersonaapellidos personalocalidad personaedad)

Persistencia

49Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Si ejecutamos el proyecto obtendremos la siguiente salida por consola

Listado de personas

533 Mostrando los datos en una tabla

Una vez que tenemos el coacutedigo para obtener todas las filas de la tabla de personas de labase de datos queda lo maacutes faacutecil mostrarlas dentro de una vista de la aplicacioacuten En estecaso utilizaremos la vista de tabla UITableView

Comenzamos creando una nueva vista en nuestro proyecto para ello hacemos clickderecho sobre el raiz y seleccionamos New File gt UIVIewController SubclassAsegurate de marcar Subclass of UITableViewController y la opcoacuten de With

XIB for user interface Hacemos click en Next y guardamos el fichero comoPersonasViewController por ejemplo

Ahora abrimos el fichero PersonasViewControllerh y antildeadimos un NSArray queseraacute el que almacene las personas de la base de datos

imports iniciales

interface PersonasViewController UITableViewController

NSArray _listadoPersonas

property (nonatomic retain) NSArray listadoPersonas

end

Ahora abrimos el fichero PersonasViewControllerm antildeadimos el syntetizeimportamos los ficheros de gestioacuten de la base de datos la clase Persona y todo el coacutedigobaacutesico para la gestioacuten de la vista de la tabla

import PersonasViewControllerhimport Personahimport BDPersistenciah

implementation PersonasViewController

synthesize listadoPersonas = _listadoPersonas

Persistencia

50Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

- (id)initWithStyle(UITableViewStyle)style

self = [super initWithStylestyle]if (self)

Custom initializationreturn self

- (void)dealloc

[super dealloc]

selflistadoPersonas = nil

- (void)didReceiveMemoryWarning

Releases the view if it doesnt have a superview[super didReceiveMemoryWarning]

Release any cached data images etc that arent inuse

pragma mark - View lifecycle

- (void)viewDidLoad

[super viewDidLoad]

selftitle = Listado de personas

selflistadoPersonas = [BDPersistenciadatabase]arrayPersonas

- (void)viewDidUnload

[super viewDidUnload] Release any retained subviews of the main view eg selfmyOutlet = nil

- (void)viewWillAppear(BOOL)animated

[super viewWillAppearanimated]

- (void)viewDidAppear(BOOL)animated

[super viewDidAppearanimated]

- (void)viewWillDisappear(BOOL)animated

[super viewWillDisappearanimated]

- (void)viewDidDisappear(BOOL)animated

[super viewDidDisappearanimated]

- (BOOL)shouldAutorotateToInterfaceOrientation

Persistencia

51Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

(UIInterfaceOrientation)interfaceOrientation

Return YES for supported orientationsreturn (interfaceOrientation ==

UIInterfaceOrientationPortrait)

pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView(UITableView)tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection

(NSInteger)section

Return the number of rows in the sectionreturn [selflistadoPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath

(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]if (cell == nil)

cell = [[[UITableViewCell alloc]initWithStyleUITableViewCellStyleSubtitle

reuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona persona = (Persona )[selflistadoPersonas

objectAtIndexindexPathrow]celltextLabeltext = [NSString stringWithFormat

(d) personanombrepersonaapellidos personaedad]celldetailTextLabeltext = personalocalidad

return cell

pragma mark - Table view delegate

- (void)tableView(UITableView )tableViewdidSelectRowAtIndexPath

(NSIndexPath )indexPath

Navigation logic may go here Create and pushanother view controller

end

Persistencia

52Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora soacutelo nos queda incorporar la vista que acabamos de crear dentro de nuestraaplicacioacuten Para ello vamos a llamarla desde el Delegate y a meterla dentro de unNavigation Controller Primero debemos de antildeadir un Outlet en la clasedelegada hacia un Navigation Controller

imports iniciales

interface sesion03_ejemplo1AppDelegate NSObjectltUIApplicationDelegategt

UIWindow _windowUINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet

UINavigationController navController

end

Ahora hacemos click sobre la vista MainWindowxib y arrastramos un Navigation

Controller desde el listado de la columna de la derecha hacia la columna de laizquierda donde pone Objects Nos apareceraacute el Navigation Controller dibujadoen pantalla Ahora desplegamos el Navigation Controller y hacemos click sobre elView Controller que hay dentro Nos vamos a la pestantildea de Identity Inspector

de la columna de la derecha y escribimos dentro de Class el nombre de la vista dellistado de personas PersonasViewController

Finalmente hacemos click sobre el objeto Delegate vamos a la pestantildea de Show

Connections Inspector y arrastramos desde navController hasta el objetoNavigation Controller de la columna de la izquierda Ya tenemos los controladoresy las vistas conectadas

Una vez hecho esto nos queda indicar dentro de nuestro coacutedigo que la aplicacioacuten arranquecon el Navigation Controller para ello escribimos lo siguiente al final del meacutetododidFinishLaunchingWithOptions de la implementacioacuten de la clase delegada

selfwindowrootViewController = selfnavController[selfwindow makeKeyAndVisible]return YES

Ya estaacute listo todo y preparado para compilar Si ejecutamos el proyecto veremos que nosaparece el listado de personas en la vista de tabla

Persistencia

53Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios

61 Creando y leyendo un fichero plist (15 puntos)

En este primer ejercicio vamos a crear nuestra primera aplicacioacuten usando el meacutetodo depersistencia de ficheros La aplicacioacuten mostraraacute en una vista de tabla TableView unlistado de series en la que si seleccionamos una veremos su informacioacuten maacutes importanteen otra vista Comenzaremos creando un fichero de propiedades (plist) siguiendo unaestructura determinada y posteriormente mostraremos en una vista de tabla todos susdatos iexclComenzamos

1) Crear un proyecto usando la plantilla Master-Detail application Lo llamaremosejercicio-plist-ios como identificador de empresa escribiremos esuajtech y como prefijode clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremos UseAutomatic Reference Counting para olvidarnos de la gestioacuten de memoria El resto deopciones las dejaremos sin marcar

2) Crear un archivo de propiedades (plist) con el nombre de series

3) Editar el archivo plist creando la siguiente estructura de datos (5 series miacutenimo)

Plist series

4) Rellenar el archivo plist con datos de series reales

5) Escribir el coacutedigo necesario en UAMasterViewControllerm que recorra el archivoplist que acabamos de crear y muestre por consola los titulos de las series

6) Mostrar en la tabla que hay implementada los tiacutetulos de la serie Para ello deberemosde completar todos los meacutetodos del protocolo del TableView

7) Ejecutar la aplicacioacuten y comprobar que funciona correctamente

Persistencia

54Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

62 Implementar vista de detalle de la serie (05 puntos)

Dentro del proyecto anterior (no hace falta crear uno nuevo) vamos a mostrar toda lainformacioacuten adicional de cada una de las series en una vista de detalle Cuandoseleccionemos una fila de la tabla nos deberaacute aparecer una nueva vista con el resto deinformacioacuten (antildeo director principal sinopsis y duracioacuten)

63 Uso baacutesico de una base de datos SQLite (1 punto)

En este ejercicio vamos a crear la misma aplicacioacuten que en el ejercicio anterior perousando el meacutetodo de persistencia de SQLite Para completar el ejercicio deberemos seguirlos siguientes pasos

1) Crear un proyecto usando la plantilla Master-Detail application lo llamaremosejercicio-sqlite-ios como identificador de empresa escribiremos esuajtech y comoprefijo de clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremosUse Automatic Reference Counting para olvidarnos de la gestioacuten de memoria El restode opciones las dejaremos sin marcar

2) Nos descargamos e instalamos el programa SQLite Database Browser desde estadireccioacuten

3) Disentildear la estructura de la base de datos usando SQLite Database Browser tal y comose muestra en la imagen siguiente

Sqlite series

4) Insertar al menos 5 series en la base de datos usando el programa SQLite DatabaseBrowser

5) Cargar todos los datos de la base de datos desde el meacutetododidFinishLaunchingWithOptions de la clase UAAppDelegate Mostrar por consola losdatos cargados

6) Mostrar los datos cargados en el paso anterior en la vista de tabla UITableView quehay creada en la clase UAMasterViewController

Persistencia

55Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7) Arrancar la aplicacioacuten y comprobar que se muestran las series en la tabla de formacorrecta

Persistencia

56Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7 Persistencia de datos en iOS User Defaults y Core Data

71 Introduccioacuten

En esta sesioacuten veremos como utilizar dos nuevos meacutetodos de persistencia en iOS ambosmuy interesantes y usados muy frecuentemente

bull User Defaults Muy uacutetil para almacenar datos simples y de uso repetido dentro denuestras aplicaciones iOS

bull Core Data Meacutetodo avanzado de almacenamiento de objetos enteros dentro de lamemoria del dispositivo iOS Muy uacutetil para almacenar datos complejos conrelaciones etc

72 User Defaults

Este meacutetodo es muy uacutetil cuando queremos almacenar pequentildeas cantidades de datos comopuntuaciones informacioacuten de usuario el estado de nuestra aplicacioacuten configuracionesetc

Este meacutetodo es el maacutes raacutepido de usar ya que no requiere de una base de datos ni ficherosni ninguna otra capa intermedia Los datos se almacenan directamente en la memoria deldispositivo dentro de un objeto de la clase NSUserDefaults

Para aprender a usar este meacutetodo de almacenamiento de datos vamos a crear unaaplicacioacuten muy simple que lea un nombre y una edad de pantalla y las guarde en unobjeto NSUserDefaults Para ello seguimos los siguientes pasos

bull 1) Comenzamos creando un nuevo proyecto llamado sesion04-ejemplo1 dentrode XCode

bull 2) Abrimos la vista sesion04_ejemplo1ViewController y arrastramos dos TextField un botoacuten y dos labels quedando la vista de la siguiente manera

Persistencia

57Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista de la aplicacioacuten

bull 3) Abrimos el fichero sesion04_ejemplo1ViewControllerh y definimos el Outletdel evento onclick del botoacuten El fichero quedaraacute de la siguiente manera

imports iniciales

interface sesion04_ejemplo1ViewController UIViewController UITextField tfNombreUITextField tfEdad

property(nonatomic retain) IBOutlet UITextField tfNombreproperty(nonatomic retain) IBOutlet UITextField tfEdad

-(IBAction) guardaDatos

end

bull 4) Ahora implementamos el coacutedigo para el meacutetodo del botoacuten dentro desesion04_ejemplo1ViewControllerm

synthesize tfEdadsynthesize tfNombre

-(IBAction) guardaDatos NSLog(Guardamos los datos)

Persistencia

58Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creamos el objeto NSUserDefaultsNSUserDefaults datos = [NSUserDefaults standardUserDefaults]

guardamos el nombre (NSString)[datos setObjecttfNombretext forKeynombre]

guardamos la edad (Integer)[datos setInteger[tfEdadtext integerValue] forKeyedad]

almacenamos los datos en la memoria[datos synchronize]

bull 5) Tenemos que enlazar el botoacuten con el evento que hemos programado en el pasoanterior para ello abrimos la vista sesion04_ejemplo1ViewControllerxib y conla tecla ctlr apretada arrastramos desde el botoacuten hasta Files Owner (columna de laizquierda parte superior) y seleccionamos el meacutetodo que hemos creado(guardaDatos) De esta forma ya tenemos conectado el botoacuten

bull 6) Ahora hacemos lo mismo con los Text Field Tenemos que conectarlos alcontrolador para ello seleccionamos el objeto Files Owner y abrimos la pestantildeade conexiones (la que estaacute maacutes a la derecha en la columna de la derecha) Hacemosclick en tfEdad y arrastramos hasta el Text Field de edad Hacemos lo mismo para eltext field de nombre Ya tenemos todos los elementos de la vista conectados

Conexioacuten de Outlets

bull 7) Arrancamos la aplicacioacuten y la probamos

Persistencia

59Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Aplicacioacuten

bull 8) Ahora vamos a hacer que la aplicacioacuten cargue los datos que se han guardado alarrancar la vista para ello simplemente tenemos que escribir el siguiente coacutedigo en elmeacutetodo (void)viewDidLoad de la clase sesion04_ejemplo1ViewControllerm

- (void)viewDidLoad

[super viewDidLoad]

NSUserDefaults datos = [NSUserDefaults standardUserDefaults]

obtenemos un NSStringNSString nombre = [datos stringForKeynombre]

obtenemos un NSIntegerNSInteger edad = [datos integerForKeyedad]

if (nombre=nil ampamp edad=0)NSLog(Nombre cargado edad dnombreedad)

else

NSLog(Sin datos)

bull 9) Ya podemos arrancar de nuevo la aplicacioacuten y veremos que si escribimos cualquier

Persistencia

60Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

texto en los Text Fields estos se almacenaraacuten dentro del objeto NSUserDefaults Alapagar la aplicacioacuten y volver a arrancarla estos datos se cargaraacuten (apareceraacuten enconsola)

Consola

NotaLos datos permaneceraacuten en nuestro dispositivo hasta que se elimine la aplicacioacuten en ese casotodos los datos guardados en NSUserDefaults se borraraacuten

bull 10) Probar eliminar la aplicacioacuten del dispositivosimulador y volver arrancarComprobamos que los datos se han borrado

Consola

73 Core Data

De todas las formas de persistencia de datos que existen en iOS Core Data es la maacutesapropiada para usar en caso de manejar datos no triviales con relaciones algo maacutescomplejas entre ellos El uso de Core Data dentro de nuestra aplicacioacuten optimiza almaacuteximo el consumo de memoria mejora el tiempo de respuesta y reduce la cantidad decoacutedigo redundante a escribir

Core Data utiliza de fondo el meacutetodo de almacenamiento SQLite visto en la sesioacuten 3 deeste moacutedulo por tanto para explicar el funcionamiento de este meacutetodo de persistenciavamos a utilizar el mismo modelo de datos que usamos en esa sesioacuten Persona -gtDetallePersona

El el siguiente ejemplo haremos la misma aplicacioacuten que hicimos con SQLite perousando este nuevo meacutetodo de persistencia

731 Creando el proyecto

Para empezar tenemos que crearnos un nuevo proyecto en XCode Para ello abrimosXCode y seleccionamos crear una nueva aplicacioacuten basada en ventanas (Window-basedapplication) Hacemos click en Next escribimos como nombre del proyectosesion04-ejemplo2 y seleccionamos Use Core Data

Persistencia

61Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creando un proyecto nuevo

Con esto ya tenemos la estructura baacutesica para empezar a programar Antes de nadahechamos un vistazo raacutepido a toda la estructura de directorios creada y vemos que dentrodel directorio principal tenemos un archivo que se llamasesion04_ejemplo2xcdatamodeld Al hacer click sobre el archivo se nos abre uneditor especial que en principio estaacute vacio y que maacutes adelante utilizaremos para disentildearnuestro modelo de datos

Ahora hechamos un vistazo a la clase delegada sesion04_ejemplo2AppDelegatemvemos que hay unos cuantos meacutetodos nuevos que serviraacuten para configurar el Core DataVamos a explicar los maacutes importantes

bull - (NSManagedObjectModel )managedObjectModel NSManagedObjectModel esla clase que contiene la definicioacuten de cada uno de los objetos o entidades quealmacenamos en la base de datos Normalmente este meacutetodo no lo utilizaremos ya quenosotros vamos a utilizar el editor que hemos visto antes con el que podremos crearnuevas entidades crear sus atributos y relaciones

bull - (NSPersistentStoreCoordinator )persistentStoreCoordinator Aquiacute esdonde configuramos los nombres y las ubicaciones de las bases de datos que se usaraacutenpara almacenar los objetos Cuando un managed object necesite guardar algo pasaraacutepor este meacutetodo

bull - (NSManagedObjectContext )managedObjectContext Esta claseNSManagedObjectContext seraacute la maacutes usada con diferencia de las tres y por tanto la

Persistencia

62Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

maacutes importante La utilizaremos baacutesicamente para obtener objetos insertarlos oborrarlos

732 Definiendo el modelo de datos

En el ejemplo que hicimos en la sesioacuten anterior de SQLite creamos dos tablas una paralos datos baacutesicos de la persona y otra para el detalle Ahora haremos lo mismo perousando Core Data la diferencia principal es que mientras que con SQLite obteniamos soacutelolos datos que necesitaacutebamos de la base de datos aquiacute obtenemos el objeto completo

Para definir nuestro modelo de datos basado en dos objetos (Persona y DetallePersona)abrimos el editor visual haciendo click sobre el ficherosesion04_ejemplo2xcdatamodeld Empezaremos creando una nueva entidad para ellohacemos click sobre el botoacuten Add Entity (+) que se encuentra en la parte inferior dela pantalla A la nueva entidad le pondremos como nombre Persona

Dentro de la seccioacuten de Attributes hacemos click sobre (+) y creamos un nuevoatributo para nuestra entidad le llamamos nombre y le asignamos como tipo StringRealizamos este mismo paso 2 veces maacutes para antildeadir apellidos y edad este uacutetlimode tipo Integer 16

Atributos Persona

Ahora creamos la entidad de DetallePersona de la misma manera que la anterior perocreando los atributos localidad pais direccion cp y telefono Todasde tipo String

Atributos DetallePersona

Finalmente nos queda relacionar de alguacuten modo las dos entidades para ello antildeadimos una

Persistencia

63Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

relacioacuten dentro de la entidad de Persona hacia DetallePersona simplementehaciendo click sobre el botoacuten (+) de la seccioacuten de Relationships A la relacioacuten leponemos como nombre detalle seleccionamos como destino la entidadDetallePersona y seraacute No inverse Este tipo de relacioacuten es uno a uno Por dentroCore Data realizaraacute crearaacute una clave ajena en la tabla de persona que apunte aDetallePersona pero eso a nosotros no nos interesa

Vista general modelo de datos (1)

Apple recomienda que siempre que hagamos una relacioacuten de una entidad a otra hagamostambieacuten la relacioacuten inversa por lo tanto hacemos justo lo anterior pero al reveacutes (deDetallePersona a Persona) con la diferencia que en la columna de Inverse

seleccionamos datosPersona

Ya tenemos las entidades y las relaciones creadas Si hacemos click en el botoacuten deEditor Style en la parte de abajo a la derecha del editor podemos ver de una formamaacutes clara la estructura que hemos disentildeado

Persistencia

64Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista general modelo de datos (2)

733 Probando el modelo

Una vez que tenemos el modelo creado pasamos a probarlo para comprobar que estaacute todocorrecto para ello abrimos la clase delegada (sesion04_ejemplo2AppDelegatem) yantildeadimos el siguiente fragmento de coacutedigo arriba del meacutetodoapplicationDidFinishLaunching

NSManagedObjectContext context = [self managedObjectContext]NSManagedObject persona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

[persona setValueJuan forKeynombre][persona setValueMartinez forKeyapellidos][persona setValue[NSNumber numberWithInt28] forKeyedad]

NSManagedObject detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersonainManagedObjectContextcontext]

[detallePersona setValueCalle Falsa 123 forKeydireccion][detallePersona setValueAlicante forKeylocalidad][detallePersona setValueEspantildea forKeypais][detallePersona setValue965656565 forKeytelefono]

[detallePersona setValuepersona forKeydatosPersona][persona setValuedetallePersona forKeydetalle]

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Persistencia

65Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Con este coacutedigo hemos creado un objeto Persona enlazado con otro objetoDetallePersona iexclComo puedes ver no hemos necesitado nada de coacutedigo SQLPrimero creamos un objeto del tipo NSManagedObjectContext que almacena el conextode Core Data Despueacutes creamos el objeto Persona en siacute con NSManagedObject A esteobjeto le pondremos los valores para cada una de las propiedades definidas en el modeloHacemos lo mismo con DetallePersona Finalmente relacionamos el objetoPersona con el objeto DetallePersona

Compilamos el proyecto comprobamos que todo funciona bien y no salen errores

Para comprobar que los datos se han almacenado correctamente en nuestro dispositivo losmostramos en la consola de la siguiente manera

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

intValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(------------------)

[fetchRequest release]

El coacutedigo de arriba lo escribiremos dentro del meacutetododidFinishLaunchingWithOptions de la clase delegada Lo que hace este coacutedigo esrecorrer todos los objetos almacenados en memoria y mostrarlos por consola Siarrancamos la aplicacioacuten veremos que se muestran todos los datos de los objetosalmacenados Cada vez que arranquemos la aplicacioacuten se mostraraacuten maacutes objetos ya quetambieacuten los creamos

Para listar los objetos usamos la clase NSFetchRequest Esta clase es equivalente aejecutar una sentencia Select de SQL Al objeto NSFetchRequest le asignamos laentidad que queremos listar (SELECT FROM ) en nuestro caso seraacute PersonaEl listado de los objetos se obtiene mediante el meacutetodo executeFetchRequest de laclase NSManagedObjectContext que hemos definido en el bloque anterior de coacutedigoEste meacutetodo devolveraacute un NSArray con todos los objetos NSManagedObject

Persistencia

66Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Salida de consola

NotaSi queremos ver queacute sentencias SQL estaacute ejecutando Core Data por debajo tenemos que activarel flag de debug para ello seleccionamos la opcioacuten Product gt Edit Scheme Enla columna de la izquierda seleccionamos el esquema que estamos utilizando para Debug y en laparte de la derecha en el cuadro de Arguments Passed on Launch antildeadimos 2paraacutemetros -comappleCoreDataSQLDebug y 1

Ahora arrancamos de nuevo la aplicacioacuten y veremos que en la consola apareceraacuten lassentencias SQL utilizadas por Core Data

()

INSERT INTO ZDETALLEPERSONA(Z_PK Z_ENT Z_OPT ZDATOSPERSONA ZTELEFONOZLOCALIDAD ZDIRECCION ZPAIS)VALUES( )CoreData sql SELECT 0 t0Z_PK t0Z_OPT t0ZTELEFONO t0ZLOCALIDADt0ZDIRECCION t0ZPAIS t0ZDATOSPERSONAFROM ZDETALLEPERSONA t0 WHERE t0Z_PK =

()

734 Generando los modelos automaacuteticamente

Generar los modelos a mano directamente desde el editor no es lo maacutes recomendado yaque podemos cometer errores de escritura errores de tipo etc La mejor manera dehacerlo es creando un archivo para cada entidad Esto se puede hacer desde 0 peroXCode tiene un generador de clases que es muy faacutecil de usar y ahorremos bastantetiempo iexclVamos a usarlo

Hacemos click sobre el raiz de nuestro proyecto y seleccionamos New File En lacolumna de la izquierda seleccionamos iOS gt Core Data y en el cuadro de laderecha NSManagedObject subclass Click en next En la siguiente pantallaseleccionamos nuestro modelo de datos sesion04_ejemplo2 y click en next Ahoranos aseguramos de seleccionar las dos entidades que hemos creado anteriormentePersona y DetallePersona click en next Seleccionamos el raiz de nuestroproyecto para guardar los ficheros que se generen y aceptamos

Persistencia

67Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ventana de seleccioacuten de entidad

Ahora veremos que xsource ha creado automaacuteticamente 4 ficheros nuevos dentro denuestro proyecto Personamh y DetallePersonamh con todos los atributosespecificados Estas nuevas clases que heredan de NSManagedObject seraacuten las queutilicemos a partir de ahora

Cambiamos el coacutedigo de la clase delegada por el siguiente

NSManagedObjectContext context = [self managedObjectContext]Persona datosPersona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

datosPersonanombre = JuandatosPersonaapellidos = MartinezdatosPersonaedad = [NSNumber numberWithInt28]

DetallePersona detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersona

inManagedObjectContextcontext]detallePersonadireccion = Calle Falsa 123

Persistencia

68Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

detallePersonalocalidad = AlicantedetallePersonapais = EspantildeadetallePersonatelefono = 965656565

detallePersonadatosPersona = datosPersonadatosPersonadetalle = detallePersona

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Listamos los datosNSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

integerValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(-----------------)[fetchRequest release]

Ejecutamos el proyecto y comprobamos que en la consola aparece lo mismo que antespero ahora tenemos el coacutedigo bastante maacutes claro

735 Creando la vista de tabla

Ahora vamos a mostrar los objetos que antes hemos listado por consola en una vista detabla UITableView Para ello creamos un UITableViewController

Click en New file gt UIViewController subclass En la siguiente pantallanos aseguramos de seleccionar Subclass of UITableViewController de esta formanos apareceraacuten ya todos los meacutetodos de la tabla creados por defecto Guardamos elarchivo con el nombre que queramos por ejemplo PersonasViewController

Abrimos ahora PersonasViewControllerh y antildeadimos 2 atributos a la clase

bull Un NSArray que se seraacute el listado de personas (objetos de la clase Persona)bull Una referencia a NSManagedObjectContext

El fichero PersonasViewControllerh quedaraacute de la siguiente manera

Persistencia

69Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

imports iniciales

interface PersonasViewController UITableViewController NSArray _listaPersonasNSManagedObjectContext _context

property (nonatomic retain) NSArray listaPersonasproperty (nonatomic retain) NSManagedObjectContext context

end

Ahora en el m importamos en el encabezado las clases Personah yDetallePersonah y en el meacutetodo viewDidLoad creamos el array listaPersonas contodos los objetos Persona de forma similar a cuando lo hicimos en el apartado anterior

import PersonasViewControllerh

import Personahimport DetallePersonah

implementation PersonasViewController

synthesize listaPersonas = _listaPersonassynthesize context = _context

En viewDidLoad

- (void)viewDidLoad

[super viewDidLoad]

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContext_context]

[fetchRequest setEntityentity]NSError errorselflistaPersonas = [_context executeFetchRequestfetchRequest

erroramperror]selftitle = Listado Personas[fetchRequest release]

Ahora completamos el resto de meacutetodos heredados de UITableViewController de lamisma forma que hicimos en sesiones anteriores

- (NSInteger)numberOfSectionsInTableView(UITableView )tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection(NSInteger)section

Persistencia

70Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Return the number of rows in the sectionreturn [_listaPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]

if (cell == nil) cell = [[[UITableViewCell alloc]

initWithStyleUITableViewCellStyleSubtitlereuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona p = (Persona )[_listaPersonas objectAtIndexindexPathrow]celltextLabeltext = pnombrecelldetailTextLabeltext = [NSString stringWithFormatd antildeos

[pedad intValue]]

return cell

Ahora para poder mostrar la tabla dentro de nuestra aplicacioacuten debemos asignarla dentrode la clase delegada para ello abrimos la vista MainWindowxib y arrastramos unacontroladora de navegacioacuten Navigation Controller a la lista de objetos de laizquierda La desplegamos y dentro de View Controller en la pestantildea de la derechade Identity Inspector escribimos el nombre de nuestra clase que tiene la vista de latabla PersonasViewController

Pantalla del disentildeador

Ahora abrimos el fichero sesion04_ejemplo2AppDelegateh y creamos un

Persistencia

71Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

UINavigationController dentro de la declaracioacuten de variables dejando el ficherocomo sigue

imports iniciales

interface sesion04_ejemplo2AppDelegate NSObject ltUIApplicationDelegategt

UINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet UINavigationControllernavController

property (nonatomic retain readonly) NSManagedObjectContextmanagedObjectContextproperty (nonatomic retain readonly) NSManagedObjectModelmanagedObjectModelproperty (nonatomic retain readonly) NSPersistentStoreCoordinatorpersistentStoreCoordinator

- (void)saveContext- (NSURL )applicationDocumentsDirectory

end

Dentro del m importamos la clase PersonasViewControllerh y completamos elsynthetize

synthesize navController = _navController

Y por uacuteltimo justo antes de la llamada al meacutetodo makeKeyAndVisible escribimos elsiguiente coacutedigo para asignar el contexto de la clase delegada alPersonasViewController

PersonasViewController root = (PersonasViewController ) [_navControllertopViewController]

rootcontext = [self managedObjectContext][selfwindow addSubview_navControllerview]

Ahora compilamos y ejecutamos nuestro proyecto y si todo ha ido bien deberiacutea de salirla tabla con los datos de las personas

Persistencia

72Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Listado de personas

736 Migraciones de bases de datos con Core Data

Una vez que tenemos disentildeada nuestra base de datos inicial iquestqueacute pasariacutea si queremosmodificarla para antildeadirle un campo maacutes a una tabla iquestQueacute haraacute Core Data para realizar lamigracioacuten de la versioacuten anterior a la actual

Siguiendo con el ejemplo anterior si despueacutes de ejecutar la aplicacioacuten en nuestrodispositivo o simulador modificamos la estructura de la base de datos antildeadiendo unatributo nuevo en la entidad DetallePersona por ejemplo codigoPostal con el tipoInteger 16 veremos que al volver a arrancar la aplicacioacuten nos apareceraacute un error entiempo de ejecucioacuten similar al siguiente

Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 Theoperationcouldnrsquot be completed(Cocoa error 134100) UserInfo=0x6b6c680 metadata=ltCFBasicHash 0x6b68380[0x1337b38]gttype = immutable dict count = 7entries =gt

2 ltCFString 0x6b70320 [0x1337b38]gtcontents =NSStoreModelVersionIdentifiers

=ltCFArray 0x6b706e0 [0x1337b38]gttype = immutable count = 1

values = (0 ltCFString 0x1332cd8 [0x1337b38]gtcontents =

)4 ltCFString 0x6b70350 [0x1337b38]gtcontents =

NSPersistenceFrameworkVersion=

Persistencia

73Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltCFNumber 0x6b6f2c0 [0x1337b38]gtvalue = +386 type =kCFNumberSInt64Type

6 ltCFString 0x6b70380 [0x1337b38]gtcontents =NSStoreModelVersionHashes =

ltCFBasicHash 0x6b707e0 [0x1337b38]gttype = immutable dict count =2entries =gt

1 ltCFString 0x6b70700 [0x1337b38]gtcontents = Persona =ltCFData 0x6b70740

[0x1337b38]gtlength = 32 capacity = 32 bytes =0x1c50e5a379ff0ddc3cda7b82a3934c5e 75649ac3c919beea2 ltCFString 0x6b70720 [0x1337b38]gtcontents = DetallePersona

=ltCFData 0x6b70790[0x1337b38]gtlength = 32 capacity = 32 bytes =0xb7ba2eb498f9a1acdd6630d56f6cd12c e8963dd3e488ba95

7 ltCFString 0x10e3aa8 [0x1337b38]gtcontents = NSStoreUUID =ltCFString 0x6b70510[0x1337b38]gtcontents = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E8 ltCFString 0x10e3948 [0x1337b38]gtcontents = NSStoreType =ltCFString 0x10e3958[0x1337b38]gtcontents = SQLite9 ltCFString 0x6b703b0 [0x1337b38]gtcontents =

_NSAutoVacuumLevel = ltCFString0x6b70830 [0x1337b38]gtcontents = 210 ltFString 0x6b703d0 [0x1337b38]gtcontents =

NSStoreModelVersionHashesVersion=ltCFNumber 0x6b61950 [0x1337b38]gtvalue = +3 type =

kCFNumberSInt32Type reason=The model used to open the store is incompatible with the oneused tocreate the store

metadata = NSPersistenceFrameworkVersion = 386NSStoreModelVersionHashes =

DetallePersona = ltb7ba2eb4 98f9a1ac dd6630d5 6f6cd12c f890007746b16f00

e8963dd3 e488ba95gtPersona = lt1c50e5a3 79ff0ddc 3cda7b82 a3934c5e 5cb4ee4b

4ac98838 75649ac3c919beeagt

NSStoreModelVersionHashesVersion = 3NSStoreModelVersionIdentifiers = (

)NSStoreType = SQLiteNSStoreUUID = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E_NSAutoVacuumLevel = 2

reason = The model used to open the store is incompatible with the

one usedto create the store

Este error es muy comuacuten cuando estamos desarrollando aplicaciones con Core DataBaacutesicamente nos dice que el modelo de base de datos que estaacute intentando cargar dememoria es distinto al que se indica en la aplicacioacuten esto es porque no hemos indicadoninguacuten meacutetodo de migracioacuten Realmente existen dos formas de solucionar este error

Persistencia

74Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Por un lado y soacutelo cuando estemos desarrollando y no tengamos ninguna versioacutende nuestra aplicacioacuten lanzada en la App Store podremos eliminar la base de datosdel dispositivo simulador simplemente borrando la aplicacioacuten de el Tambieacutenpodemos ejecutar el siguiente coacutedigo una soacutela vez [[NSFileManagerdefaultManager] removeItemAtURLstoreURL errornil]

bull En el caso de que estemos realizando una migracioacuten sobre una versioacuten ya publicadaen la App Store el meacutetodo anterior no nos serviriacutea ya que obligariacuteamos al usuario aborrar su aplicacioacuten del dispositivo con lo que todos los datos que tuviera seperderiacutean Esto no es viable por lo que necesitaremos usar otro meacutetodo para realizarla migracioacuten y solucionar el error de una forma maacutes limpia este meacutetodo es el quevamos a comentar a continuacioacuten

XCode soporta Versioning para Core Data esto es que podemos crear versiones de labase de datos de una forma sencilla si seguimos una serie de pasos

1) Antildeadimos las siguientes opciones en forma de NSDictionary a nuestro coacutedigo delpersistentStoreCoordinator

NSDictionary options = [NSDictionary dictionaryWithObjectsAndKeys[NSNumber numberWithBoolYES]NSMigratePersistentStoresAutomaticallyOption[NSNumber numberWithBoolYES]NSInferMappingModelAutomaticallyOptionnil]

Y modificamos el meacutetodo addPersistentStoreWithType pasaacutendole como paraacutemetro eldiccionario options

if ([__persistentStoreCoordinatoraddPersistentStoreWithTypeNSSQLiteStoreTypeconfigurationnil URLstoreURL optionsoptions erroramperror])

Con el coacutedigo anterior indicamos al persistentStoreCoordinator que la migracioacuten enel caso de que se deba de realizar sea de forma automaacutetica

AtencioacutenDeberemos de tener en cuenta que este tipo de migracioacuten que estamos estudiando en este puntosoacutelo es vaacutelida para cambios simples en Core Data lo que Apple llama como lightweightmigration Este tipo de migracioacuten admite una serie de cambios que se indican dentro de ladocumentacioacuten de Apple para cambios maacutes complejos deberemos realizar migracionespersonalizadas y mucho maacutes complejas El documento en donde Apple explica el tipo demigraciones que existen para Core Data y su forma de implementarlas lo podemos consultardesde esta web

2) Ahora deberemos de crear la versioacuten del esquema de Core Data dentro de XCode para

Persistencia

75Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ello seleccionamos el fichero del esquema sesion04_ejemplo2xcdatamodeld yseleccionamos en el menuacute principal Editor gt Add Model Version A la nueva versioacuten lallamaremos sesion04_ejemplo_2 (versioacuten 2) Una vez que hayamos pulsado sobreFinish se nos habraacute creado la nueva versioacuten del esquema dentro de la principal y estaraacutemarcada con un tick la versioacuten anterior

3) Una vez que tenemos creada la nueva versioacuten deberemos modificarla antildeadiendole elnuevo atributo Seleccionamos la versioacuten recieacuten creadasesion04_ejemplo_2xcdatamodel y antildeadimos a la entidad DetallePersona elatributo codigoPostal Atencioacuten Si este atributo lo hemos antildeadido anteriormentedeberemos de borrarlo de la versioacuten anterior y antildeadirlo en la segunda versioacuten

4) Ahora nos queda indicar a Core Data que queremos usar la versioacuten 2 de la base dedatos para nuestra aplicacioacuten Para ello seleccionamos el esquema principal (el raiz)sesion04_ejemplo2xcdatamodeld y en la ventana de la derecha en la pestantildea de FileInspector seleccionamos como Versioacuten actual la versioacuten 2 esto lo hacemos dentro delapartado de Versioned Core Data Model Automaacuteticamente veremos que el siacutembolo detick cambia a la versioacuten 2

5) Ahora ya podemos volver a compilar y veremos que no nos aparece el error y funcionatodo seguacuten lo esperado

Magical RecordExisten varias librerias de coacutedigo libre que facilitan enormemente el uso de Core Data ennuestras aplicaciones iOS Una de ellas es Magical Record Mediante esta libreriacutea podremos usarCore Data sin tener que preocuparnos de todo el proceso de configuracioacuten en las controladorasde la configuracioacuten de migraciones etc Su instalacioacuten en un proyecto de XCode es muy faacutecil yaltamente recomendable ya que proporciona una API muy clara y sencilla de usar

Persistencia

76Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios

81 Usando User Defaults (1 punto)

En este ejercicio vamos a usar las variables de usuario para almacenar y mostrar los datosdel dispositivo que estamos usando y las veces que hemos arrancado la aplicacioacuten

Crear un proyecto nuevo usando la plantilla Single View Application con el nombreejercicio-userdefaults-ios Se debe de ejecutar en iPhone e iPad (Universal) Comoidentificador de empresa escribiremos esuajtech y como prefijo de clase UA Debe detener el ARC activado (desmarcar el resto de opciones)

Ayuda meacutetodos de UIDevicePara obtener los datos del dispositivo usaremos el singleton UIDevice estos son algunos de losmeacutetodos que podemos usar [[UIDevice currentDevice] name] [[UIDevicecurrentDevice] systemName] [[UIDevice currentDevice]systemVersion] [[UIDevice currentDevice] model] [[UIDevicecurrentDevice] localizedModel]

82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)

En este ejercicio disentildearemos un modelo de datos de dos tablas relacionadas entre ellasantildeadiremos datos por coacutedigo y los mostraremos en una vista de tabla La base de datosque vamos a implementar constaraacute de dos tablas por un lado una que contendraacute los tiacutetulosde series y otra que tendraacute los detalles A la primera la llamaremos Serie y la segundaDetalleSerie Para realizar correctamente el ejercicio deberemos seguir los siguientespasos

1) Crear un proyecto nuevo usando la plantilla Master-Detail Application con elnombre ejercicio-coredata-ios Se debe de ejecutar en iPhone como identificador deempresa escribiremos esuajtech y como prefijo de clase UA Debe de tener el ARCactivado y la opcioacuten de Use Core Data (desmarcar el resto de opciones)

2) Disentildear el siguiente modelo de datos

Persistencia

77Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esquema modelo de datos

3) Revisar el fichero UAAppDelegatem y comprobar que tenemos todos los meacutetodosnecesarios para que funcione Core Data en nuestra aplicacioacuten

4) Modificar el fichero UAMasterViewControllerm para que se muestren los tiacutetulos delas series en la tabla

5) Probar la aplicacioacuten antildeadiendo unos tiacutetulos de prueba

83 Migrando datos con Core Data (05 puntos)

Una vez que hemos disentildeado la Base de Datos con Core Data vamos a modificarlaantildeadiendo un atributo maacutes a la tabla de DetalleSerie valoracion

a) iquestQueacute pasa si cambiamos el modelo de datos y arrancamos la aplicacioacuten iquestQueacute errornos da y por queacute

b) iquestCoacutemo podemos evitar este tipo de errores cuando estemos desarrollando iquestY siestamos en produccioacuten con la aplicacioacuten ya a la venta

c) Crea una nueva versioacuten del modelo

d) Modifica el coacutedigo necesario dentro de la clase UAAppDelegate para evitar este tipo deerrores cuando estemos en un entorno de produccioacuten

e) Vuelve a arrancar la aplicacioacuten y comprueba que funciona bien

Persistencia

78Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Persistencia

79Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

  • 1 Persistencia en Android ficheros y SQLite
    • 11 Introduccioacuten
    • 12 Manejo de ficheros tradicionales en Android
      • 121 Apertura de ficheros
      • 122 Ficheros como recursos
      • 123 Operar con ficheros
      • 124 Almacenar datos en la tarjeta de memoria
        • 13 Base de datos SQLite
          • 131 Content Values
          • 132 Cursores
          • 133 Trabajar con bases de datos SQLite
          • 134 La clase SQLiteOpenHelper
          • 135 Crear una base de datos sin SQLiteHelper
          • 136 Realizar una consulta
          • 137 Extraer resultados de un cursor
          • 138 Antildeadir actualizar y borrar filas
              • 2 Ejercicios - Persistencia en Android ficheros y SQLite
                • 21 Uso de ficheros (05 puntos)
                • 22 Persistencia con ficheros (05 puntos)
                • 23 Base de datos SQLiteOpenHelper (05 puntos)
                • 24 Base de datos insercioacuten y borrado (05 puntos)
                • 25 Base de datos probar nuestro adaptador (05 puntos)
                • 26 Base de datos cambios en la base de datos (05 puntos)
                  • 3 Persistencia en Android proveedores de contenidos y SharedPreferences
                    • 31 Shared Preferences
                      • 311 Guardar Shared Preferences
                      • 312 Leer Shared Preferences
                      • 313 Interfaces para Shared Preferences
                      • 314 Definiendo una pantalla de preferencias con un layout en XML
                      • 315 Controles nativos para preferencias
                      • 316 Actividades de preferencias
                      • 317 Shared Preference Change Listeners
                        • 32 Proveedores de contenidos
                          • 321 Proveedores nativos
                          • 322 Proveedores propios crear un nuevo proveedor de contenidos
                          • 323 Proveedores propios crear la interfaz de consultas
                          • 324 Proveedores propios tipo MIME
                          • 325 Proveedores propios registrar el proveedor
                          • 326 Content Resolvers
                          • 327 Otras operaciones con Content Resolvers
                              • 4 Ejercicios - Persistencia en Android proveedores de contenidos y SharedPreferences
                                • 41 Compartir datos entre actividades con Shared Preferences (075 puntos)
                                • 42 Actividad de preferencias (075 puntos)
                                • 43 Proveedor de contenidos propio (075 puntos)
                                • 44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)
                                  • 5 Persistencia de datos en iOS Ficheros y SQLite
                                    • 51 Introduccioacuten
                                    • 52 Ficheros plist (Property Lists)
                                      • 521 Leyendo datos desde ficheros plist
                                      • 522 Escribiendo datos en ficheros plist
                                        • 53 SQLite
                                          • 531 Herramientas disponibles
                                          • 532 Lectura de datos
                                          • 533 Mostrando los datos en una tabla
                                              • 6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios
                                                • 61 Creando y leyendo un fichero plist (15 puntos)
                                                • 62 Implementar vista de detalle de la serie (05 puntos)
                                                • 63 Uso baacutesico de una base de datos SQLite (1 punto)
                                                  • 7 Persistencia de datos en iOS User Defaults y Core Data
                                                    • 71 Introduccioacuten
                                                    • 72 User Defaults
                                                    • 73 Core Data
                                                      • 731 Creando el proyecto
                                                      • 732 Definiendo el modelo de datos
                                                      • 733 Probando el modelo
                                                      • 734 Generando los modelos automaacuteticamente
                                                      • 735 Creando la vista de tabla
                                                      • 736 Migraciones de bases de datos con Core Data
                                                          • 8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios
                                                            • 81 Usando User Defaults (1 punto)
                                                            • 82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)
                                                            • 83 Migrando datos con Core Data (05 puntos)
Page 5: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de

FileOutputStream fos = openFileOutput(ficherotxtContextMODE_PRIVATE)String cadenaOutput = new String(Contenido del ficheron)DataOutputStream dos = new DataOutputStream(fos)doswriteBytes(cadenaOutput)fosclose()

FileInputStream fin = openFileInput(ficherotxt)DataInputStream dis = new DataInputStream(fin)String string = disreadLine() Hacer algo con la cadenafinclose()

124 Almacenar datos en la tarjeta de memoria

Veamos ahora queacute pasos debemos seguir para poder escribir datos en la tarjeta dememoria (SD card) Para ello se requiere el uso del meacutetodoEnvironmentgetExternalStorageDirectory() y una instancia de la claseFileWriter tal como se muestra en el siguiente ejemplo

try File raiz = EnvironmentgetExternalStorageDirectory()if (raizcanWrite())

File file = new File(raiz ficherotxt)BufferedWriter out = new BufferedWriter(new

FileWriter(file))outwrite(Mi texto escrito desde Androidn)outclose()

catch (IOException e)

Loge(FILE IO Error en la escritura de fichero +egetMessage())

Para probarlo en el emulador necesitamos tener creada una tarjeta SD lo cual se puedeconseguir mediante la herramienta del Android SDK mksdcard

mksdcard 512M sdcardiso

Podremos cargar esta tarjeta SD en nuestro emulador seleccionando el archivo recieacutencreado dentro del apartado SD card de la ventana de edicioacuten de las propiedades delemulador Para copiar fuera del emulador el archivo que hemos grabado en la tarjeta dememoria podemos utilizar el comando

adb pull sdcardficherotxt ficherotxt

Por uacuteltimo no debemos olvidar antildeadir el permiso correspondiente en el archivo Manifestde nuestra aplicacioacuten para poder escribir datos en un dispositivo de almacenamientoexterno Para ello antildeadimos lo siguiente al elemento manifest del AndroidManifestxml(recuerda que esto no va ni dentro del elemento application ni dentro del elementoactivity)

ltuses-permission androidname=androidpermissionWRITE_EXTERNAL_STORAGEgt

Persistencia

5Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

13 Base de datos SQLite

SQLite es un gestor de bases de datos relacional y es de coacutedigo abierto cumple con losestaacutendares y es extremadamente ligero Ademaacutes guarda toda la base de datos en un uacutenicofichero Su utilidad es evidente en el caso de aplicaciones de pequentildeo tamantildeo pues eacutestasno requeriraacuten la instalacioacuten adicional de ninguacuten gestor de bases de datos Esto tambieacutenseraacute asiacute en el caso de dispositivos empotrados con recursos limitados Android incluyesoporte a SQLite y en esta seccioacuten veremos coacutemo usarlo

Por medio de SQLite podremos crear bases de datos relacionales independientes paranuestras aplicaciones Con ellas podremos almacenar datos complejos y estructuradosAunque el disentildeo de bases de datos quedaraacute fuera del contenido de este curso ya quenecesitariacuteamos de maacutes tiempo para poder tratarlo es conveniente resaltar el hecho de quelas mejores praacutecticas en este campo siguen siendo uacutetiles en el caso de aplicacionesAndroid En particular la normalizacioacuten de datos para evitar redundancia es un paso muyimportante en el caso en el que estemos disentildeando una base de datos para dispositivoscon recursos limitados

En el caso del sistema Android SQLite se implementa como una libreriacutea C compacta enlugar de ejecutarse en un proceso propio Debido a ello cualquier base de datos quecreemos seraacute integrada como parte de su correspondiente aplicacioacuten Esto reducedependencias externas minimiza la latencia y simplifica ciertos procesos como lasincronizacioacuten

NotaLas bases de datos de Android se almacenan en el directoriodatadata[PAQUETE]databases donde [PAQUETE] es el paquetecorrespondiente a la aplicacioacuten Por defecto todas las bases de datos son privadas y tan soacuteloaccesibles por la aplicacioacuten que las creoacute

131 Content Values

Para insertar nuevas filas en tablas haremos uso de objetos de la clase ContentValuesCada objeto de este tipo representa una uacutenica fila en una tabla y se encarga de emparejarnombres de columnas con valores concretos Hay que tener en cuenta que SQLite difierede muchos otros motores de bases de datos en cuanto al tipado de las columnas La ideabaacutesicamente es que los valores para las columnas no tienen por queacute ser de un uacutenico tipoes decir los datos en SQLite son deacutebilmente tipados La consecuencia de esto es que noes necesario comprobar tipos cuando se asignan o extraen valores de cada columna de unafila con lo que se mejora la eficiencia

132 Cursores

Persistencia

6Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Los cursores son una herramienta fundamental en el tratamiento de informacioacuten enAndroid y tambieacuten cuando se trabaja con bases de datos Por lo tanto en esta seccioacutenintroducimos este concepto tan importante

Cualquier consulta a una base de datos en Android devolveraacute un objeto de la claseCursor Los cursores no contienen una copia de todos los resultados de la consulta sinoque maacutes bien se trata de punteros al conjunto de resultados Estos objetos proporcionanun mecanismo para controlar nuestra posicioacuten (fila) en el conjunto de resultadosobtenidos tras la consulta

La clase Cursor incluye entre otras las siguientes funciones de navegacioacuten entreresultados

bull moveToFirst desplaza el cursor a la primera fila de los resultados de la consultabull moveToNext desplaza el cursor a la siguiente filabull moveToPrevious desplaza el cursor a la fila anteriorbull getCount devuelve el nuacutemero de filas del conjunto de resultados de la consultabull getColumnName devuelve el nombre de la columna especificada mediante un iacutendice

que se pasa como paraacutemetrobull getColumnNames devuelve un array de cadenas conteniendo el nombre de todas las

columnas en el cursorbull moveToPosition mueve el cursor a la fila especificadabull getPosition devuelve el iacutendice de la posicioacuten apuntada actualmente por el cursor

A lo largo de esta sesioacuten aprenderemos coacutemo realizar consultas a una base de datos ycomo extraer valores o nombres de columnas concretos a partir de los cursores obtenidospor medio de dichas consultas

133 Trabajar con bases de datos SQLite

Suele considerarse una buena praacutectica el crear una clase auxiliar que nos facilite lainteraccioacuten con la base de datos Se trataraacute de una clase de tipo adaptador que crearaacute unacapa de abstraccioacuten que encapsularaacute dichas interacciones De esta forma podremosantildeadir eliminar o actualizar elementos en la base de datos sin necesidad de introducir niuna uacutenica instruccioacuten en SQL en el resto del coacutedigo permitiendo ademaacutes que se hagan lascomprobaciones de tipos correspondientes

Una clase de este tipo deberiacutea incluir meacutetodos para crear abrir o cerrar la base de datosTambieacuten puede ser un lugar conveniente para publicar constantes estaacuteticas relacionadascon la base de datos como por ejemplo constantes que almacenen el nombre de la tabla olas diferentes columnas

A continuacioacuten se muestra el coacutedigo de lo que podriacutea ser un adaptador para una base dedatos estaacutendar Incluye una extensioacuten de la clase SQLiteOpenHelper que seraacute tratada enmaacutes detalle en la siguiente seccioacuten y que se utiliza para simplificar la apertura lacreacioacuten y la actualizacioacuten de la base de datos

Persistencia

7Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

import androidcontentContextimport androiddatabaseimport androiddatabasesqliteimport androiddatabasesqliteSQLiteDatabaseCursorFactoryimport androidutilLog

public class MiAdaptadorBD private static final String NOMBRE_BASE_DATOS =

mibasededatosdbprivate static final String TABLA_BASE_DATOS = mainTableprivate static final int VERSION_BASE_DATOS = 1

Nombre de la columna de clave primariapublic static final String CP_ID=_id El nombre e iacutendice de cada columna en la base de datospublic static final String COLUMNA_NOMBRE=nombrepublic static final int COLUMNA_INDICE = 1 PENDIENTE crear campos adicionales para el resto de columnas de la base de datos

Sentencia SQL para crear una nueva base de datosprivate static final String CREAR_BASE_DATOS = create table +

TABLA_BASE_DATOS + ( + CP_ID + integer primary key autoincrement +COLUMNA_NOMBRE + text not null)

Variable para almacenar la instancia de la base de datosprivate SQLiteDatabase db Contexto de la aplicacioacuten que estaacute usando la base de datosprivate final Context contexto Clase helper usada para crear o actualizar la base de datos (hablamos de ella en la siguiente seccioacuten)private miHelperBD dbHelper

Crea la base de datos con ayuda del helperpublic MiAdaptadorBD(Context _contexto)

contexto = _contextodbHelper = new miHelperBD(contexto NOMBRE_BASE_DATOS

nullVERSION_BASE_DATOS)

Abre una base de datos ya existentepublic MiAdaptadorBD open() throws SQLException

db = dbHelpergetWritableDatabase()return this

Cierra la base de datos cuando eacutesta ya no va a ser utilizadapublic void close()

dbclose()

Meacutetodo para insertar un elemento en la tabla de la base dedatos

public int insertar(MiObjeto _miObjeto) PENDIENTE crear nuevos elementos ContentValues para

representar al objeto e insertarlos en la base de datos

Devolvemos el iacutendice del nuevo elemento en la base dedatos

return index

Persistencia

8Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Meacutetodo para eliminar un elemento de la tabla de la base dedatos

Devuelve un valor booleano indicando el eacutexito o no de la operacioacutenpublic boolean eliminar(long _indiceFila)

return dbdelete(TABLA_BASE_DATOS CP_ID + = +_indiceFila null) gt 0

Meacutetodo para obtener un Cursor que apunte a todas las filascontenidas

en la tabla de la base de datospublic Cursor obtenerTodos ()

return dbquery(TABLA_BASE_DATOS new String[] CP_IDCOLUMNA_NOMBRE

null null null nullnull)

Meacutetodo para obtener un elemento determinado de la base de datos a partir de su iacutendice de filapublic MiObjeto getObjeto(long _indiceFila)

PENDIENTE obtener un cursor a una fila de la base de datos y usar sus valores para inicializar una instancia de MiObjeto

return objectInstance

Meacutetodo para actualizar un elemento de la tabla de la base dedatos

public boolean actualizar(long _indiceFila MiObjeto _miObjeto) PENDIENTE crear nuevos ContentValues a partir del

objeto y usarlos para actualizar una fila en la tabla

correspondiente

return true

Clase privada auxiliar para ayudar en la creacioacuten yactualizacioacuten

de la base de datosprivate static class miHelperBD extends SQLiteOpenHelper

public miHelperBD(Context contexto String nombreCursorFactory factory int version)

super(contexto nombre factory version)

Meacutetodo llamado cuando la base de datos no existe en eldisco

y la clase Helper necesita crearla desde ceroOverridepublic void onCreate(SQLiteDatabase _db)

_dbexecSQL(CREAR_BASE_DATOS)

Meacutetodo llamado cuando la versioacuten de la base de datos en disco es diferente a la indicada en este caso la base de datos en disco necesita ser actualizadaOverridepublic void onUpgrade(SQLiteDatabase _db int

_versionAnterior

Persistencia

9Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

int _versionNueva) Mostramos en LogCat la operacioacutenLogw(AdaptadorBD Actualizando desde la

versioacuten +_versionAnterior + a la versioacuten +_versionNueva + lo cual borraraacute los

datospreviamente almacenados)

Actualizamos la base de datos para que seadapte a la nueva versioacuten

La forma maacutes sencilla de llevar a cabo dichaactualizacioacuten es

eliminar la tabla antigua y crear una nueva_dbexecSQL(DROP TABLE IF EXISTS +

TABLA_BASE_DATOS)onCreate(_db)

134 La clase SQLiteOpenHelper

La clase SQLiteOpenHelper es una clase abstracta utilizada como medio paraimplementar un patroacuten de creacioacuten apertura y actualizacioacuten de una determinada base dedatos Al implementar una subclase de SQLiteOpenHelper podemos abstraernos de laloacutegica subyacente a la decisioacuten de crear o actualizar una base de datos de manera previa aque eacutesta deba ser abierta

En el ejemplo de coacutedigo anterior se vio coacutemo crear una subclase de SQLiteOpenHelper

por medio de la sobrecarga de sus meacutetodos onCreate y onUpgrade los cuales permitencrear una nueva base de datos o actualizar a una nueva versioacuten respectivamente

NotaEn el ejemplo anterior el meacutetodo onUpgrade simplemente eliminaba la tabla existente y lareemplazaba con la nueva definicioacuten de la misma En la praacutectica la mejor solucioacuten seriacutea migrarlos datos ya existentes a la nueva tabla

Mediante los meacutetodos getReadableDatabase y getWritableDatabase podemos abrir yobtener una instancia de la base de datos de soacutelo lectura o de lectura y escriturarespectivamente La llamada a getWritableDatabase podriacutea fallar debido a falta deespacio en disco o cuestiones relativas a permisos por lo que es una buena praacutectica teneren cuenta esta posibilidad por medio del mecanismo de manejo de excepciones de JavaEn caso de fallo la solucioacuten podriacutea pasar por al menos proporcionar una copia de soacutelolectura tal como se muestra en el siguiente ejemplo

dbHelper = new miHelperBD(contexto NOMBRE_BASE_DATOS nullVERSION_BASE_DATOS)

SQLiteDatabase dbtry

db = dbHelpergetWritableDatabase() catch (SQLiteException ex)

db = dbHelpergetReadableDatabase()

Persistencia

10Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Al hacer una llamada a cualquiera de los dos meacutetodos anteriores se invocaraacuten losmanejadores de evento adecuados Asiacute pues si por ejemplo la base de datos no existieraen disco se ejecutariacutea el coacutedigo de onCreate Si por otra parte la versioacuten de la base dedatos se hubiera modificado se disparariacutea el manejador onUpgrade Lo que sucedeentonces es que al hacer una llamada a getWritableDatabase o getReadableDatabase

se devolveraacute una base de datos nueva actualizada o ya existente seguacuten el caso

135 Crear una base de datos sin SQLiteHelper

Tambieacuten es posible crear y abrir bases de datos sin necesidad de utilizar una subclase deSQLiteHelper Para ello haremos uso del meacutetodo openOrCreateDatabase En este casoseraacute necesario tambieacuten hacer uso del meacutetodo execSQL sobre la instancia de la base dedatos devuelta por el meacutetodo anterior para ejecutar los comandos SQL que permitiraacutencrear las tablas de la base de datos y establecer las relaciones entre ellas El siguientecoacutedigo muestra un ejemplo

private static final String NOMBRE_BASE_DATOS = mibasededatosdbprivate static final String TABLA_BASE_DATOS = tablaPrincipal

private static final String CREAR_BASE_DATOS =create table + TABLA_BASE_DATOS + _id integer primare key

autoincrement +columna_uno text not null)

SQLiteDatabase db

private void crearBaseDatos() db = openOrCreateDatabase(NOMBRE_BASE_DATOS ContextMODE_PRIVATE

null)dbexecSQL(CREAR_BASE_DATOS)

NotaAunque no es necesariamente obligatorio es recomendable que cada tabla incluya un campo detipo clave primaria con autoincremento a modo de iacutendice para cada fila En el caso en el que sedesee compartir la base de datos mediante un proveedor de contenidos un campo uacutenico de estetipo es obligatorio

136 Realizar una consulta

El resultado de cualquier consulta a una base de datos seraacute un objeto de la clase CursorEsto permite a Android manejar los recursos de manera maacutes eficiente de tal forma que enlugar de devolver todos los resultados eacutestos se van proporcionando conforme senecesitan

Para realizar una consulta a una base de datos haremos uso del meacutetodo query de lainstancia correspondiente de la clase SQLiteDatabase Los paraacutemetros que requiere este

Persistencia

11Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

meacutetodo son los siguientes

bull Un booleano opcional que permite indicar si el resultado deberiacutea contener tan soacutelovalores uacutenicos

bull El nombre de la tabla sobre la que realizar la consultabull Un array de cadenas que contenta un listado de las columnas que deben incluirse en el

resultadobull Una claacuteusula where que defina las filas que se deben obtener de la tabla Se puede

utilizar el comodiacuten el cual seraacute reemplazado por los valores que se pasen a traveacutesdel siguiente paraacutemetro

bull Un array de cadenas correspondientes a argumentos de seleccioacuten que reemplazaraacutenlos siacutembolos en la claacuteusula where

bull Una claacuteusula group by que define coacutemo se agruparaacuten las filas obtenidas comoresultado

bull Un filtro having que indique que grupos incluir en el resultado en el caso en el que sehaya hecho uso del paraacutemetro anterior

bull Una cadena que describa la ordenacioacuten que se llevaraacute a cabo sobre las filas obtenidascomo resultado

bull Una cadena opcional para definir un liacutemite en el nuacutemero de resultados a devolver

NotaComo se puede observar la mayoriacutea de estos paraacutemetros hacen referencia al lenguaje SQLTratar este tema queda fuera de los objetivos de este curso por lo que en nuestros ejemplos ledaremos en la mayoriacutea de las ocasiones un valor null a estos paraacutemetros

El siguiente coacutedigo muestra un ejemplo de consultas utilizando diversos paraacutemetros

Devolver los valores de las columnas uno y tres para todas las filas sin duplicadosString[] columnas = new String[] CLAVE_ID CLAVE_COL1 CLAVE_COL3

Cursor todasFilas = dbquery(true TABLA_BASE_DATOS columnas null nullnull null

null null)

Devolvemos los valores de todas las columnas para aquellas filas cuyovalor de la columna 3 sea igual al de una determinada variable Ordenamos las filasseguacuten el valor de la columna 5 Como queremos los valores de todas las columnas le damos al paraacutemetro correspondiente a las columnas a devolver el valor null En este caso no se ha hecho uso del primer paraacutemetro booleano que esoptativoString where = CLAVE_COL3 + = + valorString orden = CLAVE_COL5Cursor miResultado = dbquery(TABLA_BASE_DATOS null where null nullnull orden)

137 Extraer resultados de un cursor

Persistencia

12Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El primer paso para obtener resultados de un cursor seraacute desplazarnos a la posicioacuten apartir de la cual queremos obtener los datos usando cualquiera de los meacutetodosespecificados anteriormente (como por ejemplo moveToFirst o moveToPosition) Unavez hecho esto hacemos uso de meacutetodos get[TIPO] pasando como paraacutemetro el iacutendice dela columna Esto tendraacute como resultado la devolucioacuten del valor para dicha columna de lafila actualmente apuntada por el cursor

String valor = miResultadogetString(indiceColumna)

A continuacioacuten podemos ver un ejemplo maacutes completo en el que se va iterando a lo largode los resultados apuntados por un cursor extrayendo y sumando los valores de unacolumna de flotantes

int COLUMNA_VALORES_FLOTANTES = 2Cursor miResultado = dbquery(Tabla null null null null nullnull)float total = 0

Nos aseguramos de que se haya devuelto al menos una filaif (miResultadomoveToFirst())

Iteramos sobre el cursordo

float valor =miResultadogetFloat(COLUMNA_VALORES_FLOTANTES)

total += valor while (miResultadomoveToNext())

float media = total miResultadogetCount()

NotaDebido a que las columnas de las bases de datos en SQLite son debilmente tipadas podemosrealizar castings cuando sea necesario Por ejemplo los valores guardados como flotantes en labase de datos podriacutean leerse maacutes adelante como cadenas

138 Antildeadir actualizar y borrar filas

La clase SQLiteDatabase proporciona los meacutetodos insert delete y update queencapsulan las instrucciones SQL requeridas para llevar a cabo estas acciones Ademaacutesdisponemos de la posibilidad de utilizar el meacutetodo execSQL el cual nos permite ejecutarcualquier sentencia SQL vaacutelida en nuestras tablas de la base de datos en el caso en el quequeramos llevar a cabo estas (u otras) operaciones manualmente

NotaCada vez que modifiques los valores de la base de datos puedes actualizar un cursor que se puedaver afectado por medio del meacutetodo refreshQuery de la clase Cursor

Para insertar una nueva fila es necesario construir un objeto de la clase ContentValues

y usar su meacutetodo put para proporcionar valor a cada una de sus columnas Para insertar la

Persistencia

13Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

nueva fila pasaremos como paraacutemetro este objeto del tipo ContentValues al meacutetodoinsert de la instancia de la base de datos junto por supuesto con el nombre de la tablaA continuacioacuten se muestra un ejemplo

Crear la nueva filaContentValues nuevaFila = new ContentValues()

Asignamos valores a cada columnanuevaFilaput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas ]

Insertar la nueva fila en la tabladbinsert(TABLA_BASE_DATOS null nuevaFila)

NotaLos ficheros como imaacutegenes o ficheros de audio no se suelen almacenar dentro de tablas en unabase de datos En estos casos se suele optar por almacenar en la base de datos una cadena con laruta al fichero o una URI

La operacioacuten de actualizar una fila tambieacuten puede ser llevada a cabo por medio del usode la clase ContentValues En este caso deberemos llamar al meacutetodo update de lainstancia de la base de datos pasando como paraacutemetro el nombre de la tabla el objetoContentValues y una claacuteusula where que especifique la fila o filas a actualizar tal comose puede ver en el siguiente ejemplo

Creamos el objeto utilizado para definir el contenido a actualizarContentValues valoresActualizar = new ContentValues()

Asignamos valores a las columnas correspondientesvaloresActualizarput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas a actualizar ]

String where = CLAVE_ID + = + idFila

Actualizamos la fila con el iacutendice especificado en la instruccioacutenanteriordbupdate(TABLA_BASE_DATOS valoresActualizar where null)

Por uacuteltimo para borrar una fila simplemente llamaremos al meacutetodo delete de lainstancia de la base de datos especificando en este caso el nombre de la tabla que se veraacuteafectada por la operacioacuten y una claacuteusula where que utilizaremos para especificar las filasque queremos borrar Un ejemplo podriacutea ser el siguiente

dbdelete(TABLA_BASE_DATOS CLAVE_ID + = + idFila null)

Persistencia

14Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

2 Ejercicios - Persistencia en Android ficheros y SQLite

21 Uso de ficheros (05 puntos)

En este ejercicio vamos a crear una aplicacioacuten que muestre un listado de cadenas porpantalla Estas cadenas se almacenaraacuten en un fichero de texto privado para la aplicacioacutenPodremos antildeadir nuevas cadenas a partir de un cuadro de edicioacuten presente en la interfazPartiremos del proyecto Ficheros proporcionado en las plantillas de la aplicacioacuten Debesseguir los siguientes pasos

bull Antildeadir un manejador al botoacuten de la actividad principal para que cada vez que seapulsado se guarde en un fichero de texto El fichero se llamaraacute ficherotxt Al abrirel fichero para escritura se utilizaraacute el paraacutemetro ContextMODE_APPEND con lo quecada nueva cadena se antildeadiraacute al final del fichero en el caso en el que eacuteste ya existieraPara escribir en el fichero utilizaremos el meacutetodo writeBytes del objetoDataOutputStream correspondiente

bull Al pulsar el botoacuten tambieacuten se deberaacute borrar el contenido del elemento EditText (leasignamos a la vista una cadena vaciacutea con el meacutetodo setText)

bull Ejecutamos la aplicacioacuten e introducimos algunas liacuteneas en el fichero Vamos acomprobar ahora que todo ha funcionado correctamente Para ello accedemos alsistema de ficheros de nuestro dispositivo ejecutando el comando adb shell

dentro de la carpeta platform-tools de nuestra carpeta de instalacioacuten del SDK deAndroid

bull Comprueba que se ha creado el fichero ficherotxt en la carpetadatadataesuajtechandroidficherosfiles Examina su contenido ejecutando cat

ficherotxt Si algo no ha salido bien siempre podraacutes eliminar el fichero con rm

ficherotxtbull En la interfaz de la actividad se ha incluido una vista de tipo TextView debajo del

botoacuten Antildeade un manejador para dicha vista de tal forma que cada vez que se hagaclick sobre ella se muestre el contenido del fichero ficherotxt Para ello leeremos elfichero liacutenea a liacutenea antildeadiendo cada una al TextView por medio del meacutetodo append

Persistencia

15Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Interfaz de la aplicacioacuten Ficheros

22 Persistencia con ficheros (05 puntos)

Seguramente habraacutes observado que si abandonas la aplicacioacuten anterior (pulsando el botoacutenBACK del dispositivo) al volver a ejecutarla el contenido del TextView ha desaparecidoDebes volver a pulsar sobre la vista para que se vuelva a mostrar el contenido del ficheroHaz las modificaciones pertinentes para que esto no sea asiacute es decir para que cuandovuelva a mostrarse la actividad tras haber sido eliminada de la memoria se muestre elTextView tal cual se veiacutea antes de abandonar la aplicacioacuten

AvisoEn este ejercicio no se estaacute pidiendo que cargues el contenido del fichero en el TextView nadamaacutes arrancar la aplicacioacuten Puede darse el caso de que lo que esteacute mostrando el TextViewcuando la actividad sea eliminada de memoria no se corresponda con el contenido actualizado delfichero

NotaPara poder completar el ejercicio deberaacutes repasar los manejadores de evento de la primera sesioacutendel moacutedulo de Android relacionados con el ciclo de vida de ejecucioacuten de actividades

23 Base de datos SQLiteOpenHelper (05 puntos)

En las plantillas de la aplicacioacuten se incluye la aplicacioacuten BaseDatos En el proyecto de laaplicacioacuten se incluye el esqueleto de una clase MiAdaptadorBD que tendremos quecompletar Se trata de un patroacuten que nos va a permitir acceder a una base de datos de

Persistencia

16Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

usuarios dentro de la aplicacioacuten sin necesidad de hacer uso de coacutedigo SQL

La clase MiAdaptadorBD incluye a su vez otro patroacuten en este caso implementado comouna subclase de SQLiteOpenHelper Eacuteste nos obliga a definir queacute ocurre cuando la basede datos todaviacutea no existe y debe ser creada y queacute ocurre si ya existe pero debe seractualizada porque ha cambiado de versioacuten Asiacute el SQLiteOpenHelper queimplementemos en este caso MiOpenHelper nos devolveraacute siempre una base de datosseparaacutendonos de la loacutegica encargada de comprobar si la base de datos existe o no

En este primer ejercicio se pide hacer lo siguiente

bull Ejecutar la sentencia de creacioacuten de bases de datos (la tenemos declarada comoconstante de la clase) en el meacutetodo MiOpenHelperonCreate

bull Implementar tambieacuten el meacutetodo onUpgrade Idealmente eacuteste deberiacutea portar las tablasde la versioacuten antigua a la versioacuten nueva copiando todos los datos Nosotros vamos aeliminar directamente la tabla que tenemos con la sentencia SQL DROP TABLE IF

EXISTS + NOMBRE_TABLA y volveremos a crearlabull En el constructor de MiAdaptadorBD debemos obtener en el campo db la base de

datos utilizando MiOpenHelper

24 Base de datos insercioacuten y borrado (05 puntos)

Continuamos trabajando con el proyecto anterior En este ejercicio completaremos elcoacutedigo relacionado con las sentencias de insercioacuten y borrado Para realizar la insercioacutenvamos a hacer uso de un mecanismo que no hemos visto en la sesioacuten de teoriacutea y queconsiste en utilizar una sentencia de insercioacuten SQL precompilada que se completaraacute conlos valores concretos a insertar en la base de datos justo antes de realizar dicha insercioacutenLa ventaja de las sentencias compiladas es que evitan que se produzcan ataques deinyeccioacuten de coacutedigo

Como se puede observar se ha incluido un atributo a la clase que representa la insercioacutenSQL

private static final String INSERT = insert into + NOMBRE_TABLA +(+COLUMNAS[1]+) values ()

En esta sentencia no se indica ninguacuten valor concreto para la columna nombre En el lugaren el que deberiacutean aparecer los valores de dicho campo se ha escrito simplemente unsiacutembolo de interrogacioacuten Tambieacuten se ha antildeadido una instancia de la claseSQLiteStatement como parte de los atributos de la clase

private SQLiteStatement insertStatement

Realizamos los siguientes pasos

bull En el constructor de la clase MiAdaptadorBD llevamos a cabo la compilacioacuten de lasentencia

thisinsertStatement = thisdbcompileStatement(INSERT)

Persistencia

17Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Una vez hecho esto cada vez que deseemos insertar un nuevo usuario en la base dedatos deberemos dar valores concretos a la columna nombre y ejecutar la sentenciaPara ello debemos antildeadir el siguiente coacutedigo dentro del meacutetodo insert de la claseMiAdaptadorBD

thisinsertStatementbindString(1 nombreUsuario)return thisinsertStatementexecuteInsert()

bull Por uacuteltimo completamos el coacutedigo del meacutetodo deleteAll cuyo contenido es eliminara todos los usuarios de la base de datos Para que ademaacutes se devuelva el nuacutemero defilas afectadas podemos insertar la siguiente liacutenea en dicho meacutetodo

return dbdelete(NOMBRE_TABLA null null)

Una vez hechos todos estos cambios podremos utilizar la clase MiAdaptadorBD parahacer operaciones con la base de datos de usuarios de manera transparente

25 Base de datos probar nuestro adaptador (05 puntos)

Antildeade coacutedigo en la actividad Main para eliminar todos los usuarios de la base de datosantildeadir dos cualesquiera y listarlos por medio de la vista de tipo TextView que seencuentra en el layout de dicha actividad

Podemos comprobar mediante la liacutenea de comandos (comando adb shell) que la basede datos ha sido creada Para ello puede ser uacutetil hacer uso de los siguientes comandos

cd datadataesuajtechandroidbasedatosdatabasessqlite3 misusuariosdbsqlitegt schemasqlitegt tablessqlitegt select from usuarios

26 Base de datos cambios en la base de datos (05 puntos)

Ahora vamos a cambiar en la clase MiAdaptadorBD el nombre de la segunda columnaque en lugar de nombre se va a llamar nombres Ejecutamos la aplicacioacuten y comprobamosque sale con una excepcioacuten Comprueba por medio de LogCat cuaacutel ha sido el erroriquestCoacutemo lo podemos solucionar

NotaPista conforme hemos programado la clase MiAdaptadorBD y siguiendo el patroacuten de disentildeode SQLiteOpenHelper es posible arreglar el problema simplemente modificando el valor deun campo

Persistencia

18Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

3 Persistencia en Android proveedores de contenidos ySharedPreferences

31 Shared Preferences

Comenzamos esta sesioacuten hablando de una de las teacutecnicas maacutes simples de persistencia enAndroid junto al uso de ficheros Shared Preferences Se trata de un mecanismo ligeropara almacenar datos basado en pares clave-valor utilizado normalmente para guardarpreferencias u opciones de la aplicacioacuten Tambieacuten puede ser utilizado para almacenar elestado de la interfaz graacutefica para contemplar el caso de que nuestra actividad debafinalizar abruptamente su ejecucioacuten Otro posible uso es el de compartir informacioacutenentre componentes de una misma aplicacioacuten

NotaLos Shared Preferences nunca son compartidos entre diferentes aplicaciones

Este meacutetodo se basa en el uso de la clase SharedPreferences Es posible utilizarla paraguardar datos en muy diversos formatos booleanos cadenas flotantes y enteros

311 Guardar Shared Preferences

Para crear o modificar un Shared Preference llamamos al meacutetodogetSharedPreferences del contexto de la aplicacioacuten pasando como paraacutemetro elidentificador del conjunto de valores de preferencias con el que queremos trabajar Pararealizar la modificacioacuten del valor de un Shared Preference hacemos uso de la claseSharedPreferencesEditor Para obtener el objeto editor invocamos el meacutetodo edit

del objeto Shared Preferences que queremos modificar Los cambios se guardan medianteel meacutetodo commit Veamos un ejemplo

int modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences = getSharedPreferences(Mispreferencias modo)

Obtenemos un editor para modificar las preferenciasSharedPreferencesEditor editor = misSharedPreferencesedit()

Guardamos nuevos valores en el objeto SharedPreferenceseditorputBoolean(isTruetrue)editorputFloat(unFloat10)editorputInt(numeroEntero12)editorputLong(otroNumero2)editorputString(unaCadenavalor)

Guardamos los cambioseditorcommit()

Persistencia

19Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

312 Leer Shared Preferences

Para leer Shared Preferences utilizamos el meacutetodo getSharedPreferences tal como seha comentado anteriormente Pasamos como paraacutemetro el identificador del conjunto deShared Preferences al que deseamos acceder Una vez hecho esto ya podemos hacer usode meacutetodos de tipo get para acceder a preferencias individuales Hay diferentes meacutetodosget para diferentes tipos de datos Cada uno de ellos requiere como paraacutemetro la claveque identifica la preferencia cuyo valor deseamos obtener y un valor por defecto el cualse usaraacute en el caso concreto en el que no existiera la preferencia con la clave pasada comoprimer paraacutemetro Podemos ver un ejemplo a continuacioacuten

public static String MIS_PREFS = MIS_PREFS

public void cargarPreferences() Obtener las preferencias almacenadasint modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences =

getSharedPreferences(MIS_PREFS modo)

boolean isTrue = misSharedPreferencesgetBoolean(isTruefalse)float unFloat = misSharedPreferencesgetFloat(unFloat0)int numeroEntero = misSharedPreferencesgetInt(numeroEntero0)long otroNumero = misSharedPreferencesgetLong(otroNumero0)String unaCadena = misSharedPreferencesgetString(unaCadena)

313 Interfaces para Shared Preferences

Android proporciona una plataforma basada en XML para la creacioacuten de interfacesgraacuteficas para el manejo de preferencias Estas interfaces tendraacuten un aspecto similar al delas del resto de aplicaciones del sistema Al utilizarla nos estaremos asegurando de quenuestras actividades para el manejo de preferencias seraacuten consistentes con las del resto deaplicaciones del sistema De esta forma los usuarios estaraacuten familiarizados con el manejode esta parte de nuestra aplicacioacuten

La plataforma consiste en tres elementos

bull Layout de la actividad de preferencias se trata de un archivo XML que definiraacute lainterfaz graacutefica de la actividad que muestre los controles para modificar los valores delas preferencias Permite especificar las vistas a mostrar los posibles valores que sepueden introducir y a queacute clave de Shared Preferences se corresponde cada vista

bull Actividad de preferencias una subclase de PreferenceActivity que secorresponderaacute con la pantalla de preferencias de nuestra aplicacioacuten

bull Shared Preference Change Listener una implementacioacuten de la claseonSharedPreferenceChangeListener que actuaraacute en el caso en el que cambie elvalor de alguna Shared Preference

314 Definiendo una pantalla de preferencias con un layout en XML

Persistencia

20Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esta es sin duda la parte maacutes importante de una actividad de preferencias Se trata de unarchivo XML utilizado para definir varios aspectos de dicha actividad Al contrario queen el caso de los archivos de layout para interfaces graacuteficas que vimos en la sesioacutencorrespondiente del moacutedulo de Android estos archivos de layout se guardan en la carpetaresxml de los recursos de la aplicacioacuten

Aunque conceptualmente estos layouts son similares a los utilizados para definirinterfaces graacuteficas los layouts de actividades de preferencias utilizan un conjuntoespecializado de componentes especiacuteficos para este tipo de actividades Estaacuten pensadospara proporcionar interfaces con un aspecto similar al del resto de actividades depreferencias del sistema Estos controles se describen en maacutes detalle en la siguienteseccioacuten

Los layout de preferencias contendraacuten un elemento PreferenceScreen

ltxml version=10 encoding=utf-8gtltPreferenceScreen

xmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceScreengt

Se pueden antildeadir maacutes elementos de este tipo si se desea Al hacerlo eacutestos serepresentaraacuten como un elemento seleccionable en un listado que mostraraacute una nuevaventana de preferencias en caso de que sea seleccionado

Dentro de cada elemento PreferenceScreen podemos incluir tantos elementos de tipoPreferenceCategory y elementos especiacuteficos para antildeadir controles como se desee Loselementos PreferenceCategory son utilizados para dividir cada pantalla de preferenciasen subcategoriacuteas mediante una barra de tiacutetulo Su sintaxis es la siguiente

ltPreferenceCategoryandroidtitle=My Preference Categorygt

ltPreferenceCategorygt

Una vez dividida la pantalla en subcategoriacuteas tan soacutelo quedariacutea antildeadir los elementoscorrespondientes a los controles especiacuteficos que veremos en la siguiente seccioacuten Losatributos de los elementos XML para estos controles pueden variar aunque existe unconjunto de atributos comunes

bull androidkey la clave de Shared Preference que se usaraacute para guardar el valor delcontrol

bull androidtitle texto a mostrar para describir la preferenciabull androidsummary una descripcioacuten maacutes larga a mostrar debajo del texto definido con

el campo anteriorbull androiddefaultValue el valor por defecto que se mostraraacute inicialmente y tambieacuten

el que finalmente se guardaraacute si ninguacuten otro valor fue asignado a este campo

El siguiente listado muestra una pantalla de preferencias muy simple con una uacutenicacategoriacutea y un uacutenico control (un check box)

ltxml version=10 encoding=utf-8gt

Persistencia

21Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltPreferenceScreenxmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceCategory

androidtitle=Mi categoriacuteagtltCheckBoxPreference

androidkey=MI_CHECK_BOXandroidtitle=Preferencia Check Boxandroidsummary=Descripcioacuten de la preferencia

Check BoxandroiddefaultValue=true

gtltPreferenceCategorygt

ltPreferenceScreengt

Esta pantalla de preferencias se mostrariacutea de la siguiente forma

Ejemplo sencillo de ventana de preferencias

315 Controles nativos para preferencias

Android incluye diferentes controles que podemos antildeadir a nuestras ventanas depreferencias por medio de los elementos XML que se indican a continuacioacuten

bull CheckBoxPreference un check box estaacutendar que puede ser utilizado parapreferencias de tipo booleano

bull EditTextPreference permite al usuario introducir una cadena como valor para unapreferencia Al seleccionar el texto se mostraraacute un diaacutelogo con el que introducir elnuevo valor

bull ListPreference el equivalente a un Spinner Seleccionar este elemento de lainterfaz mostraraacute un diaacutelogo con las diferentes opciones que es posible seleccionar Seutilizan arrays para asociar valores y textos a las opciones de la lista

bull RingtonePreference un tipo especiacutefico de preferencia de tipo listado que permiteseleccionar entre diferentes tonos de teleacutefono Esta opcioacuten es particularmente uacutetil en

Persistencia

22Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

el caso en el que se esteacute creando una pantalla de preferencias para configurar lasopciones de notificacioacuten de una aplicacioacuten o componente de la misma

Tambieacuten es posible crear nuestros propios controles personalizados definiendo unasubclase de Preference (o cualquiera de sus subclases) Se puede encontrar maacutesinformacioacuten en la documentacioacuten de AndroidhttpdeveloperandroidcomreferenceandroidpreferencePreferencehtml

316 Actividades de preferencias

Para mostrar una pantalla de preferencias debemos definir una subclase dePreferenceActivity

public class MisPreferencias extends PreferenceActivity

La interfaz graacutefica de la interfaz se puede crear a partir del layout en el meacutetodo onCreatemediante una llamada a addPreferencesFromResource

Overridepublic void onCreate(Bundle savedInstaceState)

superonCreate(savedInstanceState)addPreferencesFromResource(Rxmlmilayout)

Como en el caso de cualquier otra actividad la actividad de preferencias que hayamosdefinido debe ser incluida en el Manifest de la aplicacioacuten

ltactivity androidname=MisPreferenciasandroidlabel=Mis preferenciasgt

ltactivitygt

Eso es todo lo que necesitamos para crear este tipo de actividades y mostrar una ventanacon las preferencias definidas en el layout Esta actividad puede ser iniciada comocualquier otra mediante un Intent ya sea a traveacutes de una llamada a startActivity obien a startActivityForResult

Intent i = new Intent(this MisPreferenciasclass)startActivityForResult(i MOSTRAR_PREFERENCIAS)

Los valores para las diferentes preferencias de la actividad son almacenadas en elcontexto de la aplicacioacuten Esto permite a cualquier componente de dicha aplicacioacutenincluyendo a cualquier actividad y servicio de la misma acceder a los valores tal como semuestra en el siguiente coacutedigo de ejemplo

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto) PENDIENTE hacer uso de meacutetodos get para obtener los valores

317 Shared Preference Change Listeners

Persistencia

23Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El uacuteltimo elemento que nos queda por examinar en esta plataforma de Shared Preferenceses la interfaz onSharedPreferenceChangeListener que es utilizada para invocar unevento cada vez que una preferencia es antildeadida eliminada o modificada Para registrarListeners usamos un coacutedigo como el que se muestra a continuacioacuten en el que la parte maacutesimportante es sin duda el meacutetodo onSharedPreferenceChanged dentro del cualdeberemos determinar queacute preferencia ha cambiado de estado a partir de sus paraacutemetrospara llevar a cabo las acciones oportunas

public class MiActividad extends Activity implementsOnSharedPreferenceChangeListener

Overridepublic void onCreate(Bundle SavedInstanceState)

Registramos este objetoOnSharedPreferenceChangeListener

Context contexto = getApplicationContext()SharedPreferences prefs =

PreferenceManagergetDefaultSharedPreferences(contexto)prefsregisterOnSharedPreferenceChangeListener(this)

public void onSharedPreferenceChanged(SharedPreferences prefsString clave)

PENDIENTE comprobar queacute clave concreta ha cambiado ysu nuevo valor

para modificar la interfaz de una actividad o elcomportamiento de

alguacuten componente

Otra forma maacutes sencilla de controlar los cambios en las preferencias es registrar unlistener en el constructor de la actividad principal o aquella que haga uso de los valores depreferencias de la forma

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto)

mListener = new OnSharedPreferenceChangeListener() public void onSharedPreferenceChanged(SharedPreferences

sharedPreferences String key)

if( keyequals(MICLAVE) )boolean valor =

sharedPreferencesgetBoolean(MICLAVE false)

prefsregisterOnSharedPreferenceChangeListener(mListener)

NotaEn este segundo ejemplo mListener es una variable miembro de la clase Para este caso esnecesario registrar el listener de esta forma porque sino el garbage collector lo eliminariacutea (debidoa particularidades en la programacioacuten de los listeners asociados a las SharedPreferences)

Persistencia

24Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

32 Proveedores de contenidos

Los proveedores de contenidos o ContentProvider proporcionan una interfaz parapublicar y consumir datos identificando la fuente de datos con una direccioacuten URI queempieza por content Son una forma maacutes estaacutendar que los adaptadores (como el quevimos en la sesioacuten anterior en la seccioacuten dedicada a SQLite) de desacoplar la capa deaplicacioacuten de la capa de datos

Podemos encontrar dos tipos principales de proveedores de contenidos en Android Losproveedores nativos son proveedores que el propio sistema nos proporciona para poderacceder a datos del dispostivo incluyendo audio viacutedeo imaacutegenes informacioacuten personaletc Por otra parte tambieacuten es posible que creemos nuestros propios proveedores con talde permitir a nuestra aplicacioacuten compartir datos con el resto del sistema En esta sesioacutenhablaremos de ambos tipos

321 Proveedores nativos

Android incluye una serie de proveedores de contenidos que nos pueden proporcionarinformacioacuten de diferentes tipos informacioacuten sobre nuestros contactos informacioacuten delcalendario e incluso ficheros multimedia Ejemplos de estos proveedores de contenidosnativos son el Browser CallLog ContactsContract MediaStore Settings y elUserDictionary Para poder hacer uso de estos proveedores de contenidos y acceder alos datos que nos proporcionan debemos antildeadir los permisos adecuados al ficheroAndroidManifestxml Por ejemplo para acceder al listiacuten telefoacutenico podemos antildeadir elpermiso correspondiente de la siguiente forma

ltuses-sdk androidminSdkVersion=8 gtltuses-permission androidname=androidpermissionREAD_CONTACTSgt

ltmanifestgt

Para obtener informacioacuten de un ContentProvider debemos hacer uso del meacutetodo query

de la clase ContentResolver El primero de los paraacutemetros de dicho meacutetodo seraacute la URIdel proveedor de contenidos al que deseamos acceder Este meacutetodo devuelve un cursorque nos permitiraacute iterar entre los resultados tal como se vio en la sesioacuten anterior en elapartado de cursores

ContentResolverquery(Uri uriString[] projectionString selectionString[] selectionArgsString sortOrder)

Por ejemplo la siguiente llamada nos proporcionaraacute un cursor que nos permitiraacute acceder atodos los contactos de nuestro teleacutefono moacutevil Para ello hemos hecho uso de la constante

Persistencia

25Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ContactsContractContactsCONTENT_URI que almacena la URI del proveedor decontenidos correspondiente Al resto de paraacutemetros se le ha asignado el valor null

ContentResolver cr = getContentResolver()Cursor cursor = crquery(ContactsContractContactsCONTENT_URI

null null null null)

NotaEn versiones anteriores a Android 20 las estructuras de datos de los contactos son diferentes y nose accede a esta URI

Una vez obtenido el cursor es posible mapear sus campos con alguacuten componente de lainterfaz graacutefica de la actividad De esta forma cualquier cambio que se produzca en elcursor se reflejaraacute automaacuteticamente en el componente graacutefico sin que sea necesario queprogramemos el coacutedigo que lo refresque Para esto tenemos que llamar al meacutetodosetNotificationUri del cursor con lo cual nos aseguraremos de que los listeners seraacutennotificados cuando se produzca alguacuten cambio en los datos referenciados por dicho cursor

cursorsetNotificationUri(crContactsContractContactsCONTENT_URI)

Para asignar un adaptador a una lista de la interfaz graacutefica de la actividad maacutesconcretamente una ListView utilizamos el meacutetodo setAdapter(Adapter)

ListView lv = (ListView)findViewById(RidListView01)SimpleCursorAdapter adapter = new SimpleCursorAdapter(

getApplicationContext()Rlayouttextviewlayoutcursornew String[]

ContactsContractContacts_IDContactsContractContactsDISPLAY_NAME

new int[]RidTextView1RidTextView2)

lvsetAdapter(adapter)

En este ejemplo los identificadores RidTextView1 y RidTextView2 se correspondencon views del layout que define cada fila de la ListView como se puede ver en elarchivo textviewlayoutxml que tendriacuteamos que crear

ltxml version=10 encoding=utf-8gt

ltLinearLayout xmlnsandroid=httpschemasandroidcomapkresandroidandroidorientation=horizontalandroidlayout_width=fill_parentandroidlayout_height=wrap_contentgt

ltTextView androidid=+idTextView1androidtextStyle=bold

Persistencia

26Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

androidems=2androidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltTextView androidid=+idTextView2androidtextStyle=boldandroidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltLinearLayoutgt

La lista en siacute (identificada por RidListView01 en el presente ejemplo) estariacutea en otroXML layout por ejemplo en mainxml

Se debe tener en cuenta que en el caso de este ejemplo si quisieacuteramos obtener losnuacutemeros de teleacutefono de cada uno de nuestros contactos tendriacuteamos que recorrer el cursorobtenido a partir del proveedor de contenidos y por cada persona en nuestra agenda crearun nuevo cursor que recorriera los teleacutefonos ya que una persona puede tener asignadosvarios teleacutefonos

Puedes acceder a un listado completo de los proveedores de contenidos nativos existentesen Android consultando su manual de desarrollo Concretamente leyendo la seccioacutendedicada al paquete androidprovider enhttpdeveloperandroidcomreferenceandroidproviderpackage-summaryhtml

322 Proveedores propios crear un nuevo proveedor de contenidos

Para acceder a nuestras fuentes de datos propias de manera estaacutendar nos interesaimplementar nuestros propios ContentProvider Gracias a que proporcionamos nuestrosdatos a partir de una URI quien use nuestro proveedor de contenidos no deberaacutepreocuparse de doacutende provienen los datos indicados por dicha URI podriacutean provenir deficheros locales en la tarjeta de memoria de un servidor en Internet o de una base dedatos SQLite Dada una URI nuestro proveedor de contenidos deberaacute proporcionar en suinterfaz los meacutetodos necesarios para realizar las operaciones baacutesicas en una base de datosinsercioacuten lectura actualizacioacuten y borrado

Para crear un nuevo proveedor de contenidos definimos una subclase deContentProvider Utilizaremos una sobrecarga del meacutetodo onCreate para crear einicializar la fuente de datos que queramos hacer puacuteblica a traveacutes de dicho proveedor Unesqueleto de subclase de ContentProvide podriacutea ser el siguiente

public class MiProveedor extends ContentProvider

Overridepublic boolean onCreate()

Inicializar la base de datosreturn true

Dentro de la clase deberemos crear un atributo puacuteblico y estaacutetico de nombreCONTENT_URI en el cual almacenaremos el URI completo del proveedor que estamos

Persistencia

27Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creando Este URI debe ser uacutenico por lo que una buena idea puede ser tomar como baseel nombre de paquete de nuestra aplicacioacuten La forma general de un URI es la siguiente

content[NOMBRE_PAQUETE]provider[NOMBRE_APLICACION][RUTA_DATOS]

Por ejemplo

contentesuajtechandroidprovidermiaplicacionelementos

En general (y por simplicidad) no es necesario antildeadir la cadena provider al nombre delpaquete por lo que podemos utilizar directamente el nombre del paquete seguido de laruta de datos de la forma

contentesuajtechandroidmiaplicacionelementos

Estos URIs se pueden presentar en dos formas La URI anterior representa una consultadirigida a obtener todos los valores de ese tipo (en este caso todos los elementos) Si a laURI anterior antildeadimos un sufijo [NUMERO_FILA] estamos representando una consultadestinada a obtener un uacutenico elemento (por ejemplo el quinto elemento en el siguienteejemplo)

contentesuajtechandroidprovidermiaplicacionelementos5

NotaSe considera una buena praacutectica de programacioacuten el proporcionar acceso a nuestro proveedor decontenidos utilizando cualquiera de estas dos formas

La forma maacutes simple de determinar cuaacutel de las dos formas anteriores de URI se utilizoacutepara hacer una peticioacuten a nuestro proveedor de contenidos es mediante un Uri MatcherCrearemos y configuramos un elemento Uri Matcher para analizar una URI y determinara cuaacutel de las dos formas se corresponde En el siguiente coacutedigo se muestra el coacutedigobaacutesico que deberemos emplear para implementar este patroacuten

public class MiProveedor extends ContentProvider

private static final String miURI =contentesuajtechandroidprovidermiaplicacionelementos

public static final Uri CONTENT_URI = Uriparse(miUri)

Creamos las constantes que nos van a permitir diferenciar entre los dos tipos distintos de peticiones a partir de la URIprivate static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

Inicializamos el objeto UriMatcher Una URI que termine en elementos se corresponderaacute con una consulta para todas las filas mientras que una URI con el sufijo elementos[FILA] representaraacute una uacutenica filastatic

uriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidprovidermiaplicacion

elementos TODAS_FILAS)

Persistencia

28Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

uriMatcheraddURI(esuajtechandroidprovidermiaplicacionelementos UNA_FILA)

Overridepublic boolean onCreate()

PENDIENTE inicializar la base de datosreturn true

Esta misma teacutecnica se puede emplear para proporcionar diferentes alternativas de URIpara diferentes subconjuntos de datos como por ejemplo diferentes tablas en una base dedatos a traveacutes del mismo proveedor de contenidos

323 Proveedores propios crear la interfaz de consultas

Para permitir realizar operaciones con los datos publicados a traveacutes de nuestro proveedorde contenidos deberemos implementar los meacutetodos delete insert update y query

para el borrado insercioacuten actualizacioacuten y realizacioacuten de consultas respectivamenteEstos meacutetodos son la interfaz estaacutendar utilizada con proveedores de contenidos de formaque para compartir datos entre aplicaciones no se tendraacuten interfaces distintos paradiferentes fuentes de datos

Lo maacutes habitual suele ser implementar un proveedor de contenidos como un mecanismopara permitir el acceso a una base de datos SQLite privada a una aplicacioacuten aunque atraveacutes de estos meacutetodos indicados se podriacutea en principio acceder a cualquier fuente dedatos

El siguiente coacutedigo extiende el ejemplo anterior (MiProveedor) y muestra el esqueleto deestos meacutetodos dentro de una subclase de ContentProvider Obseacutervese que se hace usodel objeto UriMatcher para determinar que tipo de consulta se estaacute realizando

Overridepublic Cursor query(Uri uri

String[] projectionString selectionString[] selectionArgsString sort)

Si se trata de una consulta para obtener una uacutenica filalimitamos

los resultados a obtener de la fuente de datos por medio de una claacuteusula whereswitch(UriMatchermatch(uri))

case UNA_FILA PENDIENTE modifica la sentencia SELECT

mediante una claacuteusula where obteniendo el identificador de fila

como numeroFila = urigetPathSegments()get(1))

return null

Override

Persistencia

29Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public Uri insert(Uri _uri ContentValues _valores) long idFila = [ Antildeadir un nuevo elemento ]

Devolvemos la URI del elemento recieacuten antildeadidoif (idFila gt 0)

return ContentUriswithAppendendId(CONTENT_URI idFila)

throw new SQLException(No se pudo antildeadir un nuevo elemento a +_uri)

Overridepublic int delete(Uri uri String where String[] whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

Overridepublic int update(Uri uri ContentValues valores String where String[]whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

324 Proveedores propios tipo MIME

El uacuteltimo paso para crear un proveedor de contenidos es definir el tipo MIME queidentifica el tipo de datos que devuelve el proveedor Debemos sobrecargar el meacutetodogetType para que devuelva una cadena que identifique nuestro tipo de datos de manerauacutenica Se deberiacutean definir dos tipos posibles uno para el caso de una uacutenica fila y otro parael caso de devolver todos los resultados Se debe utilizar una sintaxis similar a la delsiguiente ejemplo

bull Un uacutenico elementovndesuajtechandroidmiaplicacioncursoritemmiproveedorcontent

bull Todos los elementosvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

Como se puede observar la cadena comienza siempre por vnd A continuacioacutentendriacuteamos el nombre del paquete de nuestra aplicacioacuten Lo siguiente es cursor ya quenuestro proveedor de contenidos estaacute devolviendo datos de tipo Cursor Justo antes de labarra pondremos item en el caso del tipo MIME para un uacutenico elemento y dir para elcaso del tipo MIME para todos los elementos Finalmente tras la barra pondremos elnombre de nuestra clase (en minuacutesculas) seguido de content En el siguiente coacutedigo deejemplo se muestra coacutemo sobrecargar getType seguacuten la URI

Persistencia

30Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Overridepublic String getType(Uri _uri)

switch (uriMatchermatch(_uri)) case TODAS_FILAS

returnvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

case UNA_FILAreturn

vndesuajtechandroidmiaplicacioncursoritemmiproveedorcontentdefault

throw new IllegalArgumentException(URI nosoportada + _uri)

325 Proveedores propios registrar el proveedor

No debemos olvidar incorporar nuestro proveedor de contenidos al Manifest de laaplicacioacuten una vez que lo hemos completado Esto se haraacute por medio del elementoprovider cuyo atributo authorities podremos utilizar para especificar la URI base talcomo se puede ver en el siguiente ejemplo

ltprovider androidname=MiProveedorandroidauthorities=esuajtechandroidmiaplicaciongt

326 Content Resolvers

Aunque en la seccioacuten de proveedores nativos ya se ha dado alguacuten ejemplo de coacutemo haceruso de un Content Resolver para realizar una consulta ahora que hemos podido observaren detalle la estructura de un proveedor de contenidos propio es un buen momento paraver en detalle coacutemo realizar cada una de las operaciones que eacutestos permiten

El contexto de una aplicacioacuten incluye siempre una instancia de la claseContentResolver a la cual se puede acceder mediante el meacutetodo getContentResolver

ContentResolver cr = getContentResolver()

Esta clase incorpora diversos meacutetodos para realizar consultas a proveedores decontenidos Cada uno de estos meacutetodos acepta como paraacutemetro una URI que indica conqueacute proveedor de contenidos se desea interactuar

Las consultas realizadas sobre un proveedor de contenidos recuerdan a las consultasrealizadas sobre bases de datos Los resultados de las consultas se devuelven comoobjetos de la clase Cursor Se pueden extraer valores del cursor de la misma forma en laque se explicoacute en el apartado de bases de datos de la sesioacuten anterior El meacutetodo query dela clase ContentResolver acepta los siguientes paraacutemetros

bull La URI del proveedor del contenidos al que se desea realizar la consultabull Una proyeccioacuten que enumera las columnas que se quieren incluir en los resultados

obtenidosbull Una claacuteusula where que define las filas a obtener Se pueden utilizar comodines que

Persistencia

31Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

se reemplazaraacuten por valores incluidos en el siguiente paraacutemetrobull Una matriz de cadenas que se pueden utilizar para reemplazar los comodines en la

claacuteusula where anteriorbull Una cadena que describa la ordenacioacuten de los elementos devueltos

El siguiente coacutedigo muestra un ejemplo

ContentResolver cr = getContentResolver() Devolver todas las filasCursor todasFilas = crquery(MiProveedorCONTENT_URI null null nullnull) Devolver todas columnas de las filas para las que la columna 3 tiene un valor determinado ordenadas seguacuten el valor de la columna 5String where = COL3 + = + valorString orden = COL5Cursor algunasFilas = crquery(MiProveedorCONTENT_URI null where nullorden)

327 Otras operaciones con Content Resolvers

Para realizar otro tipo de operaciones con los datos de un proveedor de contenidosharemos uso de los meacutetodos delete update e insert de un objeto ContentResolver

Con respecto a la insercioacuten la clase ContentResolver proporciona en su interfaz dosmeacutetodos diferentes insert y bulkInsert Ambos aceptan como paraacutemetro la URI delproveedor de contenidos pero mientras que el primer meacutetodo tan solo toma comoparaacutemetro un objeto de la clase ContentValues (ya vistos en la seccioacuten de bases dedatos) el segundo toma como paraacutemetro una matriz de elementos de este tipo El meacutetodoinsert devolveraacute la URI del elemento recieacuten antildeadido mientras que bulkInsert

devolveraacute el nuacutemero de filas correctamente insertadas

Obtener el Content ResolverContentResolver cr = getContentResolver()

Crear una nueva fila de valores a insertarContentValues nuevosValores = new ContentValues()

Asignar valores a cada columnanuevosValoresput(NOMBRE_COLUMNA nuevoValor)[ Repetir para cada columna ]

Uri miFilaUri = crinsert(MiProveedorCONTENT_URI nuevosValores)

Insertamos ahora una matriz de nuevas filasContentValues[] arrayValores = new ContentValues[5] PENDIENTE rellenar el array con los valores correspondientesint count = crbulkInsert(MiProveedorCONTENT_URI arrayValores)

Para el caso del borrado hacemos uso del meacutetodo delete del Content Resolver pasandocomo paraacutemetro la URI de la fila que deseamos eliminar de la fuente de datos Otraposibilidad es incluir una claacuteusula where para eliminar muacuteltiples filas Ambas teacutecnicas semuestran en el siguiente ejemplo

ContentResolver cr = getContentResolver()

Persistencia

32Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Eliminar una fila especiacuteficacrdelete(miFilaUri null null) Eliminar las primeras cinco filasString where = _id lt 5crdelete(MiProveedorCONTENT_URI where null)

Una actualizacioacuten se lleva a cabo mediante el meacutetodo update del objeto ContentResolver Este meacutetodo toma como paraacutemetro la URI del proveedor de contenidosobjetivo un objeto de la clase ContentValues que empareja nombres de columna convalores actualizados y una claacuteusula where que indica queacute filas deben ser actualizadas Elresultado de la actualizacioacuten seraacute que en cada fila en la que se cumpla la condicioacuten de laclaacuteusula where se cambiaraacuten los valores de las columnas especificados por el objeto de laclase ContentValues tambieacuten se devolveraacute el nuacutemero de filas correctamenteactualizadas A continuacioacuten se muestra un ejemplo

ContentValues nuevosValores = new ContentValues()

Utilizamos el objeto ContentValues para especificar en queacute columnas queremos que se produzca un cambio y cuaacutel debe ser el nuevo valor de dichas columnasnuevosValuesput(NOMBRE_COLUMNA nuevoValor)

Aplicamos los cambios a las primeras cinco filasString where = _id lt 5

getContentResolver()update(MiProveedorCONTENT_URI nuevosValores wherenull)

Persistencia

33Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

4 Ejercicios - Persistencia en Android proveedores de contenidos ySharedPreferences

41 Compartir datos entre actividades con Shared Preferences (075 puntos)

Descarga de las plantillas el proyecto Dni Dicho proyecto estaacute compuesto de dosactividades Formulario y Resumen El objetivo del ejercicio es conseguir que al pulsar elbotoacuten Siguiente en la actividad Formulario se muestre en la actividad Resumen cuaacutelesfueron los datos introducidos

En este ejercicio vamos a hacer uso de SharedPreferences para pasar los datos de unaactividad a la siguiente Al pulsar en Siguiente en Formulario deberemos guardar el valorde todos los campos mediante esta plataforma Al mostrarse la actividad Resumendeberemos leer estos datos tambieacuten a partir de SharedPreferences para mostrarlos porpantalla

Por uacuteltimo crea un meacutetodo que valide el DNI (debe tratarse de una secuencia de ochodiacutegitos entre 0 y 9 y una letra al final) Si se pulsa Siguiente y el DNI no tiene el formatocorrecto no se deberaacute pasar a la actividad Resumen En lugar de esto se mostraraacute unToast en pantalla con un mensaje de error

42 Actividad de preferencias (075 puntos)

En este ejercicio seguimos trabajando con el proyecto del ejercicio anterior Vamos aantildeadir un menuacute de opciones que permita escoger en queacute momento se comprobaraacute lavalidez del DNI Para ello antildeade en primer lugar un menuacute a la actividad Formulario conuna uacutenica opcioacuten cuyo nombre seraacute Opciones

Al seleccionar esta opcioacuten se deberaacute mostrar una actividad de preferencias cuyo aspectoseraacute el siguiente

Persistencia

34Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Preferencias de la aplicacioacuten Dni

El significado de las opciones seraacute el siguiente

bull En el campo con esta opcioacuten seleccionada al pulsar sobre el campo de DNI en elformulario deberaacute mostrarse un Toast indicando si la sintaxis del DNI es correcta ono

bull Al pulsar si se desactiva esta opcioacuten (que deberaacute estar activada por defecto) no seharaacute la comprobacioacuten del DNI al pulsar el botoacuten Siguiente por lo que siempre seraacuteposible pasar de la actividad Formulario a la actividad Resumen sea cual sea elformato del DNI introducido

bull El uacuteltimo check box se encontraraacute siempre deshabilitado iquestQueacute atributo debemosutilizar para conseguir esto

Crea dos variables booleanas llamadas enElCampo y alPulsar Su valor dependeraacute delestado de los checkboxes anteriores y se actualizaraacute por medio del eventoonSharedPreferenceChange Utiliza el valor de estas variables en el coacutedigo de tuactividad para que esta tenga el comportamiento indicado

43 Proveedor de contenidos propio (075 puntos)

Vamos a implementar otra forma de acceder a la base de datos de usuarios de laaplicacioacuten BaseDatos desarrollada durante los ejercicios de la sesioacuten anterior siguiendoesta vez el patroacuten de disentildeo ContentProvider de Android Seguiremos trabajando puescon dicho proyecto

bull Creamos una nueva clase llamada UsuariosProvider que herede deContentProvider Esto nos obligaraacute a sobrecargar una serie de meacutetodos abstractosAntes de implementar la query vamos a configurar el provider

bull Antildeadimos algunos campos tiacutepicos de los content provider

Persistencia

35Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public static final Uri CONTENT_URI =Uriparse(contentesuajtechandroidbasedatosusuarios)

private static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

staticuriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

TODAS_FILAS)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

UNA_FILA)

bull Vamos a acceder a la misma base de datos de usuarios utilizada en los ejercicios de lasesioacuten anterior pero no vamos a hacerlo a traveacutes del adaptador que tuvimos queimplementar sino que vamos a copiar de eacutel el coacutedigo que nos haga falta Copia loscampos que definen el nombre de la base de datos de la tabla de las columnas laversioacuten asiacute como la referencia al contexto y a la base de datos La sentenciacompilada del insert ya no va a hacer falta Inicializa los valores necesarios en elconstructor Para inicializar la referencia a la base de datos vamos a utilizar una vezmaacutes MiOpenHelper Podemos copiarlo del adaptador que ya implementamos en losejercicios de la sesioacuten anterior

bull Implementa de forma apropiada el getType para devolver un tipo MIME diferenteseguacuten si se trata de una URI de una fila o de todas las filas Para ello ayuacutedate deluriMatcher

bull Implementa el meacutetodo query Simplemente se trata de devolver el cursor queobtenemos al hacer una consulta a la base de datos SQLite Algunos de los paraacutemetrosque le pasaremos los recibimos como paraacutemetros del meacutetodo del provider Los que notengamos iraacuten con valor null

bull Aunque no tenemos todos los meacutetodos del UsuariosProvider implementadospodemos probarlo Para ello debemos registarlo en el AndroidManifestxml

ltprovider androidname=UsuariosProvider

androidauthorities=esuajtechandroidbasedatosgtltapplicationgt

ltmanifestgt

bull En la clase Main se antildeadioacute codigo para insertar una serie de valores en la base dedatos y mostrarlos en el campo de texto Manteniendo este coacutedigo vamos a antildeadir alcampo de texto el resultado obtenido con la consulta del UsuariosProvider paracomprobar que tanto el adaptador de la base de datos como el proveedor nosdevuelven el mismo resultado

44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)

Porque es la forma estaacutendar que establece Android de acceder a contenidos Ademaacutes elproveedor de contenidos nos permitiraacute notificar al ContentResolver de los cambiosocurridos Asiacute componentes en la pantalla podraacuten refrescarse de forma automaacutetica

Persistencia

36Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Utiliza el proyecto ProveedorContenidos de las plantillas Implementa la insercioacuten enel proveedor de contenidos Prueacutebala insertando algunos usuarios de ejemplo en laclase Main Implementa tambieacuten el OnClickListener del botoacuten que inserta nuevosusuarios El nombre del nuevo usuario iraacute indicado en el EditText

bull Comprueba que la insercioacuten funciona y que gracias a la siguiente liacutenea y al estarusando un proveedor de contenidos la lista se actualiza automaacuteticamente cuandoocurre alguacuten cambio sin necesidad de pedir expliacutecitamente la actualizacioacuten al pulsarel botoacuten

cursorsetNotificationUri(cr UsuariosProviderCONTENT_URI)

NotaEsto no va a funcionar si se notifica que se ha producido un cambio al insertar o borrar unelemento Para ello usamosgetContext()getContentResolver()notifyChange(UsuariosProviderCONTENT_URI null)

bull Implementa el meacutetodo delete del proveedor de contenidos Prueacutebalo en la claseMain Termina de implementar el onCreateContextMenuListener que se ejecutaraacutecada vez que se haga una pulsacioacuten larga sobre alguna entrada de la lista Compruebaque funciona (eliminando el usuario correspondiente y no otro)

Persistencia

37Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

5 Persistencia de datos en iOS Ficheros y SQLite

51 Introduccioacuten

La persistencia de datos en iOS se realiza en una pequentildea memoria flash que tienenuestro dispositivo la cual es equivalente a un disco duro de tamantildeo limitado Los datosque ahiacute se almacenen se conservaraacuten aunque el dispositivo se apague Por motivos deseguridad el SO de iOS no permite que las aplicaciones accedan directamente a lamemoria interna pero a cambio existe una API completa de acceso a la porcioacuten dememoria que le corresponde a la app que desarrollemos

En una aplicacioacuten iOS encontraremos baacutesicamente 4 modos de almacenamiento deinformacioacuten de forma persistente

bull Ficheros de propiedades (plist)bull Bases de datos embebidas SQLitebull Datos de usuario (User Defaults)bull Core Data

En esta primera sesioacuten veremos coacutemo usar los ficheros de texto (Property Lists) y elalmacenamiento en base de datos de tipo SQLite

52 Ficheros plist (Property Lists)

El almacenamiento de datos en ficheros de propiedades es uno de los maacutes usados en eldesarrollo de las aplicaciones para iOS Se usa principalmente para guardar datos deconfiguracioacuten de la aplicacioacuten u otra informacioacuten poco compleja y acceder a ella deforma sencilla Si estamos desarrollando un juego podemos usarlos para la definicioacuten delos niveles almacenar las puntuaciones del jugador etc

521 Leyendo datos desde ficheros plist

Los ficheros plist (Property Lists) tienen un formato XML aunque XCode nosproporciona una interfaz que nos permite acceder a ellos de forma muy sencilla Paraentender el funcionamiento de este tipo de ficheros vamos a realizar un ejemplo en el queprimero crearemos un fichero plist lo completaremos con datos de ejemplo y despueacutes loleeremos para mostrarlo por pantalla

Un ejemplo de un fichero plist podriacutea ser el siguiente

Persistencia

38Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Fichero plist

Antes de nada creamos un proyecto nuevo en XCode de tipo Window-based

Application al que llamaremos sesion03-ejemplo1 Para crear un fichero plistdebemos hacer click derecho sobre el directorio Supporting Files de nuestro proyectoy seleccionamos iOS gt Resource gt Property List Lo guardaremos con el nombreconfigUsuario Seguidamente ya podemos acceder al fichero recieacuten creado haciendoclick sobre el en este momento XCode nos mostraraacute un pequentildeo editor totalmente vacioen el que iremos rellenando con datos Podemos verlo tambieacuten en formato XML sihacemos click derecho sobre el y seleccionamos Open As gt Preview

Para empezar a rellenar el fichero que acabamos de crear hacemos click derecho dentrode su contenido (ahora vacio) y seleccionamos Add row Entonces nos apareceraacute unafila vaciacutea Dentro del campo Key escribimos la clave un nombre descriptivo quedeberemos utilizar maacutes adelante para acceder a los datos En nuestro caso vamos aescribir config como clave Dentro de la columna Type seleccionamosDictionary Veremos que aparece una flecha en el lado izquierdo que indica quepueden haber subniveles dentro de nuestro diccionario

Ahora antildeadimos un nuevo item que cuelgue del diccionario que acabamos de crear paraello hacemos click sobre la flecha de la izquierda esta se giraraacute hacia abajo entonceshacemos click ahora sobre el siacutembolo + (maacutes) Nos apareceraacute una nueva liacutenea que cuelgadel diccionario

En esta nueva liacutenea dentro del campo key escribimos nombre el campo type lodejamos como de tipo String ya que va a ser una cadena de texto y en la columna deValue escribimos nuestro nombre por ejemplo Javi

Ahora vamos a antildeadir otro campo nuevo en nuestra configuracioacuten para ello hacemosclick sobre el siacutembolo + y nos apareceraacute una nueva liacutenea justo al mismo nivel que laanterior y colgando del diccionario Dentro del campo de Key escribimos ciudad yen Value escribimos Alicante El campo de Type lo dejamos como String

Persistencia

39Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora dentro de nuestro fichero plist vamos a indicar los dispositivos que dispone elusuario para ello creamos una nueva linea dentro del diccionario y le pondremos comoclave dispositivos Seleccionamos el tipo Array y seguimos los mismos pasosque al crear el diccionario para ello hacemos click sobre la flecha de la izquierda ydespueacutes sobre el siacutembolo + (maacutes) Ahora nos apareceraacute una nueva fila con clave Item 0Seleccionamos el tipo String y en el campo Value iPhone

Ahora repetimos el paso 3 veces indicando como values iPad Android y

Blackberry

Una vez seguidos todos estos pasos tendremos nuestro fichero de propiedades listo Debede quedar como se muestra a continuacioacuten

Fichero plist terminado

Este fichero se almacenaraacute dentro del directorio de bundle cuando compilemos laaplicacioacuten En el caso de que queramos escribir en el debermos almacenar el ficherodentro del directorio de Documents del binario

Ahora desde nuestro coacutedigo vamos a acceder a eacutel y a mostrarlo por pantalla para elloescribimos el siguiente coacutedigo dentro del meacutetodo didFinishLaunchingWithOptions dela clase delegada

Cargamos el fichero PLISTNSString mainBundlePath = [[NSBundle mainBundle] bundlePath]NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]NSDictionary diccionario = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionario delraiz

NSString nombre = [dicConfig objectForKeynombre]

Persistencia

40Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

[selfwindow makeKeyAndVisible]return YES

Si todo ha ido correctamente deberemos obtener la siguiente salida por la consola

Lista de dispositivos

522 Escribiendo datos en ficheros plist

Partiendo del ejemplo anterior vamos a escribir ahora en el fichero para ello primerodebemos comprobar que el fichero plist se encuentre dentro del directorio de Documents

del dispositivo en el caso de que no lo esteacute (por ejemplo si es la primera vez que arrancala aplicacioacuten) debemos copiarlo a ese directorio Una vez que tenemos el fichero dentrodel directorio de documentos ya podemos escribir en el

Para realizar todo el proceso de comprobacioacuten de la existencia del fichero dentro deldirectorio de documentos copiarlo en ese directorio y leer los datos debemos de sustituirel coacutedigo que hay dentro del meacutetodo didFinishLaunchingWithOptions de la clasedelegada por este otro

AtencioacutenPara que funcione la escritura en un fichero plist este debe de estar dentro del directorio deDocuments de nuestro dispositivo iOS no en el boundle

NSDictionary diccionarioRaiz

Ruta del directorio de documentos de nuestro dispositivoNSArray paths =

NSSearchPathForDirectoriesInDomains(NSDocumentDirectoryNSUserDomainMask YES)

if ([paths count] gt 0)

Persistencia

41Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

Primero comprobamos si existe el fichero en eldirectorio de documentos

BOOL fileExists = [[NSFileManager defaultManager]fileExistsAtPathdocumentsFilename]if (fileExists)

NSLog(Fichero encontrado en directorio de documentosOK

documentsFilename)

Si existe ya cargamos los datos desde aquidirectamente

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

else

NSLog(No se encuentra el fichero configUsuarioplisten

el directorio de documentos-gtLo creamos)

Si no existe primero cargamos el fichero PLISTdesde el Boundle

NSString mainBundlePath = [[NSBundle mainBundle]bundlePath]

NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Y despues escribimos los datos cargados (eldiccionario completo)

a un fichero nuevo de la carpeta de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

NSLog(plist de configUsuario creado)

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionariodel raiz

NSString nombre = [dicConfig objectForKeynombre]NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

Persistencia

42Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Por ultimo liberamos la memoria del diccionario raiz[diccionarioRaiz release]

[selfwindow makeKeyAndVisible]return YES

Para comprobar el funcionamiento arrancamos la aplicacioacuten y veremos que la primera vezse crearaacute el fichero dentro de la carpeta de Documents de nuestro dispositivo y en lassiguientes ya no se crearaacute y simplemente lo leeraacute desde la carpeta de Documents

Ahora que ya tenemos la gestioacuten de ficheros baacutesica completada vamos a escribir sobre elalguacuten dato Para ello simplemente escribimos el siguiente fragmento de coacutedigo justo antesde la liacutenea [selfwindow makeKeyAndVisible]

Cambiamos el nombre y lo guardamos en el fichero Ruta del directorio de documentos de nuestro dispositivo

if ([paths count] gt 0)

Cargamos el fichero desde el directorio dedocumentos del dispositivo

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cambiamos el valor del nombre en el diccionario[dicConfig setValueOtro nombre forKeynombre]

Escribimos todo el diccionario de nuevo al fichero del directorio de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

Por ultimo liberamos el diccionario de lamemoria

[diccionarioRaiz release]

Lo que hemos hecho en el coacutedigo anterior es cargar todo el diccionario de nuevo desde eldirectorio de documentos modificar los datos que queremos cambiar o incluso antildeadir ydespueacutes guardar el diccionario en el fichero

Volvemos a arrancar la aplicacioacuten y veremos que ha cambiado el campo de nombre

Nota

Persistencia

43Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

La lectura y escritura de datos en un fichero plist se debe de hacer de una soacutela vez nunca porpartes Siempre se debe de cargar o escribir un diccionario NSDictionary como elementoprincipal del fichero

53 SQLite

SQLite es una base de datos relacional ligera con la que se puede trabajar de formasencilla mediante sentencias SQL La base de datos se almacenaraacute en un fichero dentrode nuestra aplicacioacuten Este meacutetodo de persistencia puede ser el adecuado cuando los datosa almacenar esteacuten en un formato de tablas filas y columnas y se necesite que los datossean accesibles de forma raacutepida

531 Herramientas disponibles

Para explicar el funcionamiento de las librerias de SQLite de Cocoa Touch vamos a seguirun ejemplo completo Comenzamos creando la base de datos para ello podemos utilizaruno de los frontends que facilitan la gestioacuten del SQLite uno bastante sencillo yrecomendado es el SQLite Database Browser y otro es fmdb

Nosotros utilizaremos el primero (SQLite Database Browser) Abrimos la aplicacioacuten yhacemos click sobre el botoacuten de crear nueva base de datos que llamaremospersonasDBsqlite Seguidamente creamos las tablas para simplificar en nuestroejemplo crearemos una soacutela tabla llamada personas Esta tabla tendraacute las siguientescolumnas id (NUMERIC) nombre (TEXT) apellidos (TEXT) dni (TEXT) edad

(NUMERIC) y localidad (TEXT)

Persistencia

44Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Estructura de la Base de Datos

Una vez creada la tabla pasamos a insertar datos dentro de ella para ello abrimos lapestantildea de Browse Data y hacemos click en New Record Cuando tengamos al menos 3registros metidos dentro de la tabla ya podemos guardar la base de datos Hacemos clicksobre guardar le ponemos el nombre que queramos y elegimos cualquier directorio delMAC

Datos de la Base de Datos

532 Lectura de datos

Una vez que tenemos la base de datos SQLite creada ya podemos empezar a escribir elcoacutedigo para leer y mostrar los datos dentro de nuestra aplicacioacuten

Primero empezamos creando un proyecto nuevo en XCode usando la plantilla basada envistas View-based application y lo llamaremos sesion03-ejemplo2 Ahora antildeadimosel framework de sqlite a nuestro proyecto para ello hacemos click sobre el raiz delproyecto y seleccionamos la pestantildea Build Phases Desplegamos el listado de Link

Binary With Libraries y hacemos click sobre el botoacuten (+) El el listadoseleccionamos el framework libsqlite3dylib y hacemos click en Add

Persistencia

45Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Antildeadiendo el framework libsqlite3dylib

Ahora antildeadimos a nuestro proyecto la base de datos que hemos creado anteriormentepara ello arrastramos el fichero personasDBsqlite a nuestro directorio Supporting

Files Asegurate de seleccionar la opcioacuten de Copy items to destination groups

folder (if needed) para que se copie el fichero dentro de la carpeta del proyecto

Para acelerar el proceso de lectura de la base de datos y ahorrar memoria vamos a cargarprimero todos los datos en objetos para ello vamos a crear una clase que se encargue dealmacenar los datos Hacemos click derecho sobre el raiz del proyecto New file gtObjective-C class seleccionamos NSObject como clase principal y le damos a NextEscribimos como nombre de la clase Persona y hacemos click en save

Personah

imports iniciales

interface Persona NSObject int _uIdNSString _nombreNSString _apellidos

Persistencia

46Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSInteger _edadNSString _localidad

property (nonatomic assign) int uIdproperty (nonatomic copy) NSString nombreproperty (nonatomic copy) NSString apellidosproperty (nonatomic) NSInteger edadproperty (nonatomic copy) NSString localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidos edad(NSInteger)edadlocalidad(NSString )localidad

end

Personam

import Personah

implementation Persona

synthesize uId = _uIdsynthesize nombre = _nombresynthesize apellidos = _apellidossynthesize edad = _edadsynthesize localidad = _localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidosedad(NSInteger)edad localidad(NSString )localidad

if ((self = [super init])) selfuId = uIdselfnombre = nombreselfapellidos = apellidosselfedad = edadselflocalidad = localidad

return self

- (void) dealloc selfnombre = nilselfapellidos = nilselflocalidad = nil[super dealloc]

end

Una vez creada la clase Persona vamos a crear la clase encargada de manejar la basede datos sqlite3 Otra opcioacuten seriacutea cargar todos los datos directamente desde la clasedelegada al arrancar la aplicacioacuten pero no es recomendable ya que si en alguacuten momentotenemos que cambiar de motor de base de datos lo tendremos maacutes complicado

Persistencia

47Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creamos entonces una clase dentro de nuestro proyecto llamada BDPersistencia queherede de NSObject y escribimos el siguiente coacutedigo

BDPersistenciah

imports iniciales

interface BDPersistencia NSObject sqlite3 _database

+ (BDPersistencia)database- (NSArray )arrayPersonas

end

BDPersistenciam

import BDPersistenciahimport Personah

implementation BDPersistencia

static BDPersistencia _database

+ (BDPersistencia)database if (_database == nil)

_database = [[BDPersistencia alloc] init]return _database

- (id)init if ((self = [super init]))

NSString sqLiteDb = [[NSBundle mainBundle]pathForResourcepersonasDB

ofTypesqlite]

if (sqlite3_open([sqLiteDb UTF8String]amp_database) = SQLITE_OK)

NSLog(Fallo al abrir la BD)

return self

- (void)dealloc sqlite3_close(_database)[super dealloc]

- (NSArray )arrayPersonas

NSMutableArray arraySalida = [[[NSMutableArray alloc]init]

autorelease]

Persistencia

48Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString query = SELECT id nombre apellidoslocalidad edad

FROM personas ORDER BY apellidos DESC

sqlite3_stmt statementif (sqlite3_prepare_v2(_database [query UTF8String]

-1ampstatement nil)

== SQLITE_OK) while (sqlite3_step(statement) == SQLITE_ROW)

int uniqueId = sqlite3_column_int(statement0)

char nombreChar = (char )sqlite3_column_text(statement 1)

char apellidosChar = (char )sqlite3_column_text(statement 2)

char localidadChar = (char )sqlite3_column_text(statement 3)

int edad = sqlite3_column_int(statement 4)

NSString nombre = [[NSString alloc]initWithUTF8StringnombreChar]NSString apellidos = [[NSString alloc]initWithUTF8StringapellidosChar]NSString localidad = [[NSString alloc]initWithUTF8StringlocalidadChar]Persona persona = [[Persona alloc]initWithUiduniqueId nombrenombreapellidosapellidos edadedad

localidadlocalidad]

[arraySalida addObjectpersona][nombre release][apellidos release][localidad release][persona release]

sqlite3_finalize(statement)

return arraySalida

end

Con esto ya podemos leer de nuestra base de datos las personas Para ver que funcionatodo a la perfeccioacuten escribimos el siguiente coacutedigo dentro de la clase delegada en elmeacutetodo applicationDidFinishLaunching Antes debemos incluir en la parte superiorlas clases creadas

import BDPersistenciahimport Personah

NSArray personas = [BDPersistenciadatabase]arrayPersonas

for (Persona persona in personas) NSLog(d d personauId

personanombrepersonaapellidos personalocalidad personaedad)

Persistencia

49Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Si ejecutamos el proyecto obtendremos la siguiente salida por consola

Listado de personas

533 Mostrando los datos en una tabla

Una vez que tenemos el coacutedigo para obtener todas las filas de la tabla de personas de labase de datos queda lo maacutes faacutecil mostrarlas dentro de una vista de la aplicacioacuten En estecaso utilizaremos la vista de tabla UITableView

Comenzamos creando una nueva vista en nuestro proyecto para ello hacemos clickderecho sobre el raiz y seleccionamos New File gt UIVIewController SubclassAsegurate de marcar Subclass of UITableViewController y la opcoacuten de With

XIB for user interface Hacemos click en Next y guardamos el fichero comoPersonasViewController por ejemplo

Ahora abrimos el fichero PersonasViewControllerh y antildeadimos un NSArray queseraacute el que almacene las personas de la base de datos

imports iniciales

interface PersonasViewController UITableViewController

NSArray _listadoPersonas

property (nonatomic retain) NSArray listadoPersonas

end

Ahora abrimos el fichero PersonasViewControllerm antildeadimos el syntetizeimportamos los ficheros de gestioacuten de la base de datos la clase Persona y todo el coacutedigobaacutesico para la gestioacuten de la vista de la tabla

import PersonasViewControllerhimport Personahimport BDPersistenciah

implementation PersonasViewController

synthesize listadoPersonas = _listadoPersonas

Persistencia

50Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

- (id)initWithStyle(UITableViewStyle)style

self = [super initWithStylestyle]if (self)

Custom initializationreturn self

- (void)dealloc

[super dealloc]

selflistadoPersonas = nil

- (void)didReceiveMemoryWarning

Releases the view if it doesnt have a superview[super didReceiveMemoryWarning]

Release any cached data images etc that arent inuse

pragma mark - View lifecycle

- (void)viewDidLoad

[super viewDidLoad]

selftitle = Listado de personas

selflistadoPersonas = [BDPersistenciadatabase]arrayPersonas

- (void)viewDidUnload

[super viewDidUnload] Release any retained subviews of the main view eg selfmyOutlet = nil

- (void)viewWillAppear(BOOL)animated

[super viewWillAppearanimated]

- (void)viewDidAppear(BOOL)animated

[super viewDidAppearanimated]

- (void)viewWillDisappear(BOOL)animated

[super viewWillDisappearanimated]

- (void)viewDidDisappear(BOOL)animated

[super viewDidDisappearanimated]

- (BOOL)shouldAutorotateToInterfaceOrientation

Persistencia

51Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

(UIInterfaceOrientation)interfaceOrientation

Return YES for supported orientationsreturn (interfaceOrientation ==

UIInterfaceOrientationPortrait)

pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView(UITableView)tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection

(NSInteger)section

Return the number of rows in the sectionreturn [selflistadoPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath

(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]if (cell == nil)

cell = [[[UITableViewCell alloc]initWithStyleUITableViewCellStyleSubtitle

reuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona persona = (Persona )[selflistadoPersonas

objectAtIndexindexPathrow]celltextLabeltext = [NSString stringWithFormat

(d) personanombrepersonaapellidos personaedad]celldetailTextLabeltext = personalocalidad

return cell

pragma mark - Table view delegate

- (void)tableView(UITableView )tableViewdidSelectRowAtIndexPath

(NSIndexPath )indexPath

Navigation logic may go here Create and pushanother view controller

end

Persistencia

52Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora soacutelo nos queda incorporar la vista que acabamos de crear dentro de nuestraaplicacioacuten Para ello vamos a llamarla desde el Delegate y a meterla dentro de unNavigation Controller Primero debemos de antildeadir un Outlet en la clasedelegada hacia un Navigation Controller

imports iniciales

interface sesion03_ejemplo1AppDelegate NSObjectltUIApplicationDelegategt

UIWindow _windowUINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet

UINavigationController navController

end

Ahora hacemos click sobre la vista MainWindowxib y arrastramos un Navigation

Controller desde el listado de la columna de la derecha hacia la columna de laizquierda donde pone Objects Nos apareceraacute el Navigation Controller dibujadoen pantalla Ahora desplegamos el Navigation Controller y hacemos click sobre elView Controller que hay dentro Nos vamos a la pestantildea de Identity Inspector

de la columna de la derecha y escribimos dentro de Class el nombre de la vista dellistado de personas PersonasViewController

Finalmente hacemos click sobre el objeto Delegate vamos a la pestantildea de Show

Connections Inspector y arrastramos desde navController hasta el objetoNavigation Controller de la columna de la izquierda Ya tenemos los controladoresy las vistas conectadas

Una vez hecho esto nos queda indicar dentro de nuestro coacutedigo que la aplicacioacuten arranquecon el Navigation Controller para ello escribimos lo siguiente al final del meacutetododidFinishLaunchingWithOptions de la implementacioacuten de la clase delegada

selfwindowrootViewController = selfnavController[selfwindow makeKeyAndVisible]return YES

Ya estaacute listo todo y preparado para compilar Si ejecutamos el proyecto veremos que nosaparece el listado de personas en la vista de tabla

Persistencia

53Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios

61 Creando y leyendo un fichero plist (15 puntos)

En este primer ejercicio vamos a crear nuestra primera aplicacioacuten usando el meacutetodo depersistencia de ficheros La aplicacioacuten mostraraacute en una vista de tabla TableView unlistado de series en la que si seleccionamos una veremos su informacioacuten maacutes importanteen otra vista Comenzaremos creando un fichero de propiedades (plist) siguiendo unaestructura determinada y posteriormente mostraremos en una vista de tabla todos susdatos iexclComenzamos

1) Crear un proyecto usando la plantilla Master-Detail application Lo llamaremosejercicio-plist-ios como identificador de empresa escribiremos esuajtech y como prefijode clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremos UseAutomatic Reference Counting para olvidarnos de la gestioacuten de memoria El resto deopciones las dejaremos sin marcar

2) Crear un archivo de propiedades (plist) con el nombre de series

3) Editar el archivo plist creando la siguiente estructura de datos (5 series miacutenimo)

Plist series

4) Rellenar el archivo plist con datos de series reales

5) Escribir el coacutedigo necesario en UAMasterViewControllerm que recorra el archivoplist que acabamos de crear y muestre por consola los titulos de las series

6) Mostrar en la tabla que hay implementada los tiacutetulos de la serie Para ello deberemosde completar todos los meacutetodos del protocolo del TableView

7) Ejecutar la aplicacioacuten y comprobar que funciona correctamente

Persistencia

54Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

62 Implementar vista de detalle de la serie (05 puntos)

Dentro del proyecto anterior (no hace falta crear uno nuevo) vamos a mostrar toda lainformacioacuten adicional de cada una de las series en una vista de detalle Cuandoseleccionemos una fila de la tabla nos deberaacute aparecer una nueva vista con el resto deinformacioacuten (antildeo director principal sinopsis y duracioacuten)

63 Uso baacutesico de una base de datos SQLite (1 punto)

En este ejercicio vamos a crear la misma aplicacioacuten que en el ejercicio anterior perousando el meacutetodo de persistencia de SQLite Para completar el ejercicio deberemos seguirlos siguientes pasos

1) Crear un proyecto usando la plantilla Master-Detail application lo llamaremosejercicio-sqlite-ios como identificador de empresa escribiremos esuajtech y comoprefijo de clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremosUse Automatic Reference Counting para olvidarnos de la gestioacuten de memoria El restode opciones las dejaremos sin marcar

2) Nos descargamos e instalamos el programa SQLite Database Browser desde estadireccioacuten

3) Disentildear la estructura de la base de datos usando SQLite Database Browser tal y comose muestra en la imagen siguiente

Sqlite series

4) Insertar al menos 5 series en la base de datos usando el programa SQLite DatabaseBrowser

5) Cargar todos los datos de la base de datos desde el meacutetododidFinishLaunchingWithOptions de la clase UAAppDelegate Mostrar por consola losdatos cargados

6) Mostrar los datos cargados en el paso anterior en la vista de tabla UITableView quehay creada en la clase UAMasterViewController

Persistencia

55Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7) Arrancar la aplicacioacuten y comprobar que se muestran las series en la tabla de formacorrecta

Persistencia

56Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7 Persistencia de datos en iOS User Defaults y Core Data

71 Introduccioacuten

En esta sesioacuten veremos como utilizar dos nuevos meacutetodos de persistencia en iOS ambosmuy interesantes y usados muy frecuentemente

bull User Defaults Muy uacutetil para almacenar datos simples y de uso repetido dentro denuestras aplicaciones iOS

bull Core Data Meacutetodo avanzado de almacenamiento de objetos enteros dentro de lamemoria del dispositivo iOS Muy uacutetil para almacenar datos complejos conrelaciones etc

72 User Defaults

Este meacutetodo es muy uacutetil cuando queremos almacenar pequentildeas cantidades de datos comopuntuaciones informacioacuten de usuario el estado de nuestra aplicacioacuten configuracionesetc

Este meacutetodo es el maacutes raacutepido de usar ya que no requiere de una base de datos ni ficherosni ninguna otra capa intermedia Los datos se almacenan directamente en la memoria deldispositivo dentro de un objeto de la clase NSUserDefaults

Para aprender a usar este meacutetodo de almacenamiento de datos vamos a crear unaaplicacioacuten muy simple que lea un nombre y una edad de pantalla y las guarde en unobjeto NSUserDefaults Para ello seguimos los siguientes pasos

bull 1) Comenzamos creando un nuevo proyecto llamado sesion04-ejemplo1 dentrode XCode

bull 2) Abrimos la vista sesion04_ejemplo1ViewController y arrastramos dos TextField un botoacuten y dos labels quedando la vista de la siguiente manera

Persistencia

57Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista de la aplicacioacuten

bull 3) Abrimos el fichero sesion04_ejemplo1ViewControllerh y definimos el Outletdel evento onclick del botoacuten El fichero quedaraacute de la siguiente manera

imports iniciales

interface sesion04_ejemplo1ViewController UIViewController UITextField tfNombreUITextField tfEdad

property(nonatomic retain) IBOutlet UITextField tfNombreproperty(nonatomic retain) IBOutlet UITextField tfEdad

-(IBAction) guardaDatos

end

bull 4) Ahora implementamos el coacutedigo para el meacutetodo del botoacuten dentro desesion04_ejemplo1ViewControllerm

synthesize tfEdadsynthesize tfNombre

-(IBAction) guardaDatos NSLog(Guardamos los datos)

Persistencia

58Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creamos el objeto NSUserDefaultsNSUserDefaults datos = [NSUserDefaults standardUserDefaults]

guardamos el nombre (NSString)[datos setObjecttfNombretext forKeynombre]

guardamos la edad (Integer)[datos setInteger[tfEdadtext integerValue] forKeyedad]

almacenamos los datos en la memoria[datos synchronize]

bull 5) Tenemos que enlazar el botoacuten con el evento que hemos programado en el pasoanterior para ello abrimos la vista sesion04_ejemplo1ViewControllerxib y conla tecla ctlr apretada arrastramos desde el botoacuten hasta Files Owner (columna de laizquierda parte superior) y seleccionamos el meacutetodo que hemos creado(guardaDatos) De esta forma ya tenemos conectado el botoacuten

bull 6) Ahora hacemos lo mismo con los Text Field Tenemos que conectarlos alcontrolador para ello seleccionamos el objeto Files Owner y abrimos la pestantildeade conexiones (la que estaacute maacutes a la derecha en la columna de la derecha) Hacemosclick en tfEdad y arrastramos hasta el Text Field de edad Hacemos lo mismo para eltext field de nombre Ya tenemos todos los elementos de la vista conectados

Conexioacuten de Outlets

bull 7) Arrancamos la aplicacioacuten y la probamos

Persistencia

59Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Aplicacioacuten

bull 8) Ahora vamos a hacer que la aplicacioacuten cargue los datos que se han guardado alarrancar la vista para ello simplemente tenemos que escribir el siguiente coacutedigo en elmeacutetodo (void)viewDidLoad de la clase sesion04_ejemplo1ViewControllerm

- (void)viewDidLoad

[super viewDidLoad]

NSUserDefaults datos = [NSUserDefaults standardUserDefaults]

obtenemos un NSStringNSString nombre = [datos stringForKeynombre]

obtenemos un NSIntegerNSInteger edad = [datos integerForKeyedad]

if (nombre=nil ampamp edad=0)NSLog(Nombre cargado edad dnombreedad)

else

NSLog(Sin datos)

bull 9) Ya podemos arrancar de nuevo la aplicacioacuten y veremos que si escribimos cualquier

Persistencia

60Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

texto en los Text Fields estos se almacenaraacuten dentro del objeto NSUserDefaults Alapagar la aplicacioacuten y volver a arrancarla estos datos se cargaraacuten (apareceraacuten enconsola)

Consola

NotaLos datos permaneceraacuten en nuestro dispositivo hasta que se elimine la aplicacioacuten en ese casotodos los datos guardados en NSUserDefaults se borraraacuten

bull 10) Probar eliminar la aplicacioacuten del dispositivosimulador y volver arrancarComprobamos que los datos se han borrado

Consola

73 Core Data

De todas las formas de persistencia de datos que existen en iOS Core Data es la maacutesapropiada para usar en caso de manejar datos no triviales con relaciones algo maacutescomplejas entre ellos El uso de Core Data dentro de nuestra aplicacioacuten optimiza almaacuteximo el consumo de memoria mejora el tiempo de respuesta y reduce la cantidad decoacutedigo redundante a escribir

Core Data utiliza de fondo el meacutetodo de almacenamiento SQLite visto en la sesioacuten 3 deeste moacutedulo por tanto para explicar el funcionamiento de este meacutetodo de persistenciavamos a utilizar el mismo modelo de datos que usamos en esa sesioacuten Persona -gtDetallePersona

El el siguiente ejemplo haremos la misma aplicacioacuten que hicimos con SQLite perousando este nuevo meacutetodo de persistencia

731 Creando el proyecto

Para empezar tenemos que crearnos un nuevo proyecto en XCode Para ello abrimosXCode y seleccionamos crear una nueva aplicacioacuten basada en ventanas (Window-basedapplication) Hacemos click en Next escribimos como nombre del proyectosesion04-ejemplo2 y seleccionamos Use Core Data

Persistencia

61Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creando un proyecto nuevo

Con esto ya tenemos la estructura baacutesica para empezar a programar Antes de nadahechamos un vistazo raacutepido a toda la estructura de directorios creada y vemos que dentrodel directorio principal tenemos un archivo que se llamasesion04_ejemplo2xcdatamodeld Al hacer click sobre el archivo se nos abre uneditor especial que en principio estaacute vacio y que maacutes adelante utilizaremos para disentildearnuestro modelo de datos

Ahora hechamos un vistazo a la clase delegada sesion04_ejemplo2AppDelegatemvemos que hay unos cuantos meacutetodos nuevos que serviraacuten para configurar el Core DataVamos a explicar los maacutes importantes

bull - (NSManagedObjectModel )managedObjectModel NSManagedObjectModel esla clase que contiene la definicioacuten de cada uno de los objetos o entidades quealmacenamos en la base de datos Normalmente este meacutetodo no lo utilizaremos ya quenosotros vamos a utilizar el editor que hemos visto antes con el que podremos crearnuevas entidades crear sus atributos y relaciones

bull - (NSPersistentStoreCoordinator )persistentStoreCoordinator Aquiacute esdonde configuramos los nombres y las ubicaciones de las bases de datos que se usaraacutenpara almacenar los objetos Cuando un managed object necesite guardar algo pasaraacutepor este meacutetodo

bull - (NSManagedObjectContext )managedObjectContext Esta claseNSManagedObjectContext seraacute la maacutes usada con diferencia de las tres y por tanto la

Persistencia

62Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

maacutes importante La utilizaremos baacutesicamente para obtener objetos insertarlos oborrarlos

732 Definiendo el modelo de datos

En el ejemplo que hicimos en la sesioacuten anterior de SQLite creamos dos tablas una paralos datos baacutesicos de la persona y otra para el detalle Ahora haremos lo mismo perousando Core Data la diferencia principal es que mientras que con SQLite obteniamos soacutelolos datos que necesitaacutebamos de la base de datos aquiacute obtenemos el objeto completo

Para definir nuestro modelo de datos basado en dos objetos (Persona y DetallePersona)abrimos el editor visual haciendo click sobre el ficherosesion04_ejemplo2xcdatamodeld Empezaremos creando una nueva entidad para ellohacemos click sobre el botoacuten Add Entity (+) que se encuentra en la parte inferior dela pantalla A la nueva entidad le pondremos como nombre Persona

Dentro de la seccioacuten de Attributes hacemos click sobre (+) y creamos un nuevoatributo para nuestra entidad le llamamos nombre y le asignamos como tipo StringRealizamos este mismo paso 2 veces maacutes para antildeadir apellidos y edad este uacutetlimode tipo Integer 16

Atributos Persona

Ahora creamos la entidad de DetallePersona de la misma manera que la anterior perocreando los atributos localidad pais direccion cp y telefono Todasde tipo String

Atributos DetallePersona

Finalmente nos queda relacionar de alguacuten modo las dos entidades para ello antildeadimos una

Persistencia

63Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

relacioacuten dentro de la entidad de Persona hacia DetallePersona simplementehaciendo click sobre el botoacuten (+) de la seccioacuten de Relationships A la relacioacuten leponemos como nombre detalle seleccionamos como destino la entidadDetallePersona y seraacute No inverse Este tipo de relacioacuten es uno a uno Por dentroCore Data realizaraacute crearaacute una clave ajena en la tabla de persona que apunte aDetallePersona pero eso a nosotros no nos interesa

Vista general modelo de datos (1)

Apple recomienda que siempre que hagamos una relacioacuten de una entidad a otra hagamostambieacuten la relacioacuten inversa por lo tanto hacemos justo lo anterior pero al reveacutes (deDetallePersona a Persona) con la diferencia que en la columna de Inverse

seleccionamos datosPersona

Ya tenemos las entidades y las relaciones creadas Si hacemos click en el botoacuten deEditor Style en la parte de abajo a la derecha del editor podemos ver de una formamaacutes clara la estructura que hemos disentildeado

Persistencia

64Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista general modelo de datos (2)

733 Probando el modelo

Una vez que tenemos el modelo creado pasamos a probarlo para comprobar que estaacute todocorrecto para ello abrimos la clase delegada (sesion04_ejemplo2AppDelegatem) yantildeadimos el siguiente fragmento de coacutedigo arriba del meacutetodoapplicationDidFinishLaunching

NSManagedObjectContext context = [self managedObjectContext]NSManagedObject persona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

[persona setValueJuan forKeynombre][persona setValueMartinez forKeyapellidos][persona setValue[NSNumber numberWithInt28] forKeyedad]

NSManagedObject detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersonainManagedObjectContextcontext]

[detallePersona setValueCalle Falsa 123 forKeydireccion][detallePersona setValueAlicante forKeylocalidad][detallePersona setValueEspantildea forKeypais][detallePersona setValue965656565 forKeytelefono]

[detallePersona setValuepersona forKeydatosPersona][persona setValuedetallePersona forKeydetalle]

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Persistencia

65Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Con este coacutedigo hemos creado un objeto Persona enlazado con otro objetoDetallePersona iexclComo puedes ver no hemos necesitado nada de coacutedigo SQLPrimero creamos un objeto del tipo NSManagedObjectContext que almacena el conextode Core Data Despueacutes creamos el objeto Persona en siacute con NSManagedObject A esteobjeto le pondremos los valores para cada una de las propiedades definidas en el modeloHacemos lo mismo con DetallePersona Finalmente relacionamos el objetoPersona con el objeto DetallePersona

Compilamos el proyecto comprobamos que todo funciona bien y no salen errores

Para comprobar que los datos se han almacenado correctamente en nuestro dispositivo losmostramos en la consola de la siguiente manera

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

intValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(------------------)

[fetchRequest release]

El coacutedigo de arriba lo escribiremos dentro del meacutetododidFinishLaunchingWithOptions de la clase delegada Lo que hace este coacutedigo esrecorrer todos los objetos almacenados en memoria y mostrarlos por consola Siarrancamos la aplicacioacuten veremos que se muestran todos los datos de los objetosalmacenados Cada vez que arranquemos la aplicacioacuten se mostraraacuten maacutes objetos ya quetambieacuten los creamos

Para listar los objetos usamos la clase NSFetchRequest Esta clase es equivalente aejecutar una sentencia Select de SQL Al objeto NSFetchRequest le asignamos laentidad que queremos listar (SELECT FROM ) en nuestro caso seraacute PersonaEl listado de los objetos se obtiene mediante el meacutetodo executeFetchRequest de laclase NSManagedObjectContext que hemos definido en el bloque anterior de coacutedigoEste meacutetodo devolveraacute un NSArray con todos los objetos NSManagedObject

Persistencia

66Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Salida de consola

NotaSi queremos ver queacute sentencias SQL estaacute ejecutando Core Data por debajo tenemos que activarel flag de debug para ello seleccionamos la opcioacuten Product gt Edit Scheme Enla columna de la izquierda seleccionamos el esquema que estamos utilizando para Debug y en laparte de la derecha en el cuadro de Arguments Passed on Launch antildeadimos 2paraacutemetros -comappleCoreDataSQLDebug y 1

Ahora arrancamos de nuevo la aplicacioacuten y veremos que en la consola apareceraacuten lassentencias SQL utilizadas por Core Data

()

INSERT INTO ZDETALLEPERSONA(Z_PK Z_ENT Z_OPT ZDATOSPERSONA ZTELEFONOZLOCALIDAD ZDIRECCION ZPAIS)VALUES( )CoreData sql SELECT 0 t0Z_PK t0Z_OPT t0ZTELEFONO t0ZLOCALIDADt0ZDIRECCION t0ZPAIS t0ZDATOSPERSONAFROM ZDETALLEPERSONA t0 WHERE t0Z_PK =

()

734 Generando los modelos automaacuteticamente

Generar los modelos a mano directamente desde el editor no es lo maacutes recomendado yaque podemos cometer errores de escritura errores de tipo etc La mejor manera dehacerlo es creando un archivo para cada entidad Esto se puede hacer desde 0 peroXCode tiene un generador de clases que es muy faacutecil de usar y ahorremos bastantetiempo iexclVamos a usarlo

Hacemos click sobre el raiz de nuestro proyecto y seleccionamos New File En lacolumna de la izquierda seleccionamos iOS gt Core Data y en el cuadro de laderecha NSManagedObject subclass Click en next En la siguiente pantallaseleccionamos nuestro modelo de datos sesion04_ejemplo2 y click en next Ahoranos aseguramos de seleccionar las dos entidades que hemos creado anteriormentePersona y DetallePersona click en next Seleccionamos el raiz de nuestroproyecto para guardar los ficheros que se generen y aceptamos

Persistencia

67Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ventana de seleccioacuten de entidad

Ahora veremos que xsource ha creado automaacuteticamente 4 ficheros nuevos dentro denuestro proyecto Personamh y DetallePersonamh con todos los atributosespecificados Estas nuevas clases que heredan de NSManagedObject seraacuten las queutilicemos a partir de ahora

Cambiamos el coacutedigo de la clase delegada por el siguiente

NSManagedObjectContext context = [self managedObjectContext]Persona datosPersona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

datosPersonanombre = JuandatosPersonaapellidos = MartinezdatosPersonaedad = [NSNumber numberWithInt28]

DetallePersona detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersona

inManagedObjectContextcontext]detallePersonadireccion = Calle Falsa 123

Persistencia

68Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

detallePersonalocalidad = AlicantedetallePersonapais = EspantildeadetallePersonatelefono = 965656565

detallePersonadatosPersona = datosPersonadatosPersonadetalle = detallePersona

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Listamos los datosNSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

integerValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(-----------------)[fetchRequest release]

Ejecutamos el proyecto y comprobamos que en la consola aparece lo mismo que antespero ahora tenemos el coacutedigo bastante maacutes claro

735 Creando la vista de tabla

Ahora vamos a mostrar los objetos que antes hemos listado por consola en una vista detabla UITableView Para ello creamos un UITableViewController

Click en New file gt UIViewController subclass En la siguiente pantallanos aseguramos de seleccionar Subclass of UITableViewController de esta formanos apareceraacuten ya todos los meacutetodos de la tabla creados por defecto Guardamos elarchivo con el nombre que queramos por ejemplo PersonasViewController

Abrimos ahora PersonasViewControllerh y antildeadimos 2 atributos a la clase

bull Un NSArray que se seraacute el listado de personas (objetos de la clase Persona)bull Una referencia a NSManagedObjectContext

El fichero PersonasViewControllerh quedaraacute de la siguiente manera

Persistencia

69Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

imports iniciales

interface PersonasViewController UITableViewController NSArray _listaPersonasNSManagedObjectContext _context

property (nonatomic retain) NSArray listaPersonasproperty (nonatomic retain) NSManagedObjectContext context

end

Ahora en el m importamos en el encabezado las clases Personah yDetallePersonah y en el meacutetodo viewDidLoad creamos el array listaPersonas contodos los objetos Persona de forma similar a cuando lo hicimos en el apartado anterior

import PersonasViewControllerh

import Personahimport DetallePersonah

implementation PersonasViewController

synthesize listaPersonas = _listaPersonassynthesize context = _context

En viewDidLoad

- (void)viewDidLoad

[super viewDidLoad]

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContext_context]

[fetchRequest setEntityentity]NSError errorselflistaPersonas = [_context executeFetchRequestfetchRequest

erroramperror]selftitle = Listado Personas[fetchRequest release]

Ahora completamos el resto de meacutetodos heredados de UITableViewController de lamisma forma que hicimos en sesiones anteriores

- (NSInteger)numberOfSectionsInTableView(UITableView )tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection(NSInteger)section

Persistencia

70Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Return the number of rows in the sectionreturn [_listaPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]

if (cell == nil) cell = [[[UITableViewCell alloc]

initWithStyleUITableViewCellStyleSubtitlereuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona p = (Persona )[_listaPersonas objectAtIndexindexPathrow]celltextLabeltext = pnombrecelldetailTextLabeltext = [NSString stringWithFormatd antildeos

[pedad intValue]]

return cell

Ahora para poder mostrar la tabla dentro de nuestra aplicacioacuten debemos asignarla dentrode la clase delegada para ello abrimos la vista MainWindowxib y arrastramos unacontroladora de navegacioacuten Navigation Controller a la lista de objetos de laizquierda La desplegamos y dentro de View Controller en la pestantildea de la derechade Identity Inspector escribimos el nombre de nuestra clase que tiene la vista de latabla PersonasViewController

Pantalla del disentildeador

Ahora abrimos el fichero sesion04_ejemplo2AppDelegateh y creamos un

Persistencia

71Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

UINavigationController dentro de la declaracioacuten de variables dejando el ficherocomo sigue

imports iniciales

interface sesion04_ejemplo2AppDelegate NSObject ltUIApplicationDelegategt

UINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet UINavigationControllernavController

property (nonatomic retain readonly) NSManagedObjectContextmanagedObjectContextproperty (nonatomic retain readonly) NSManagedObjectModelmanagedObjectModelproperty (nonatomic retain readonly) NSPersistentStoreCoordinatorpersistentStoreCoordinator

- (void)saveContext- (NSURL )applicationDocumentsDirectory

end

Dentro del m importamos la clase PersonasViewControllerh y completamos elsynthetize

synthesize navController = _navController

Y por uacuteltimo justo antes de la llamada al meacutetodo makeKeyAndVisible escribimos elsiguiente coacutedigo para asignar el contexto de la clase delegada alPersonasViewController

PersonasViewController root = (PersonasViewController ) [_navControllertopViewController]

rootcontext = [self managedObjectContext][selfwindow addSubview_navControllerview]

Ahora compilamos y ejecutamos nuestro proyecto y si todo ha ido bien deberiacutea de salirla tabla con los datos de las personas

Persistencia

72Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Listado de personas

736 Migraciones de bases de datos con Core Data

Una vez que tenemos disentildeada nuestra base de datos inicial iquestqueacute pasariacutea si queremosmodificarla para antildeadirle un campo maacutes a una tabla iquestQueacute haraacute Core Data para realizar lamigracioacuten de la versioacuten anterior a la actual

Siguiendo con el ejemplo anterior si despueacutes de ejecutar la aplicacioacuten en nuestrodispositivo o simulador modificamos la estructura de la base de datos antildeadiendo unatributo nuevo en la entidad DetallePersona por ejemplo codigoPostal con el tipoInteger 16 veremos que al volver a arrancar la aplicacioacuten nos apareceraacute un error entiempo de ejecucioacuten similar al siguiente

Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 Theoperationcouldnrsquot be completed(Cocoa error 134100) UserInfo=0x6b6c680 metadata=ltCFBasicHash 0x6b68380[0x1337b38]gttype = immutable dict count = 7entries =gt

2 ltCFString 0x6b70320 [0x1337b38]gtcontents =NSStoreModelVersionIdentifiers

=ltCFArray 0x6b706e0 [0x1337b38]gttype = immutable count = 1

values = (0 ltCFString 0x1332cd8 [0x1337b38]gtcontents =

)4 ltCFString 0x6b70350 [0x1337b38]gtcontents =

NSPersistenceFrameworkVersion=

Persistencia

73Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltCFNumber 0x6b6f2c0 [0x1337b38]gtvalue = +386 type =kCFNumberSInt64Type

6 ltCFString 0x6b70380 [0x1337b38]gtcontents =NSStoreModelVersionHashes =

ltCFBasicHash 0x6b707e0 [0x1337b38]gttype = immutable dict count =2entries =gt

1 ltCFString 0x6b70700 [0x1337b38]gtcontents = Persona =ltCFData 0x6b70740

[0x1337b38]gtlength = 32 capacity = 32 bytes =0x1c50e5a379ff0ddc3cda7b82a3934c5e 75649ac3c919beea2 ltCFString 0x6b70720 [0x1337b38]gtcontents = DetallePersona

=ltCFData 0x6b70790[0x1337b38]gtlength = 32 capacity = 32 bytes =0xb7ba2eb498f9a1acdd6630d56f6cd12c e8963dd3e488ba95

7 ltCFString 0x10e3aa8 [0x1337b38]gtcontents = NSStoreUUID =ltCFString 0x6b70510[0x1337b38]gtcontents = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E8 ltCFString 0x10e3948 [0x1337b38]gtcontents = NSStoreType =ltCFString 0x10e3958[0x1337b38]gtcontents = SQLite9 ltCFString 0x6b703b0 [0x1337b38]gtcontents =

_NSAutoVacuumLevel = ltCFString0x6b70830 [0x1337b38]gtcontents = 210 ltFString 0x6b703d0 [0x1337b38]gtcontents =

NSStoreModelVersionHashesVersion=ltCFNumber 0x6b61950 [0x1337b38]gtvalue = +3 type =

kCFNumberSInt32Type reason=The model used to open the store is incompatible with the oneused tocreate the store

metadata = NSPersistenceFrameworkVersion = 386NSStoreModelVersionHashes =

DetallePersona = ltb7ba2eb4 98f9a1ac dd6630d5 6f6cd12c f890007746b16f00

e8963dd3 e488ba95gtPersona = lt1c50e5a3 79ff0ddc 3cda7b82 a3934c5e 5cb4ee4b

4ac98838 75649ac3c919beeagt

NSStoreModelVersionHashesVersion = 3NSStoreModelVersionIdentifiers = (

)NSStoreType = SQLiteNSStoreUUID = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E_NSAutoVacuumLevel = 2

reason = The model used to open the store is incompatible with the

one usedto create the store

Este error es muy comuacuten cuando estamos desarrollando aplicaciones con Core DataBaacutesicamente nos dice que el modelo de base de datos que estaacute intentando cargar dememoria es distinto al que se indica en la aplicacioacuten esto es porque no hemos indicadoninguacuten meacutetodo de migracioacuten Realmente existen dos formas de solucionar este error

Persistencia

74Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Por un lado y soacutelo cuando estemos desarrollando y no tengamos ninguna versioacutende nuestra aplicacioacuten lanzada en la App Store podremos eliminar la base de datosdel dispositivo simulador simplemente borrando la aplicacioacuten de el Tambieacutenpodemos ejecutar el siguiente coacutedigo una soacutela vez [[NSFileManagerdefaultManager] removeItemAtURLstoreURL errornil]

bull En el caso de que estemos realizando una migracioacuten sobre una versioacuten ya publicadaen la App Store el meacutetodo anterior no nos serviriacutea ya que obligariacuteamos al usuario aborrar su aplicacioacuten del dispositivo con lo que todos los datos que tuviera seperderiacutean Esto no es viable por lo que necesitaremos usar otro meacutetodo para realizarla migracioacuten y solucionar el error de una forma maacutes limpia este meacutetodo es el quevamos a comentar a continuacioacuten

XCode soporta Versioning para Core Data esto es que podemos crear versiones de labase de datos de una forma sencilla si seguimos una serie de pasos

1) Antildeadimos las siguientes opciones en forma de NSDictionary a nuestro coacutedigo delpersistentStoreCoordinator

NSDictionary options = [NSDictionary dictionaryWithObjectsAndKeys[NSNumber numberWithBoolYES]NSMigratePersistentStoresAutomaticallyOption[NSNumber numberWithBoolYES]NSInferMappingModelAutomaticallyOptionnil]

Y modificamos el meacutetodo addPersistentStoreWithType pasaacutendole como paraacutemetro eldiccionario options

if ([__persistentStoreCoordinatoraddPersistentStoreWithTypeNSSQLiteStoreTypeconfigurationnil URLstoreURL optionsoptions erroramperror])

Con el coacutedigo anterior indicamos al persistentStoreCoordinator que la migracioacuten enel caso de que se deba de realizar sea de forma automaacutetica

AtencioacutenDeberemos de tener en cuenta que este tipo de migracioacuten que estamos estudiando en este puntosoacutelo es vaacutelida para cambios simples en Core Data lo que Apple llama como lightweightmigration Este tipo de migracioacuten admite una serie de cambios que se indican dentro de ladocumentacioacuten de Apple para cambios maacutes complejos deberemos realizar migracionespersonalizadas y mucho maacutes complejas El documento en donde Apple explica el tipo demigraciones que existen para Core Data y su forma de implementarlas lo podemos consultardesde esta web

2) Ahora deberemos de crear la versioacuten del esquema de Core Data dentro de XCode para

Persistencia

75Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ello seleccionamos el fichero del esquema sesion04_ejemplo2xcdatamodeld yseleccionamos en el menuacute principal Editor gt Add Model Version A la nueva versioacuten lallamaremos sesion04_ejemplo_2 (versioacuten 2) Una vez que hayamos pulsado sobreFinish se nos habraacute creado la nueva versioacuten del esquema dentro de la principal y estaraacutemarcada con un tick la versioacuten anterior

3) Una vez que tenemos creada la nueva versioacuten deberemos modificarla antildeadiendole elnuevo atributo Seleccionamos la versioacuten recieacuten creadasesion04_ejemplo_2xcdatamodel y antildeadimos a la entidad DetallePersona elatributo codigoPostal Atencioacuten Si este atributo lo hemos antildeadido anteriormentedeberemos de borrarlo de la versioacuten anterior y antildeadirlo en la segunda versioacuten

4) Ahora nos queda indicar a Core Data que queremos usar la versioacuten 2 de la base dedatos para nuestra aplicacioacuten Para ello seleccionamos el esquema principal (el raiz)sesion04_ejemplo2xcdatamodeld y en la ventana de la derecha en la pestantildea de FileInspector seleccionamos como Versioacuten actual la versioacuten 2 esto lo hacemos dentro delapartado de Versioned Core Data Model Automaacuteticamente veremos que el siacutembolo detick cambia a la versioacuten 2

5) Ahora ya podemos volver a compilar y veremos que no nos aparece el error y funcionatodo seguacuten lo esperado

Magical RecordExisten varias librerias de coacutedigo libre que facilitan enormemente el uso de Core Data ennuestras aplicaciones iOS Una de ellas es Magical Record Mediante esta libreriacutea podremos usarCore Data sin tener que preocuparnos de todo el proceso de configuracioacuten en las controladorasde la configuracioacuten de migraciones etc Su instalacioacuten en un proyecto de XCode es muy faacutecil yaltamente recomendable ya que proporciona una API muy clara y sencilla de usar

Persistencia

76Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios

81 Usando User Defaults (1 punto)

En este ejercicio vamos a usar las variables de usuario para almacenar y mostrar los datosdel dispositivo que estamos usando y las veces que hemos arrancado la aplicacioacuten

Crear un proyecto nuevo usando la plantilla Single View Application con el nombreejercicio-userdefaults-ios Se debe de ejecutar en iPhone e iPad (Universal) Comoidentificador de empresa escribiremos esuajtech y como prefijo de clase UA Debe detener el ARC activado (desmarcar el resto de opciones)

Ayuda meacutetodos de UIDevicePara obtener los datos del dispositivo usaremos el singleton UIDevice estos son algunos de losmeacutetodos que podemos usar [[UIDevice currentDevice] name] [[UIDevicecurrentDevice] systemName] [[UIDevice currentDevice]systemVersion] [[UIDevice currentDevice] model] [[UIDevicecurrentDevice] localizedModel]

82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)

En este ejercicio disentildearemos un modelo de datos de dos tablas relacionadas entre ellasantildeadiremos datos por coacutedigo y los mostraremos en una vista de tabla La base de datosque vamos a implementar constaraacute de dos tablas por un lado una que contendraacute los tiacutetulosde series y otra que tendraacute los detalles A la primera la llamaremos Serie y la segundaDetalleSerie Para realizar correctamente el ejercicio deberemos seguir los siguientespasos

1) Crear un proyecto nuevo usando la plantilla Master-Detail Application con elnombre ejercicio-coredata-ios Se debe de ejecutar en iPhone como identificador deempresa escribiremos esuajtech y como prefijo de clase UA Debe de tener el ARCactivado y la opcioacuten de Use Core Data (desmarcar el resto de opciones)

2) Disentildear el siguiente modelo de datos

Persistencia

77Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esquema modelo de datos

3) Revisar el fichero UAAppDelegatem y comprobar que tenemos todos los meacutetodosnecesarios para que funcione Core Data en nuestra aplicacioacuten

4) Modificar el fichero UAMasterViewControllerm para que se muestren los tiacutetulos delas series en la tabla

5) Probar la aplicacioacuten antildeadiendo unos tiacutetulos de prueba

83 Migrando datos con Core Data (05 puntos)

Una vez que hemos disentildeado la Base de Datos con Core Data vamos a modificarlaantildeadiendo un atributo maacutes a la tabla de DetalleSerie valoracion

a) iquestQueacute pasa si cambiamos el modelo de datos y arrancamos la aplicacioacuten iquestQueacute errornos da y por queacute

b) iquestCoacutemo podemos evitar este tipo de errores cuando estemos desarrollando iquestY siestamos en produccioacuten con la aplicacioacuten ya a la venta

c) Crea una nueva versioacuten del modelo

d) Modifica el coacutedigo necesario dentro de la clase UAAppDelegate para evitar este tipo deerrores cuando estemos en un entorno de produccioacuten

e) Vuelve a arrancar la aplicacioacuten y comprueba que funciona bien

Persistencia

78Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Persistencia

79Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

  • 1 Persistencia en Android ficheros y SQLite
    • 11 Introduccioacuten
    • 12 Manejo de ficheros tradicionales en Android
      • 121 Apertura de ficheros
      • 122 Ficheros como recursos
      • 123 Operar con ficheros
      • 124 Almacenar datos en la tarjeta de memoria
        • 13 Base de datos SQLite
          • 131 Content Values
          • 132 Cursores
          • 133 Trabajar con bases de datos SQLite
          • 134 La clase SQLiteOpenHelper
          • 135 Crear una base de datos sin SQLiteHelper
          • 136 Realizar una consulta
          • 137 Extraer resultados de un cursor
          • 138 Antildeadir actualizar y borrar filas
              • 2 Ejercicios - Persistencia en Android ficheros y SQLite
                • 21 Uso de ficheros (05 puntos)
                • 22 Persistencia con ficheros (05 puntos)
                • 23 Base de datos SQLiteOpenHelper (05 puntos)
                • 24 Base de datos insercioacuten y borrado (05 puntos)
                • 25 Base de datos probar nuestro adaptador (05 puntos)
                • 26 Base de datos cambios en la base de datos (05 puntos)
                  • 3 Persistencia en Android proveedores de contenidos y SharedPreferences
                    • 31 Shared Preferences
                      • 311 Guardar Shared Preferences
                      • 312 Leer Shared Preferences
                      • 313 Interfaces para Shared Preferences
                      • 314 Definiendo una pantalla de preferencias con un layout en XML
                      • 315 Controles nativos para preferencias
                      • 316 Actividades de preferencias
                      • 317 Shared Preference Change Listeners
                        • 32 Proveedores de contenidos
                          • 321 Proveedores nativos
                          • 322 Proveedores propios crear un nuevo proveedor de contenidos
                          • 323 Proveedores propios crear la interfaz de consultas
                          • 324 Proveedores propios tipo MIME
                          • 325 Proveedores propios registrar el proveedor
                          • 326 Content Resolvers
                          • 327 Otras operaciones con Content Resolvers
                              • 4 Ejercicios - Persistencia en Android proveedores de contenidos y SharedPreferences
                                • 41 Compartir datos entre actividades con Shared Preferences (075 puntos)
                                • 42 Actividad de preferencias (075 puntos)
                                • 43 Proveedor de contenidos propio (075 puntos)
                                • 44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)
                                  • 5 Persistencia de datos en iOS Ficheros y SQLite
                                    • 51 Introduccioacuten
                                    • 52 Ficheros plist (Property Lists)
                                      • 521 Leyendo datos desde ficheros plist
                                      • 522 Escribiendo datos en ficheros plist
                                        • 53 SQLite
                                          • 531 Herramientas disponibles
                                          • 532 Lectura de datos
                                          • 533 Mostrando los datos en una tabla
                                              • 6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios
                                                • 61 Creando y leyendo un fichero plist (15 puntos)
                                                • 62 Implementar vista de detalle de la serie (05 puntos)
                                                • 63 Uso baacutesico de una base de datos SQLite (1 punto)
                                                  • 7 Persistencia de datos en iOS User Defaults y Core Data
                                                    • 71 Introduccioacuten
                                                    • 72 User Defaults
                                                    • 73 Core Data
                                                      • 731 Creando el proyecto
                                                      • 732 Definiendo el modelo de datos
                                                      • 733 Probando el modelo
                                                      • 734 Generando los modelos automaacuteticamente
                                                      • 735 Creando la vista de tabla
                                                      • 736 Migraciones de bases de datos con Core Data
                                                          • 8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios
                                                            • 81 Usando User Defaults (1 punto)
                                                            • 82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)
                                                            • 83 Migrando datos con Core Data (05 puntos)
Page 6: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de

13 Base de datos SQLite

SQLite es un gestor de bases de datos relacional y es de coacutedigo abierto cumple con losestaacutendares y es extremadamente ligero Ademaacutes guarda toda la base de datos en un uacutenicofichero Su utilidad es evidente en el caso de aplicaciones de pequentildeo tamantildeo pues eacutestasno requeriraacuten la instalacioacuten adicional de ninguacuten gestor de bases de datos Esto tambieacutenseraacute asiacute en el caso de dispositivos empotrados con recursos limitados Android incluyesoporte a SQLite y en esta seccioacuten veremos coacutemo usarlo

Por medio de SQLite podremos crear bases de datos relacionales independientes paranuestras aplicaciones Con ellas podremos almacenar datos complejos y estructuradosAunque el disentildeo de bases de datos quedaraacute fuera del contenido de este curso ya quenecesitariacuteamos de maacutes tiempo para poder tratarlo es conveniente resaltar el hecho de quelas mejores praacutecticas en este campo siguen siendo uacutetiles en el caso de aplicacionesAndroid En particular la normalizacioacuten de datos para evitar redundancia es un paso muyimportante en el caso en el que estemos disentildeando una base de datos para dispositivoscon recursos limitados

En el caso del sistema Android SQLite se implementa como una libreriacutea C compacta enlugar de ejecutarse en un proceso propio Debido a ello cualquier base de datos quecreemos seraacute integrada como parte de su correspondiente aplicacioacuten Esto reducedependencias externas minimiza la latencia y simplifica ciertos procesos como lasincronizacioacuten

NotaLas bases de datos de Android se almacenan en el directoriodatadata[PAQUETE]databases donde [PAQUETE] es el paquetecorrespondiente a la aplicacioacuten Por defecto todas las bases de datos son privadas y tan soacuteloaccesibles por la aplicacioacuten que las creoacute

131 Content Values

Para insertar nuevas filas en tablas haremos uso de objetos de la clase ContentValuesCada objeto de este tipo representa una uacutenica fila en una tabla y se encarga de emparejarnombres de columnas con valores concretos Hay que tener en cuenta que SQLite difierede muchos otros motores de bases de datos en cuanto al tipado de las columnas La ideabaacutesicamente es que los valores para las columnas no tienen por queacute ser de un uacutenico tipoes decir los datos en SQLite son deacutebilmente tipados La consecuencia de esto es que noes necesario comprobar tipos cuando se asignan o extraen valores de cada columna de unafila con lo que se mejora la eficiencia

132 Cursores

Persistencia

6Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Los cursores son una herramienta fundamental en el tratamiento de informacioacuten enAndroid y tambieacuten cuando se trabaja con bases de datos Por lo tanto en esta seccioacutenintroducimos este concepto tan importante

Cualquier consulta a una base de datos en Android devolveraacute un objeto de la claseCursor Los cursores no contienen una copia de todos los resultados de la consulta sinoque maacutes bien se trata de punteros al conjunto de resultados Estos objetos proporcionanun mecanismo para controlar nuestra posicioacuten (fila) en el conjunto de resultadosobtenidos tras la consulta

La clase Cursor incluye entre otras las siguientes funciones de navegacioacuten entreresultados

bull moveToFirst desplaza el cursor a la primera fila de los resultados de la consultabull moveToNext desplaza el cursor a la siguiente filabull moveToPrevious desplaza el cursor a la fila anteriorbull getCount devuelve el nuacutemero de filas del conjunto de resultados de la consultabull getColumnName devuelve el nombre de la columna especificada mediante un iacutendice

que se pasa como paraacutemetrobull getColumnNames devuelve un array de cadenas conteniendo el nombre de todas las

columnas en el cursorbull moveToPosition mueve el cursor a la fila especificadabull getPosition devuelve el iacutendice de la posicioacuten apuntada actualmente por el cursor

A lo largo de esta sesioacuten aprenderemos coacutemo realizar consultas a una base de datos ycomo extraer valores o nombres de columnas concretos a partir de los cursores obtenidospor medio de dichas consultas

133 Trabajar con bases de datos SQLite

Suele considerarse una buena praacutectica el crear una clase auxiliar que nos facilite lainteraccioacuten con la base de datos Se trataraacute de una clase de tipo adaptador que crearaacute unacapa de abstraccioacuten que encapsularaacute dichas interacciones De esta forma podremosantildeadir eliminar o actualizar elementos en la base de datos sin necesidad de introducir niuna uacutenica instruccioacuten en SQL en el resto del coacutedigo permitiendo ademaacutes que se hagan lascomprobaciones de tipos correspondientes

Una clase de este tipo deberiacutea incluir meacutetodos para crear abrir o cerrar la base de datosTambieacuten puede ser un lugar conveniente para publicar constantes estaacuteticas relacionadascon la base de datos como por ejemplo constantes que almacenen el nombre de la tabla olas diferentes columnas

A continuacioacuten se muestra el coacutedigo de lo que podriacutea ser un adaptador para una base dedatos estaacutendar Incluye una extensioacuten de la clase SQLiteOpenHelper que seraacute tratada enmaacutes detalle en la siguiente seccioacuten y que se utiliza para simplificar la apertura lacreacioacuten y la actualizacioacuten de la base de datos

Persistencia

7Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

import androidcontentContextimport androiddatabaseimport androiddatabasesqliteimport androiddatabasesqliteSQLiteDatabaseCursorFactoryimport androidutilLog

public class MiAdaptadorBD private static final String NOMBRE_BASE_DATOS =

mibasededatosdbprivate static final String TABLA_BASE_DATOS = mainTableprivate static final int VERSION_BASE_DATOS = 1

Nombre de la columna de clave primariapublic static final String CP_ID=_id El nombre e iacutendice de cada columna en la base de datospublic static final String COLUMNA_NOMBRE=nombrepublic static final int COLUMNA_INDICE = 1 PENDIENTE crear campos adicionales para el resto de columnas de la base de datos

Sentencia SQL para crear una nueva base de datosprivate static final String CREAR_BASE_DATOS = create table +

TABLA_BASE_DATOS + ( + CP_ID + integer primary key autoincrement +COLUMNA_NOMBRE + text not null)

Variable para almacenar la instancia de la base de datosprivate SQLiteDatabase db Contexto de la aplicacioacuten que estaacute usando la base de datosprivate final Context contexto Clase helper usada para crear o actualizar la base de datos (hablamos de ella en la siguiente seccioacuten)private miHelperBD dbHelper

Crea la base de datos con ayuda del helperpublic MiAdaptadorBD(Context _contexto)

contexto = _contextodbHelper = new miHelperBD(contexto NOMBRE_BASE_DATOS

nullVERSION_BASE_DATOS)

Abre una base de datos ya existentepublic MiAdaptadorBD open() throws SQLException

db = dbHelpergetWritableDatabase()return this

Cierra la base de datos cuando eacutesta ya no va a ser utilizadapublic void close()

dbclose()

Meacutetodo para insertar un elemento en la tabla de la base dedatos

public int insertar(MiObjeto _miObjeto) PENDIENTE crear nuevos elementos ContentValues para

representar al objeto e insertarlos en la base de datos

Devolvemos el iacutendice del nuevo elemento en la base dedatos

return index

Persistencia

8Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Meacutetodo para eliminar un elemento de la tabla de la base dedatos

Devuelve un valor booleano indicando el eacutexito o no de la operacioacutenpublic boolean eliminar(long _indiceFila)

return dbdelete(TABLA_BASE_DATOS CP_ID + = +_indiceFila null) gt 0

Meacutetodo para obtener un Cursor que apunte a todas las filascontenidas

en la tabla de la base de datospublic Cursor obtenerTodos ()

return dbquery(TABLA_BASE_DATOS new String[] CP_IDCOLUMNA_NOMBRE

null null null nullnull)

Meacutetodo para obtener un elemento determinado de la base de datos a partir de su iacutendice de filapublic MiObjeto getObjeto(long _indiceFila)

PENDIENTE obtener un cursor a una fila de la base de datos y usar sus valores para inicializar una instancia de MiObjeto

return objectInstance

Meacutetodo para actualizar un elemento de la tabla de la base dedatos

public boolean actualizar(long _indiceFila MiObjeto _miObjeto) PENDIENTE crear nuevos ContentValues a partir del

objeto y usarlos para actualizar una fila en la tabla

correspondiente

return true

Clase privada auxiliar para ayudar en la creacioacuten yactualizacioacuten

de la base de datosprivate static class miHelperBD extends SQLiteOpenHelper

public miHelperBD(Context contexto String nombreCursorFactory factory int version)

super(contexto nombre factory version)

Meacutetodo llamado cuando la base de datos no existe en eldisco

y la clase Helper necesita crearla desde ceroOverridepublic void onCreate(SQLiteDatabase _db)

_dbexecSQL(CREAR_BASE_DATOS)

Meacutetodo llamado cuando la versioacuten de la base de datos en disco es diferente a la indicada en este caso la base de datos en disco necesita ser actualizadaOverridepublic void onUpgrade(SQLiteDatabase _db int

_versionAnterior

Persistencia

9Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

int _versionNueva) Mostramos en LogCat la operacioacutenLogw(AdaptadorBD Actualizando desde la

versioacuten +_versionAnterior + a la versioacuten +_versionNueva + lo cual borraraacute los

datospreviamente almacenados)

Actualizamos la base de datos para que seadapte a la nueva versioacuten

La forma maacutes sencilla de llevar a cabo dichaactualizacioacuten es

eliminar la tabla antigua y crear una nueva_dbexecSQL(DROP TABLE IF EXISTS +

TABLA_BASE_DATOS)onCreate(_db)

134 La clase SQLiteOpenHelper

La clase SQLiteOpenHelper es una clase abstracta utilizada como medio paraimplementar un patroacuten de creacioacuten apertura y actualizacioacuten de una determinada base dedatos Al implementar una subclase de SQLiteOpenHelper podemos abstraernos de laloacutegica subyacente a la decisioacuten de crear o actualizar una base de datos de manera previa aque eacutesta deba ser abierta

En el ejemplo de coacutedigo anterior se vio coacutemo crear una subclase de SQLiteOpenHelper

por medio de la sobrecarga de sus meacutetodos onCreate y onUpgrade los cuales permitencrear una nueva base de datos o actualizar a una nueva versioacuten respectivamente

NotaEn el ejemplo anterior el meacutetodo onUpgrade simplemente eliminaba la tabla existente y lareemplazaba con la nueva definicioacuten de la misma En la praacutectica la mejor solucioacuten seriacutea migrarlos datos ya existentes a la nueva tabla

Mediante los meacutetodos getReadableDatabase y getWritableDatabase podemos abrir yobtener una instancia de la base de datos de soacutelo lectura o de lectura y escriturarespectivamente La llamada a getWritableDatabase podriacutea fallar debido a falta deespacio en disco o cuestiones relativas a permisos por lo que es una buena praacutectica teneren cuenta esta posibilidad por medio del mecanismo de manejo de excepciones de JavaEn caso de fallo la solucioacuten podriacutea pasar por al menos proporcionar una copia de soacutelolectura tal como se muestra en el siguiente ejemplo

dbHelper = new miHelperBD(contexto NOMBRE_BASE_DATOS nullVERSION_BASE_DATOS)

SQLiteDatabase dbtry

db = dbHelpergetWritableDatabase() catch (SQLiteException ex)

db = dbHelpergetReadableDatabase()

Persistencia

10Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Al hacer una llamada a cualquiera de los dos meacutetodos anteriores se invocaraacuten losmanejadores de evento adecuados Asiacute pues si por ejemplo la base de datos no existieraen disco se ejecutariacutea el coacutedigo de onCreate Si por otra parte la versioacuten de la base dedatos se hubiera modificado se disparariacutea el manejador onUpgrade Lo que sucedeentonces es que al hacer una llamada a getWritableDatabase o getReadableDatabase

se devolveraacute una base de datos nueva actualizada o ya existente seguacuten el caso

135 Crear una base de datos sin SQLiteHelper

Tambieacuten es posible crear y abrir bases de datos sin necesidad de utilizar una subclase deSQLiteHelper Para ello haremos uso del meacutetodo openOrCreateDatabase En este casoseraacute necesario tambieacuten hacer uso del meacutetodo execSQL sobre la instancia de la base dedatos devuelta por el meacutetodo anterior para ejecutar los comandos SQL que permitiraacutencrear las tablas de la base de datos y establecer las relaciones entre ellas El siguientecoacutedigo muestra un ejemplo

private static final String NOMBRE_BASE_DATOS = mibasededatosdbprivate static final String TABLA_BASE_DATOS = tablaPrincipal

private static final String CREAR_BASE_DATOS =create table + TABLA_BASE_DATOS + _id integer primare key

autoincrement +columna_uno text not null)

SQLiteDatabase db

private void crearBaseDatos() db = openOrCreateDatabase(NOMBRE_BASE_DATOS ContextMODE_PRIVATE

null)dbexecSQL(CREAR_BASE_DATOS)

NotaAunque no es necesariamente obligatorio es recomendable que cada tabla incluya un campo detipo clave primaria con autoincremento a modo de iacutendice para cada fila En el caso en el que sedesee compartir la base de datos mediante un proveedor de contenidos un campo uacutenico de estetipo es obligatorio

136 Realizar una consulta

El resultado de cualquier consulta a una base de datos seraacute un objeto de la clase CursorEsto permite a Android manejar los recursos de manera maacutes eficiente de tal forma que enlugar de devolver todos los resultados eacutestos se van proporcionando conforme senecesitan

Para realizar una consulta a una base de datos haremos uso del meacutetodo query de lainstancia correspondiente de la clase SQLiteDatabase Los paraacutemetros que requiere este

Persistencia

11Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

meacutetodo son los siguientes

bull Un booleano opcional que permite indicar si el resultado deberiacutea contener tan soacutelovalores uacutenicos

bull El nombre de la tabla sobre la que realizar la consultabull Un array de cadenas que contenta un listado de las columnas que deben incluirse en el

resultadobull Una claacuteusula where que defina las filas que se deben obtener de la tabla Se puede

utilizar el comodiacuten el cual seraacute reemplazado por los valores que se pasen a traveacutesdel siguiente paraacutemetro

bull Un array de cadenas correspondientes a argumentos de seleccioacuten que reemplazaraacutenlos siacutembolos en la claacuteusula where

bull Una claacuteusula group by que define coacutemo se agruparaacuten las filas obtenidas comoresultado

bull Un filtro having que indique que grupos incluir en el resultado en el caso en el que sehaya hecho uso del paraacutemetro anterior

bull Una cadena que describa la ordenacioacuten que se llevaraacute a cabo sobre las filas obtenidascomo resultado

bull Una cadena opcional para definir un liacutemite en el nuacutemero de resultados a devolver

NotaComo se puede observar la mayoriacutea de estos paraacutemetros hacen referencia al lenguaje SQLTratar este tema queda fuera de los objetivos de este curso por lo que en nuestros ejemplos ledaremos en la mayoriacutea de las ocasiones un valor null a estos paraacutemetros

El siguiente coacutedigo muestra un ejemplo de consultas utilizando diversos paraacutemetros

Devolver los valores de las columnas uno y tres para todas las filas sin duplicadosString[] columnas = new String[] CLAVE_ID CLAVE_COL1 CLAVE_COL3

Cursor todasFilas = dbquery(true TABLA_BASE_DATOS columnas null nullnull null

null null)

Devolvemos los valores de todas las columnas para aquellas filas cuyovalor de la columna 3 sea igual al de una determinada variable Ordenamos las filasseguacuten el valor de la columna 5 Como queremos los valores de todas las columnas le damos al paraacutemetro correspondiente a las columnas a devolver el valor null En este caso no se ha hecho uso del primer paraacutemetro booleano que esoptativoString where = CLAVE_COL3 + = + valorString orden = CLAVE_COL5Cursor miResultado = dbquery(TABLA_BASE_DATOS null where null nullnull orden)

137 Extraer resultados de un cursor

Persistencia

12Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El primer paso para obtener resultados de un cursor seraacute desplazarnos a la posicioacuten apartir de la cual queremos obtener los datos usando cualquiera de los meacutetodosespecificados anteriormente (como por ejemplo moveToFirst o moveToPosition) Unavez hecho esto hacemos uso de meacutetodos get[TIPO] pasando como paraacutemetro el iacutendice dela columna Esto tendraacute como resultado la devolucioacuten del valor para dicha columna de lafila actualmente apuntada por el cursor

String valor = miResultadogetString(indiceColumna)

A continuacioacuten podemos ver un ejemplo maacutes completo en el que se va iterando a lo largode los resultados apuntados por un cursor extrayendo y sumando los valores de unacolumna de flotantes

int COLUMNA_VALORES_FLOTANTES = 2Cursor miResultado = dbquery(Tabla null null null null nullnull)float total = 0

Nos aseguramos de que se haya devuelto al menos una filaif (miResultadomoveToFirst())

Iteramos sobre el cursordo

float valor =miResultadogetFloat(COLUMNA_VALORES_FLOTANTES)

total += valor while (miResultadomoveToNext())

float media = total miResultadogetCount()

NotaDebido a que las columnas de las bases de datos en SQLite son debilmente tipadas podemosrealizar castings cuando sea necesario Por ejemplo los valores guardados como flotantes en labase de datos podriacutean leerse maacutes adelante como cadenas

138 Antildeadir actualizar y borrar filas

La clase SQLiteDatabase proporciona los meacutetodos insert delete y update queencapsulan las instrucciones SQL requeridas para llevar a cabo estas acciones Ademaacutesdisponemos de la posibilidad de utilizar el meacutetodo execSQL el cual nos permite ejecutarcualquier sentencia SQL vaacutelida en nuestras tablas de la base de datos en el caso en el quequeramos llevar a cabo estas (u otras) operaciones manualmente

NotaCada vez que modifiques los valores de la base de datos puedes actualizar un cursor que se puedaver afectado por medio del meacutetodo refreshQuery de la clase Cursor

Para insertar una nueva fila es necesario construir un objeto de la clase ContentValues

y usar su meacutetodo put para proporcionar valor a cada una de sus columnas Para insertar la

Persistencia

13Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

nueva fila pasaremos como paraacutemetro este objeto del tipo ContentValues al meacutetodoinsert de la instancia de la base de datos junto por supuesto con el nombre de la tablaA continuacioacuten se muestra un ejemplo

Crear la nueva filaContentValues nuevaFila = new ContentValues()

Asignamos valores a cada columnanuevaFilaput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas ]

Insertar la nueva fila en la tabladbinsert(TABLA_BASE_DATOS null nuevaFila)

NotaLos ficheros como imaacutegenes o ficheros de audio no se suelen almacenar dentro de tablas en unabase de datos En estos casos se suele optar por almacenar en la base de datos una cadena con laruta al fichero o una URI

La operacioacuten de actualizar una fila tambieacuten puede ser llevada a cabo por medio del usode la clase ContentValues En este caso deberemos llamar al meacutetodo update de lainstancia de la base de datos pasando como paraacutemetro el nombre de la tabla el objetoContentValues y una claacuteusula where que especifique la fila o filas a actualizar tal comose puede ver en el siguiente ejemplo

Creamos el objeto utilizado para definir el contenido a actualizarContentValues valoresActualizar = new ContentValues()

Asignamos valores a las columnas correspondientesvaloresActualizarput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas a actualizar ]

String where = CLAVE_ID + = + idFila

Actualizamos la fila con el iacutendice especificado en la instruccioacutenanteriordbupdate(TABLA_BASE_DATOS valoresActualizar where null)

Por uacuteltimo para borrar una fila simplemente llamaremos al meacutetodo delete de lainstancia de la base de datos especificando en este caso el nombre de la tabla que se veraacuteafectada por la operacioacuten y una claacuteusula where que utilizaremos para especificar las filasque queremos borrar Un ejemplo podriacutea ser el siguiente

dbdelete(TABLA_BASE_DATOS CLAVE_ID + = + idFila null)

Persistencia

14Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

2 Ejercicios - Persistencia en Android ficheros y SQLite

21 Uso de ficheros (05 puntos)

En este ejercicio vamos a crear una aplicacioacuten que muestre un listado de cadenas porpantalla Estas cadenas se almacenaraacuten en un fichero de texto privado para la aplicacioacutenPodremos antildeadir nuevas cadenas a partir de un cuadro de edicioacuten presente en la interfazPartiremos del proyecto Ficheros proporcionado en las plantillas de la aplicacioacuten Debesseguir los siguientes pasos

bull Antildeadir un manejador al botoacuten de la actividad principal para que cada vez que seapulsado se guarde en un fichero de texto El fichero se llamaraacute ficherotxt Al abrirel fichero para escritura se utilizaraacute el paraacutemetro ContextMODE_APPEND con lo quecada nueva cadena se antildeadiraacute al final del fichero en el caso en el que eacuteste ya existieraPara escribir en el fichero utilizaremos el meacutetodo writeBytes del objetoDataOutputStream correspondiente

bull Al pulsar el botoacuten tambieacuten se deberaacute borrar el contenido del elemento EditText (leasignamos a la vista una cadena vaciacutea con el meacutetodo setText)

bull Ejecutamos la aplicacioacuten e introducimos algunas liacuteneas en el fichero Vamos acomprobar ahora que todo ha funcionado correctamente Para ello accedemos alsistema de ficheros de nuestro dispositivo ejecutando el comando adb shell

dentro de la carpeta platform-tools de nuestra carpeta de instalacioacuten del SDK deAndroid

bull Comprueba que se ha creado el fichero ficherotxt en la carpetadatadataesuajtechandroidficherosfiles Examina su contenido ejecutando cat

ficherotxt Si algo no ha salido bien siempre podraacutes eliminar el fichero con rm

ficherotxtbull En la interfaz de la actividad se ha incluido una vista de tipo TextView debajo del

botoacuten Antildeade un manejador para dicha vista de tal forma que cada vez que se hagaclick sobre ella se muestre el contenido del fichero ficherotxt Para ello leeremos elfichero liacutenea a liacutenea antildeadiendo cada una al TextView por medio del meacutetodo append

Persistencia

15Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Interfaz de la aplicacioacuten Ficheros

22 Persistencia con ficheros (05 puntos)

Seguramente habraacutes observado que si abandonas la aplicacioacuten anterior (pulsando el botoacutenBACK del dispositivo) al volver a ejecutarla el contenido del TextView ha desaparecidoDebes volver a pulsar sobre la vista para que se vuelva a mostrar el contenido del ficheroHaz las modificaciones pertinentes para que esto no sea asiacute es decir para que cuandovuelva a mostrarse la actividad tras haber sido eliminada de la memoria se muestre elTextView tal cual se veiacutea antes de abandonar la aplicacioacuten

AvisoEn este ejercicio no se estaacute pidiendo que cargues el contenido del fichero en el TextView nadamaacutes arrancar la aplicacioacuten Puede darse el caso de que lo que esteacute mostrando el TextViewcuando la actividad sea eliminada de memoria no se corresponda con el contenido actualizado delfichero

NotaPara poder completar el ejercicio deberaacutes repasar los manejadores de evento de la primera sesioacutendel moacutedulo de Android relacionados con el ciclo de vida de ejecucioacuten de actividades

23 Base de datos SQLiteOpenHelper (05 puntos)

En las plantillas de la aplicacioacuten se incluye la aplicacioacuten BaseDatos En el proyecto de laaplicacioacuten se incluye el esqueleto de una clase MiAdaptadorBD que tendremos quecompletar Se trata de un patroacuten que nos va a permitir acceder a una base de datos de

Persistencia

16Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

usuarios dentro de la aplicacioacuten sin necesidad de hacer uso de coacutedigo SQL

La clase MiAdaptadorBD incluye a su vez otro patroacuten en este caso implementado comouna subclase de SQLiteOpenHelper Eacuteste nos obliga a definir queacute ocurre cuando la basede datos todaviacutea no existe y debe ser creada y queacute ocurre si ya existe pero debe seractualizada porque ha cambiado de versioacuten Asiacute el SQLiteOpenHelper queimplementemos en este caso MiOpenHelper nos devolveraacute siempre una base de datosseparaacutendonos de la loacutegica encargada de comprobar si la base de datos existe o no

En este primer ejercicio se pide hacer lo siguiente

bull Ejecutar la sentencia de creacioacuten de bases de datos (la tenemos declarada comoconstante de la clase) en el meacutetodo MiOpenHelperonCreate

bull Implementar tambieacuten el meacutetodo onUpgrade Idealmente eacuteste deberiacutea portar las tablasde la versioacuten antigua a la versioacuten nueva copiando todos los datos Nosotros vamos aeliminar directamente la tabla que tenemos con la sentencia SQL DROP TABLE IF

EXISTS + NOMBRE_TABLA y volveremos a crearlabull En el constructor de MiAdaptadorBD debemos obtener en el campo db la base de

datos utilizando MiOpenHelper

24 Base de datos insercioacuten y borrado (05 puntos)

Continuamos trabajando con el proyecto anterior En este ejercicio completaremos elcoacutedigo relacionado con las sentencias de insercioacuten y borrado Para realizar la insercioacutenvamos a hacer uso de un mecanismo que no hemos visto en la sesioacuten de teoriacutea y queconsiste en utilizar una sentencia de insercioacuten SQL precompilada que se completaraacute conlos valores concretos a insertar en la base de datos justo antes de realizar dicha insercioacutenLa ventaja de las sentencias compiladas es que evitan que se produzcan ataques deinyeccioacuten de coacutedigo

Como se puede observar se ha incluido un atributo a la clase que representa la insercioacutenSQL

private static final String INSERT = insert into + NOMBRE_TABLA +(+COLUMNAS[1]+) values ()

En esta sentencia no se indica ninguacuten valor concreto para la columna nombre En el lugaren el que deberiacutean aparecer los valores de dicho campo se ha escrito simplemente unsiacutembolo de interrogacioacuten Tambieacuten se ha antildeadido una instancia de la claseSQLiteStatement como parte de los atributos de la clase

private SQLiteStatement insertStatement

Realizamos los siguientes pasos

bull En el constructor de la clase MiAdaptadorBD llevamos a cabo la compilacioacuten de lasentencia

thisinsertStatement = thisdbcompileStatement(INSERT)

Persistencia

17Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Una vez hecho esto cada vez que deseemos insertar un nuevo usuario en la base dedatos deberemos dar valores concretos a la columna nombre y ejecutar la sentenciaPara ello debemos antildeadir el siguiente coacutedigo dentro del meacutetodo insert de la claseMiAdaptadorBD

thisinsertStatementbindString(1 nombreUsuario)return thisinsertStatementexecuteInsert()

bull Por uacuteltimo completamos el coacutedigo del meacutetodo deleteAll cuyo contenido es eliminara todos los usuarios de la base de datos Para que ademaacutes se devuelva el nuacutemero defilas afectadas podemos insertar la siguiente liacutenea en dicho meacutetodo

return dbdelete(NOMBRE_TABLA null null)

Una vez hechos todos estos cambios podremos utilizar la clase MiAdaptadorBD parahacer operaciones con la base de datos de usuarios de manera transparente

25 Base de datos probar nuestro adaptador (05 puntos)

Antildeade coacutedigo en la actividad Main para eliminar todos los usuarios de la base de datosantildeadir dos cualesquiera y listarlos por medio de la vista de tipo TextView que seencuentra en el layout de dicha actividad

Podemos comprobar mediante la liacutenea de comandos (comando adb shell) que la basede datos ha sido creada Para ello puede ser uacutetil hacer uso de los siguientes comandos

cd datadataesuajtechandroidbasedatosdatabasessqlite3 misusuariosdbsqlitegt schemasqlitegt tablessqlitegt select from usuarios

26 Base de datos cambios en la base de datos (05 puntos)

Ahora vamos a cambiar en la clase MiAdaptadorBD el nombre de la segunda columnaque en lugar de nombre se va a llamar nombres Ejecutamos la aplicacioacuten y comprobamosque sale con una excepcioacuten Comprueba por medio de LogCat cuaacutel ha sido el erroriquestCoacutemo lo podemos solucionar

NotaPista conforme hemos programado la clase MiAdaptadorBD y siguiendo el patroacuten de disentildeode SQLiteOpenHelper es posible arreglar el problema simplemente modificando el valor deun campo

Persistencia

18Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

3 Persistencia en Android proveedores de contenidos ySharedPreferences

31 Shared Preferences

Comenzamos esta sesioacuten hablando de una de las teacutecnicas maacutes simples de persistencia enAndroid junto al uso de ficheros Shared Preferences Se trata de un mecanismo ligeropara almacenar datos basado en pares clave-valor utilizado normalmente para guardarpreferencias u opciones de la aplicacioacuten Tambieacuten puede ser utilizado para almacenar elestado de la interfaz graacutefica para contemplar el caso de que nuestra actividad debafinalizar abruptamente su ejecucioacuten Otro posible uso es el de compartir informacioacutenentre componentes de una misma aplicacioacuten

NotaLos Shared Preferences nunca son compartidos entre diferentes aplicaciones

Este meacutetodo se basa en el uso de la clase SharedPreferences Es posible utilizarla paraguardar datos en muy diversos formatos booleanos cadenas flotantes y enteros

311 Guardar Shared Preferences

Para crear o modificar un Shared Preference llamamos al meacutetodogetSharedPreferences del contexto de la aplicacioacuten pasando como paraacutemetro elidentificador del conjunto de valores de preferencias con el que queremos trabajar Pararealizar la modificacioacuten del valor de un Shared Preference hacemos uso de la claseSharedPreferencesEditor Para obtener el objeto editor invocamos el meacutetodo edit

del objeto Shared Preferences que queremos modificar Los cambios se guardan medianteel meacutetodo commit Veamos un ejemplo

int modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences = getSharedPreferences(Mispreferencias modo)

Obtenemos un editor para modificar las preferenciasSharedPreferencesEditor editor = misSharedPreferencesedit()

Guardamos nuevos valores en el objeto SharedPreferenceseditorputBoolean(isTruetrue)editorputFloat(unFloat10)editorputInt(numeroEntero12)editorputLong(otroNumero2)editorputString(unaCadenavalor)

Guardamos los cambioseditorcommit()

Persistencia

19Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

312 Leer Shared Preferences

Para leer Shared Preferences utilizamos el meacutetodo getSharedPreferences tal como seha comentado anteriormente Pasamos como paraacutemetro el identificador del conjunto deShared Preferences al que deseamos acceder Una vez hecho esto ya podemos hacer usode meacutetodos de tipo get para acceder a preferencias individuales Hay diferentes meacutetodosget para diferentes tipos de datos Cada uno de ellos requiere como paraacutemetro la claveque identifica la preferencia cuyo valor deseamos obtener y un valor por defecto el cualse usaraacute en el caso concreto en el que no existiera la preferencia con la clave pasada comoprimer paraacutemetro Podemos ver un ejemplo a continuacioacuten

public static String MIS_PREFS = MIS_PREFS

public void cargarPreferences() Obtener las preferencias almacenadasint modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences =

getSharedPreferences(MIS_PREFS modo)

boolean isTrue = misSharedPreferencesgetBoolean(isTruefalse)float unFloat = misSharedPreferencesgetFloat(unFloat0)int numeroEntero = misSharedPreferencesgetInt(numeroEntero0)long otroNumero = misSharedPreferencesgetLong(otroNumero0)String unaCadena = misSharedPreferencesgetString(unaCadena)

313 Interfaces para Shared Preferences

Android proporciona una plataforma basada en XML para la creacioacuten de interfacesgraacuteficas para el manejo de preferencias Estas interfaces tendraacuten un aspecto similar al delas del resto de aplicaciones del sistema Al utilizarla nos estaremos asegurando de quenuestras actividades para el manejo de preferencias seraacuten consistentes con las del resto deaplicaciones del sistema De esta forma los usuarios estaraacuten familiarizados con el manejode esta parte de nuestra aplicacioacuten

La plataforma consiste en tres elementos

bull Layout de la actividad de preferencias se trata de un archivo XML que definiraacute lainterfaz graacutefica de la actividad que muestre los controles para modificar los valores delas preferencias Permite especificar las vistas a mostrar los posibles valores que sepueden introducir y a queacute clave de Shared Preferences se corresponde cada vista

bull Actividad de preferencias una subclase de PreferenceActivity que secorresponderaacute con la pantalla de preferencias de nuestra aplicacioacuten

bull Shared Preference Change Listener una implementacioacuten de la claseonSharedPreferenceChangeListener que actuaraacute en el caso en el que cambie elvalor de alguna Shared Preference

314 Definiendo una pantalla de preferencias con un layout en XML

Persistencia

20Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esta es sin duda la parte maacutes importante de una actividad de preferencias Se trata de unarchivo XML utilizado para definir varios aspectos de dicha actividad Al contrario queen el caso de los archivos de layout para interfaces graacuteficas que vimos en la sesioacutencorrespondiente del moacutedulo de Android estos archivos de layout se guardan en la carpetaresxml de los recursos de la aplicacioacuten

Aunque conceptualmente estos layouts son similares a los utilizados para definirinterfaces graacuteficas los layouts de actividades de preferencias utilizan un conjuntoespecializado de componentes especiacuteficos para este tipo de actividades Estaacuten pensadospara proporcionar interfaces con un aspecto similar al del resto de actividades depreferencias del sistema Estos controles se describen en maacutes detalle en la siguienteseccioacuten

Los layout de preferencias contendraacuten un elemento PreferenceScreen

ltxml version=10 encoding=utf-8gtltPreferenceScreen

xmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceScreengt

Se pueden antildeadir maacutes elementos de este tipo si se desea Al hacerlo eacutestos serepresentaraacuten como un elemento seleccionable en un listado que mostraraacute una nuevaventana de preferencias en caso de que sea seleccionado

Dentro de cada elemento PreferenceScreen podemos incluir tantos elementos de tipoPreferenceCategory y elementos especiacuteficos para antildeadir controles como se desee Loselementos PreferenceCategory son utilizados para dividir cada pantalla de preferenciasen subcategoriacuteas mediante una barra de tiacutetulo Su sintaxis es la siguiente

ltPreferenceCategoryandroidtitle=My Preference Categorygt

ltPreferenceCategorygt

Una vez dividida la pantalla en subcategoriacuteas tan soacutelo quedariacutea antildeadir los elementoscorrespondientes a los controles especiacuteficos que veremos en la siguiente seccioacuten Losatributos de los elementos XML para estos controles pueden variar aunque existe unconjunto de atributos comunes

bull androidkey la clave de Shared Preference que se usaraacute para guardar el valor delcontrol

bull androidtitle texto a mostrar para describir la preferenciabull androidsummary una descripcioacuten maacutes larga a mostrar debajo del texto definido con

el campo anteriorbull androiddefaultValue el valor por defecto que se mostraraacute inicialmente y tambieacuten

el que finalmente se guardaraacute si ninguacuten otro valor fue asignado a este campo

El siguiente listado muestra una pantalla de preferencias muy simple con una uacutenicacategoriacutea y un uacutenico control (un check box)

ltxml version=10 encoding=utf-8gt

Persistencia

21Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltPreferenceScreenxmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceCategory

androidtitle=Mi categoriacuteagtltCheckBoxPreference

androidkey=MI_CHECK_BOXandroidtitle=Preferencia Check Boxandroidsummary=Descripcioacuten de la preferencia

Check BoxandroiddefaultValue=true

gtltPreferenceCategorygt

ltPreferenceScreengt

Esta pantalla de preferencias se mostrariacutea de la siguiente forma

Ejemplo sencillo de ventana de preferencias

315 Controles nativos para preferencias

Android incluye diferentes controles que podemos antildeadir a nuestras ventanas depreferencias por medio de los elementos XML que se indican a continuacioacuten

bull CheckBoxPreference un check box estaacutendar que puede ser utilizado parapreferencias de tipo booleano

bull EditTextPreference permite al usuario introducir una cadena como valor para unapreferencia Al seleccionar el texto se mostraraacute un diaacutelogo con el que introducir elnuevo valor

bull ListPreference el equivalente a un Spinner Seleccionar este elemento de lainterfaz mostraraacute un diaacutelogo con las diferentes opciones que es posible seleccionar Seutilizan arrays para asociar valores y textos a las opciones de la lista

bull RingtonePreference un tipo especiacutefico de preferencia de tipo listado que permiteseleccionar entre diferentes tonos de teleacutefono Esta opcioacuten es particularmente uacutetil en

Persistencia

22Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

el caso en el que se esteacute creando una pantalla de preferencias para configurar lasopciones de notificacioacuten de una aplicacioacuten o componente de la misma

Tambieacuten es posible crear nuestros propios controles personalizados definiendo unasubclase de Preference (o cualquiera de sus subclases) Se puede encontrar maacutesinformacioacuten en la documentacioacuten de AndroidhttpdeveloperandroidcomreferenceandroidpreferencePreferencehtml

316 Actividades de preferencias

Para mostrar una pantalla de preferencias debemos definir una subclase dePreferenceActivity

public class MisPreferencias extends PreferenceActivity

La interfaz graacutefica de la interfaz se puede crear a partir del layout en el meacutetodo onCreatemediante una llamada a addPreferencesFromResource

Overridepublic void onCreate(Bundle savedInstaceState)

superonCreate(savedInstanceState)addPreferencesFromResource(Rxmlmilayout)

Como en el caso de cualquier otra actividad la actividad de preferencias que hayamosdefinido debe ser incluida en el Manifest de la aplicacioacuten

ltactivity androidname=MisPreferenciasandroidlabel=Mis preferenciasgt

ltactivitygt

Eso es todo lo que necesitamos para crear este tipo de actividades y mostrar una ventanacon las preferencias definidas en el layout Esta actividad puede ser iniciada comocualquier otra mediante un Intent ya sea a traveacutes de una llamada a startActivity obien a startActivityForResult

Intent i = new Intent(this MisPreferenciasclass)startActivityForResult(i MOSTRAR_PREFERENCIAS)

Los valores para las diferentes preferencias de la actividad son almacenadas en elcontexto de la aplicacioacuten Esto permite a cualquier componente de dicha aplicacioacutenincluyendo a cualquier actividad y servicio de la misma acceder a los valores tal como semuestra en el siguiente coacutedigo de ejemplo

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto) PENDIENTE hacer uso de meacutetodos get para obtener los valores

317 Shared Preference Change Listeners

Persistencia

23Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El uacuteltimo elemento que nos queda por examinar en esta plataforma de Shared Preferenceses la interfaz onSharedPreferenceChangeListener que es utilizada para invocar unevento cada vez que una preferencia es antildeadida eliminada o modificada Para registrarListeners usamos un coacutedigo como el que se muestra a continuacioacuten en el que la parte maacutesimportante es sin duda el meacutetodo onSharedPreferenceChanged dentro del cualdeberemos determinar queacute preferencia ha cambiado de estado a partir de sus paraacutemetrospara llevar a cabo las acciones oportunas

public class MiActividad extends Activity implementsOnSharedPreferenceChangeListener

Overridepublic void onCreate(Bundle SavedInstanceState)

Registramos este objetoOnSharedPreferenceChangeListener

Context contexto = getApplicationContext()SharedPreferences prefs =

PreferenceManagergetDefaultSharedPreferences(contexto)prefsregisterOnSharedPreferenceChangeListener(this)

public void onSharedPreferenceChanged(SharedPreferences prefsString clave)

PENDIENTE comprobar queacute clave concreta ha cambiado ysu nuevo valor

para modificar la interfaz de una actividad o elcomportamiento de

alguacuten componente

Otra forma maacutes sencilla de controlar los cambios en las preferencias es registrar unlistener en el constructor de la actividad principal o aquella que haga uso de los valores depreferencias de la forma

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto)

mListener = new OnSharedPreferenceChangeListener() public void onSharedPreferenceChanged(SharedPreferences

sharedPreferences String key)

if( keyequals(MICLAVE) )boolean valor =

sharedPreferencesgetBoolean(MICLAVE false)

prefsregisterOnSharedPreferenceChangeListener(mListener)

NotaEn este segundo ejemplo mListener es una variable miembro de la clase Para este caso esnecesario registrar el listener de esta forma porque sino el garbage collector lo eliminariacutea (debidoa particularidades en la programacioacuten de los listeners asociados a las SharedPreferences)

Persistencia

24Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

32 Proveedores de contenidos

Los proveedores de contenidos o ContentProvider proporcionan una interfaz parapublicar y consumir datos identificando la fuente de datos con una direccioacuten URI queempieza por content Son una forma maacutes estaacutendar que los adaptadores (como el quevimos en la sesioacuten anterior en la seccioacuten dedicada a SQLite) de desacoplar la capa deaplicacioacuten de la capa de datos

Podemos encontrar dos tipos principales de proveedores de contenidos en Android Losproveedores nativos son proveedores que el propio sistema nos proporciona para poderacceder a datos del dispostivo incluyendo audio viacutedeo imaacutegenes informacioacuten personaletc Por otra parte tambieacuten es posible que creemos nuestros propios proveedores con talde permitir a nuestra aplicacioacuten compartir datos con el resto del sistema En esta sesioacutenhablaremos de ambos tipos

321 Proveedores nativos

Android incluye una serie de proveedores de contenidos que nos pueden proporcionarinformacioacuten de diferentes tipos informacioacuten sobre nuestros contactos informacioacuten delcalendario e incluso ficheros multimedia Ejemplos de estos proveedores de contenidosnativos son el Browser CallLog ContactsContract MediaStore Settings y elUserDictionary Para poder hacer uso de estos proveedores de contenidos y acceder alos datos que nos proporcionan debemos antildeadir los permisos adecuados al ficheroAndroidManifestxml Por ejemplo para acceder al listiacuten telefoacutenico podemos antildeadir elpermiso correspondiente de la siguiente forma

ltuses-sdk androidminSdkVersion=8 gtltuses-permission androidname=androidpermissionREAD_CONTACTSgt

ltmanifestgt

Para obtener informacioacuten de un ContentProvider debemos hacer uso del meacutetodo query

de la clase ContentResolver El primero de los paraacutemetros de dicho meacutetodo seraacute la URIdel proveedor de contenidos al que deseamos acceder Este meacutetodo devuelve un cursorque nos permitiraacute iterar entre los resultados tal como se vio en la sesioacuten anterior en elapartado de cursores

ContentResolverquery(Uri uriString[] projectionString selectionString[] selectionArgsString sortOrder)

Por ejemplo la siguiente llamada nos proporcionaraacute un cursor que nos permitiraacute acceder atodos los contactos de nuestro teleacutefono moacutevil Para ello hemos hecho uso de la constante

Persistencia

25Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ContactsContractContactsCONTENT_URI que almacena la URI del proveedor decontenidos correspondiente Al resto de paraacutemetros se le ha asignado el valor null

ContentResolver cr = getContentResolver()Cursor cursor = crquery(ContactsContractContactsCONTENT_URI

null null null null)

NotaEn versiones anteriores a Android 20 las estructuras de datos de los contactos son diferentes y nose accede a esta URI

Una vez obtenido el cursor es posible mapear sus campos con alguacuten componente de lainterfaz graacutefica de la actividad De esta forma cualquier cambio que se produzca en elcursor se reflejaraacute automaacuteticamente en el componente graacutefico sin que sea necesario queprogramemos el coacutedigo que lo refresque Para esto tenemos que llamar al meacutetodosetNotificationUri del cursor con lo cual nos aseguraremos de que los listeners seraacutennotificados cuando se produzca alguacuten cambio en los datos referenciados por dicho cursor

cursorsetNotificationUri(crContactsContractContactsCONTENT_URI)

Para asignar un adaptador a una lista de la interfaz graacutefica de la actividad maacutesconcretamente una ListView utilizamos el meacutetodo setAdapter(Adapter)

ListView lv = (ListView)findViewById(RidListView01)SimpleCursorAdapter adapter = new SimpleCursorAdapter(

getApplicationContext()Rlayouttextviewlayoutcursornew String[]

ContactsContractContacts_IDContactsContractContactsDISPLAY_NAME

new int[]RidTextView1RidTextView2)

lvsetAdapter(adapter)

En este ejemplo los identificadores RidTextView1 y RidTextView2 se correspondencon views del layout que define cada fila de la ListView como se puede ver en elarchivo textviewlayoutxml que tendriacuteamos que crear

ltxml version=10 encoding=utf-8gt

ltLinearLayout xmlnsandroid=httpschemasandroidcomapkresandroidandroidorientation=horizontalandroidlayout_width=fill_parentandroidlayout_height=wrap_contentgt

ltTextView androidid=+idTextView1androidtextStyle=bold

Persistencia

26Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

androidems=2androidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltTextView androidid=+idTextView2androidtextStyle=boldandroidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltLinearLayoutgt

La lista en siacute (identificada por RidListView01 en el presente ejemplo) estariacutea en otroXML layout por ejemplo en mainxml

Se debe tener en cuenta que en el caso de este ejemplo si quisieacuteramos obtener losnuacutemeros de teleacutefono de cada uno de nuestros contactos tendriacuteamos que recorrer el cursorobtenido a partir del proveedor de contenidos y por cada persona en nuestra agenda crearun nuevo cursor que recorriera los teleacutefonos ya que una persona puede tener asignadosvarios teleacutefonos

Puedes acceder a un listado completo de los proveedores de contenidos nativos existentesen Android consultando su manual de desarrollo Concretamente leyendo la seccioacutendedicada al paquete androidprovider enhttpdeveloperandroidcomreferenceandroidproviderpackage-summaryhtml

322 Proveedores propios crear un nuevo proveedor de contenidos

Para acceder a nuestras fuentes de datos propias de manera estaacutendar nos interesaimplementar nuestros propios ContentProvider Gracias a que proporcionamos nuestrosdatos a partir de una URI quien use nuestro proveedor de contenidos no deberaacutepreocuparse de doacutende provienen los datos indicados por dicha URI podriacutean provenir deficheros locales en la tarjeta de memoria de un servidor en Internet o de una base dedatos SQLite Dada una URI nuestro proveedor de contenidos deberaacute proporcionar en suinterfaz los meacutetodos necesarios para realizar las operaciones baacutesicas en una base de datosinsercioacuten lectura actualizacioacuten y borrado

Para crear un nuevo proveedor de contenidos definimos una subclase deContentProvider Utilizaremos una sobrecarga del meacutetodo onCreate para crear einicializar la fuente de datos que queramos hacer puacuteblica a traveacutes de dicho proveedor Unesqueleto de subclase de ContentProvide podriacutea ser el siguiente

public class MiProveedor extends ContentProvider

Overridepublic boolean onCreate()

Inicializar la base de datosreturn true

Dentro de la clase deberemos crear un atributo puacuteblico y estaacutetico de nombreCONTENT_URI en el cual almacenaremos el URI completo del proveedor que estamos

Persistencia

27Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creando Este URI debe ser uacutenico por lo que una buena idea puede ser tomar como baseel nombre de paquete de nuestra aplicacioacuten La forma general de un URI es la siguiente

content[NOMBRE_PAQUETE]provider[NOMBRE_APLICACION][RUTA_DATOS]

Por ejemplo

contentesuajtechandroidprovidermiaplicacionelementos

En general (y por simplicidad) no es necesario antildeadir la cadena provider al nombre delpaquete por lo que podemos utilizar directamente el nombre del paquete seguido de laruta de datos de la forma

contentesuajtechandroidmiaplicacionelementos

Estos URIs se pueden presentar en dos formas La URI anterior representa una consultadirigida a obtener todos los valores de ese tipo (en este caso todos los elementos) Si a laURI anterior antildeadimos un sufijo [NUMERO_FILA] estamos representando una consultadestinada a obtener un uacutenico elemento (por ejemplo el quinto elemento en el siguienteejemplo)

contentesuajtechandroidprovidermiaplicacionelementos5

NotaSe considera una buena praacutectica de programacioacuten el proporcionar acceso a nuestro proveedor decontenidos utilizando cualquiera de estas dos formas

La forma maacutes simple de determinar cuaacutel de las dos formas anteriores de URI se utilizoacutepara hacer una peticioacuten a nuestro proveedor de contenidos es mediante un Uri MatcherCrearemos y configuramos un elemento Uri Matcher para analizar una URI y determinara cuaacutel de las dos formas se corresponde En el siguiente coacutedigo se muestra el coacutedigobaacutesico que deberemos emplear para implementar este patroacuten

public class MiProveedor extends ContentProvider

private static final String miURI =contentesuajtechandroidprovidermiaplicacionelementos

public static final Uri CONTENT_URI = Uriparse(miUri)

Creamos las constantes que nos van a permitir diferenciar entre los dos tipos distintos de peticiones a partir de la URIprivate static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

Inicializamos el objeto UriMatcher Una URI que termine en elementos se corresponderaacute con una consulta para todas las filas mientras que una URI con el sufijo elementos[FILA] representaraacute una uacutenica filastatic

uriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidprovidermiaplicacion

elementos TODAS_FILAS)

Persistencia

28Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

uriMatcheraddURI(esuajtechandroidprovidermiaplicacionelementos UNA_FILA)

Overridepublic boolean onCreate()

PENDIENTE inicializar la base de datosreturn true

Esta misma teacutecnica se puede emplear para proporcionar diferentes alternativas de URIpara diferentes subconjuntos de datos como por ejemplo diferentes tablas en una base dedatos a traveacutes del mismo proveedor de contenidos

323 Proveedores propios crear la interfaz de consultas

Para permitir realizar operaciones con los datos publicados a traveacutes de nuestro proveedorde contenidos deberemos implementar los meacutetodos delete insert update y query

para el borrado insercioacuten actualizacioacuten y realizacioacuten de consultas respectivamenteEstos meacutetodos son la interfaz estaacutendar utilizada con proveedores de contenidos de formaque para compartir datos entre aplicaciones no se tendraacuten interfaces distintos paradiferentes fuentes de datos

Lo maacutes habitual suele ser implementar un proveedor de contenidos como un mecanismopara permitir el acceso a una base de datos SQLite privada a una aplicacioacuten aunque atraveacutes de estos meacutetodos indicados se podriacutea en principio acceder a cualquier fuente dedatos

El siguiente coacutedigo extiende el ejemplo anterior (MiProveedor) y muestra el esqueleto deestos meacutetodos dentro de una subclase de ContentProvider Obseacutervese que se hace usodel objeto UriMatcher para determinar que tipo de consulta se estaacute realizando

Overridepublic Cursor query(Uri uri

String[] projectionString selectionString[] selectionArgsString sort)

Si se trata de una consulta para obtener una uacutenica filalimitamos

los resultados a obtener de la fuente de datos por medio de una claacuteusula whereswitch(UriMatchermatch(uri))

case UNA_FILA PENDIENTE modifica la sentencia SELECT

mediante una claacuteusula where obteniendo el identificador de fila

como numeroFila = urigetPathSegments()get(1))

return null

Override

Persistencia

29Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public Uri insert(Uri _uri ContentValues _valores) long idFila = [ Antildeadir un nuevo elemento ]

Devolvemos la URI del elemento recieacuten antildeadidoif (idFila gt 0)

return ContentUriswithAppendendId(CONTENT_URI idFila)

throw new SQLException(No se pudo antildeadir un nuevo elemento a +_uri)

Overridepublic int delete(Uri uri String where String[] whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

Overridepublic int update(Uri uri ContentValues valores String where String[]whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

324 Proveedores propios tipo MIME

El uacuteltimo paso para crear un proveedor de contenidos es definir el tipo MIME queidentifica el tipo de datos que devuelve el proveedor Debemos sobrecargar el meacutetodogetType para que devuelva una cadena que identifique nuestro tipo de datos de manerauacutenica Se deberiacutean definir dos tipos posibles uno para el caso de una uacutenica fila y otro parael caso de devolver todos los resultados Se debe utilizar una sintaxis similar a la delsiguiente ejemplo

bull Un uacutenico elementovndesuajtechandroidmiaplicacioncursoritemmiproveedorcontent

bull Todos los elementosvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

Como se puede observar la cadena comienza siempre por vnd A continuacioacutentendriacuteamos el nombre del paquete de nuestra aplicacioacuten Lo siguiente es cursor ya quenuestro proveedor de contenidos estaacute devolviendo datos de tipo Cursor Justo antes de labarra pondremos item en el caso del tipo MIME para un uacutenico elemento y dir para elcaso del tipo MIME para todos los elementos Finalmente tras la barra pondremos elnombre de nuestra clase (en minuacutesculas) seguido de content En el siguiente coacutedigo deejemplo se muestra coacutemo sobrecargar getType seguacuten la URI

Persistencia

30Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Overridepublic String getType(Uri _uri)

switch (uriMatchermatch(_uri)) case TODAS_FILAS

returnvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

case UNA_FILAreturn

vndesuajtechandroidmiaplicacioncursoritemmiproveedorcontentdefault

throw new IllegalArgumentException(URI nosoportada + _uri)

325 Proveedores propios registrar el proveedor

No debemos olvidar incorporar nuestro proveedor de contenidos al Manifest de laaplicacioacuten una vez que lo hemos completado Esto se haraacute por medio del elementoprovider cuyo atributo authorities podremos utilizar para especificar la URI base talcomo se puede ver en el siguiente ejemplo

ltprovider androidname=MiProveedorandroidauthorities=esuajtechandroidmiaplicaciongt

326 Content Resolvers

Aunque en la seccioacuten de proveedores nativos ya se ha dado alguacuten ejemplo de coacutemo haceruso de un Content Resolver para realizar una consulta ahora que hemos podido observaren detalle la estructura de un proveedor de contenidos propio es un buen momento paraver en detalle coacutemo realizar cada una de las operaciones que eacutestos permiten

El contexto de una aplicacioacuten incluye siempre una instancia de la claseContentResolver a la cual se puede acceder mediante el meacutetodo getContentResolver

ContentResolver cr = getContentResolver()

Esta clase incorpora diversos meacutetodos para realizar consultas a proveedores decontenidos Cada uno de estos meacutetodos acepta como paraacutemetro una URI que indica conqueacute proveedor de contenidos se desea interactuar

Las consultas realizadas sobre un proveedor de contenidos recuerdan a las consultasrealizadas sobre bases de datos Los resultados de las consultas se devuelven comoobjetos de la clase Cursor Se pueden extraer valores del cursor de la misma forma en laque se explicoacute en el apartado de bases de datos de la sesioacuten anterior El meacutetodo query dela clase ContentResolver acepta los siguientes paraacutemetros

bull La URI del proveedor del contenidos al que se desea realizar la consultabull Una proyeccioacuten que enumera las columnas que se quieren incluir en los resultados

obtenidosbull Una claacuteusula where que define las filas a obtener Se pueden utilizar comodines que

Persistencia

31Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

se reemplazaraacuten por valores incluidos en el siguiente paraacutemetrobull Una matriz de cadenas que se pueden utilizar para reemplazar los comodines en la

claacuteusula where anteriorbull Una cadena que describa la ordenacioacuten de los elementos devueltos

El siguiente coacutedigo muestra un ejemplo

ContentResolver cr = getContentResolver() Devolver todas las filasCursor todasFilas = crquery(MiProveedorCONTENT_URI null null nullnull) Devolver todas columnas de las filas para las que la columna 3 tiene un valor determinado ordenadas seguacuten el valor de la columna 5String where = COL3 + = + valorString orden = COL5Cursor algunasFilas = crquery(MiProveedorCONTENT_URI null where nullorden)

327 Otras operaciones con Content Resolvers

Para realizar otro tipo de operaciones con los datos de un proveedor de contenidosharemos uso de los meacutetodos delete update e insert de un objeto ContentResolver

Con respecto a la insercioacuten la clase ContentResolver proporciona en su interfaz dosmeacutetodos diferentes insert y bulkInsert Ambos aceptan como paraacutemetro la URI delproveedor de contenidos pero mientras que el primer meacutetodo tan solo toma comoparaacutemetro un objeto de la clase ContentValues (ya vistos en la seccioacuten de bases dedatos) el segundo toma como paraacutemetro una matriz de elementos de este tipo El meacutetodoinsert devolveraacute la URI del elemento recieacuten antildeadido mientras que bulkInsert

devolveraacute el nuacutemero de filas correctamente insertadas

Obtener el Content ResolverContentResolver cr = getContentResolver()

Crear una nueva fila de valores a insertarContentValues nuevosValores = new ContentValues()

Asignar valores a cada columnanuevosValoresput(NOMBRE_COLUMNA nuevoValor)[ Repetir para cada columna ]

Uri miFilaUri = crinsert(MiProveedorCONTENT_URI nuevosValores)

Insertamos ahora una matriz de nuevas filasContentValues[] arrayValores = new ContentValues[5] PENDIENTE rellenar el array con los valores correspondientesint count = crbulkInsert(MiProveedorCONTENT_URI arrayValores)

Para el caso del borrado hacemos uso del meacutetodo delete del Content Resolver pasandocomo paraacutemetro la URI de la fila que deseamos eliminar de la fuente de datos Otraposibilidad es incluir una claacuteusula where para eliminar muacuteltiples filas Ambas teacutecnicas semuestran en el siguiente ejemplo

ContentResolver cr = getContentResolver()

Persistencia

32Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Eliminar una fila especiacuteficacrdelete(miFilaUri null null) Eliminar las primeras cinco filasString where = _id lt 5crdelete(MiProveedorCONTENT_URI where null)

Una actualizacioacuten se lleva a cabo mediante el meacutetodo update del objeto ContentResolver Este meacutetodo toma como paraacutemetro la URI del proveedor de contenidosobjetivo un objeto de la clase ContentValues que empareja nombres de columna convalores actualizados y una claacuteusula where que indica queacute filas deben ser actualizadas Elresultado de la actualizacioacuten seraacute que en cada fila en la que se cumpla la condicioacuten de laclaacuteusula where se cambiaraacuten los valores de las columnas especificados por el objeto de laclase ContentValues tambieacuten se devolveraacute el nuacutemero de filas correctamenteactualizadas A continuacioacuten se muestra un ejemplo

ContentValues nuevosValores = new ContentValues()

Utilizamos el objeto ContentValues para especificar en queacute columnas queremos que se produzca un cambio y cuaacutel debe ser el nuevo valor de dichas columnasnuevosValuesput(NOMBRE_COLUMNA nuevoValor)

Aplicamos los cambios a las primeras cinco filasString where = _id lt 5

getContentResolver()update(MiProveedorCONTENT_URI nuevosValores wherenull)

Persistencia

33Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

4 Ejercicios - Persistencia en Android proveedores de contenidos ySharedPreferences

41 Compartir datos entre actividades con Shared Preferences (075 puntos)

Descarga de las plantillas el proyecto Dni Dicho proyecto estaacute compuesto de dosactividades Formulario y Resumen El objetivo del ejercicio es conseguir que al pulsar elbotoacuten Siguiente en la actividad Formulario se muestre en la actividad Resumen cuaacutelesfueron los datos introducidos

En este ejercicio vamos a hacer uso de SharedPreferences para pasar los datos de unaactividad a la siguiente Al pulsar en Siguiente en Formulario deberemos guardar el valorde todos los campos mediante esta plataforma Al mostrarse la actividad Resumendeberemos leer estos datos tambieacuten a partir de SharedPreferences para mostrarlos porpantalla

Por uacuteltimo crea un meacutetodo que valide el DNI (debe tratarse de una secuencia de ochodiacutegitos entre 0 y 9 y una letra al final) Si se pulsa Siguiente y el DNI no tiene el formatocorrecto no se deberaacute pasar a la actividad Resumen En lugar de esto se mostraraacute unToast en pantalla con un mensaje de error

42 Actividad de preferencias (075 puntos)

En este ejercicio seguimos trabajando con el proyecto del ejercicio anterior Vamos aantildeadir un menuacute de opciones que permita escoger en queacute momento se comprobaraacute lavalidez del DNI Para ello antildeade en primer lugar un menuacute a la actividad Formulario conuna uacutenica opcioacuten cuyo nombre seraacute Opciones

Al seleccionar esta opcioacuten se deberaacute mostrar una actividad de preferencias cuyo aspectoseraacute el siguiente

Persistencia

34Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Preferencias de la aplicacioacuten Dni

El significado de las opciones seraacute el siguiente

bull En el campo con esta opcioacuten seleccionada al pulsar sobre el campo de DNI en elformulario deberaacute mostrarse un Toast indicando si la sintaxis del DNI es correcta ono

bull Al pulsar si se desactiva esta opcioacuten (que deberaacute estar activada por defecto) no seharaacute la comprobacioacuten del DNI al pulsar el botoacuten Siguiente por lo que siempre seraacuteposible pasar de la actividad Formulario a la actividad Resumen sea cual sea elformato del DNI introducido

bull El uacuteltimo check box se encontraraacute siempre deshabilitado iquestQueacute atributo debemosutilizar para conseguir esto

Crea dos variables booleanas llamadas enElCampo y alPulsar Su valor dependeraacute delestado de los checkboxes anteriores y se actualizaraacute por medio del eventoonSharedPreferenceChange Utiliza el valor de estas variables en el coacutedigo de tuactividad para que esta tenga el comportamiento indicado

43 Proveedor de contenidos propio (075 puntos)

Vamos a implementar otra forma de acceder a la base de datos de usuarios de laaplicacioacuten BaseDatos desarrollada durante los ejercicios de la sesioacuten anterior siguiendoesta vez el patroacuten de disentildeo ContentProvider de Android Seguiremos trabajando puescon dicho proyecto

bull Creamos una nueva clase llamada UsuariosProvider que herede deContentProvider Esto nos obligaraacute a sobrecargar una serie de meacutetodos abstractosAntes de implementar la query vamos a configurar el provider

bull Antildeadimos algunos campos tiacutepicos de los content provider

Persistencia

35Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public static final Uri CONTENT_URI =Uriparse(contentesuajtechandroidbasedatosusuarios)

private static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

staticuriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

TODAS_FILAS)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

UNA_FILA)

bull Vamos a acceder a la misma base de datos de usuarios utilizada en los ejercicios de lasesioacuten anterior pero no vamos a hacerlo a traveacutes del adaptador que tuvimos queimplementar sino que vamos a copiar de eacutel el coacutedigo que nos haga falta Copia loscampos que definen el nombre de la base de datos de la tabla de las columnas laversioacuten asiacute como la referencia al contexto y a la base de datos La sentenciacompilada del insert ya no va a hacer falta Inicializa los valores necesarios en elconstructor Para inicializar la referencia a la base de datos vamos a utilizar una vezmaacutes MiOpenHelper Podemos copiarlo del adaptador que ya implementamos en losejercicios de la sesioacuten anterior

bull Implementa de forma apropiada el getType para devolver un tipo MIME diferenteseguacuten si se trata de una URI de una fila o de todas las filas Para ello ayuacutedate deluriMatcher

bull Implementa el meacutetodo query Simplemente se trata de devolver el cursor queobtenemos al hacer una consulta a la base de datos SQLite Algunos de los paraacutemetrosque le pasaremos los recibimos como paraacutemetros del meacutetodo del provider Los que notengamos iraacuten con valor null

bull Aunque no tenemos todos los meacutetodos del UsuariosProvider implementadospodemos probarlo Para ello debemos registarlo en el AndroidManifestxml

ltprovider androidname=UsuariosProvider

androidauthorities=esuajtechandroidbasedatosgtltapplicationgt

ltmanifestgt

bull En la clase Main se antildeadioacute codigo para insertar una serie de valores en la base dedatos y mostrarlos en el campo de texto Manteniendo este coacutedigo vamos a antildeadir alcampo de texto el resultado obtenido con la consulta del UsuariosProvider paracomprobar que tanto el adaptador de la base de datos como el proveedor nosdevuelven el mismo resultado

44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)

Porque es la forma estaacutendar que establece Android de acceder a contenidos Ademaacutes elproveedor de contenidos nos permitiraacute notificar al ContentResolver de los cambiosocurridos Asiacute componentes en la pantalla podraacuten refrescarse de forma automaacutetica

Persistencia

36Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Utiliza el proyecto ProveedorContenidos de las plantillas Implementa la insercioacuten enel proveedor de contenidos Prueacutebala insertando algunos usuarios de ejemplo en laclase Main Implementa tambieacuten el OnClickListener del botoacuten que inserta nuevosusuarios El nombre del nuevo usuario iraacute indicado en el EditText

bull Comprueba que la insercioacuten funciona y que gracias a la siguiente liacutenea y al estarusando un proveedor de contenidos la lista se actualiza automaacuteticamente cuandoocurre alguacuten cambio sin necesidad de pedir expliacutecitamente la actualizacioacuten al pulsarel botoacuten

cursorsetNotificationUri(cr UsuariosProviderCONTENT_URI)

NotaEsto no va a funcionar si se notifica que se ha producido un cambio al insertar o borrar unelemento Para ello usamosgetContext()getContentResolver()notifyChange(UsuariosProviderCONTENT_URI null)

bull Implementa el meacutetodo delete del proveedor de contenidos Prueacutebalo en la claseMain Termina de implementar el onCreateContextMenuListener que se ejecutaraacutecada vez que se haga una pulsacioacuten larga sobre alguna entrada de la lista Compruebaque funciona (eliminando el usuario correspondiente y no otro)

Persistencia

37Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

5 Persistencia de datos en iOS Ficheros y SQLite

51 Introduccioacuten

La persistencia de datos en iOS se realiza en una pequentildea memoria flash que tienenuestro dispositivo la cual es equivalente a un disco duro de tamantildeo limitado Los datosque ahiacute se almacenen se conservaraacuten aunque el dispositivo se apague Por motivos deseguridad el SO de iOS no permite que las aplicaciones accedan directamente a lamemoria interna pero a cambio existe una API completa de acceso a la porcioacuten dememoria que le corresponde a la app que desarrollemos

En una aplicacioacuten iOS encontraremos baacutesicamente 4 modos de almacenamiento deinformacioacuten de forma persistente

bull Ficheros de propiedades (plist)bull Bases de datos embebidas SQLitebull Datos de usuario (User Defaults)bull Core Data

En esta primera sesioacuten veremos coacutemo usar los ficheros de texto (Property Lists) y elalmacenamiento en base de datos de tipo SQLite

52 Ficheros plist (Property Lists)

El almacenamiento de datos en ficheros de propiedades es uno de los maacutes usados en eldesarrollo de las aplicaciones para iOS Se usa principalmente para guardar datos deconfiguracioacuten de la aplicacioacuten u otra informacioacuten poco compleja y acceder a ella deforma sencilla Si estamos desarrollando un juego podemos usarlos para la definicioacuten delos niveles almacenar las puntuaciones del jugador etc

521 Leyendo datos desde ficheros plist

Los ficheros plist (Property Lists) tienen un formato XML aunque XCode nosproporciona una interfaz que nos permite acceder a ellos de forma muy sencilla Paraentender el funcionamiento de este tipo de ficheros vamos a realizar un ejemplo en el queprimero crearemos un fichero plist lo completaremos con datos de ejemplo y despueacutes loleeremos para mostrarlo por pantalla

Un ejemplo de un fichero plist podriacutea ser el siguiente

Persistencia

38Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Fichero plist

Antes de nada creamos un proyecto nuevo en XCode de tipo Window-based

Application al que llamaremos sesion03-ejemplo1 Para crear un fichero plistdebemos hacer click derecho sobre el directorio Supporting Files de nuestro proyectoy seleccionamos iOS gt Resource gt Property List Lo guardaremos con el nombreconfigUsuario Seguidamente ya podemos acceder al fichero recieacuten creado haciendoclick sobre el en este momento XCode nos mostraraacute un pequentildeo editor totalmente vacioen el que iremos rellenando con datos Podemos verlo tambieacuten en formato XML sihacemos click derecho sobre el y seleccionamos Open As gt Preview

Para empezar a rellenar el fichero que acabamos de crear hacemos click derecho dentrode su contenido (ahora vacio) y seleccionamos Add row Entonces nos apareceraacute unafila vaciacutea Dentro del campo Key escribimos la clave un nombre descriptivo quedeberemos utilizar maacutes adelante para acceder a los datos En nuestro caso vamos aescribir config como clave Dentro de la columna Type seleccionamosDictionary Veremos que aparece una flecha en el lado izquierdo que indica quepueden haber subniveles dentro de nuestro diccionario

Ahora antildeadimos un nuevo item que cuelgue del diccionario que acabamos de crear paraello hacemos click sobre la flecha de la izquierda esta se giraraacute hacia abajo entonceshacemos click ahora sobre el siacutembolo + (maacutes) Nos apareceraacute una nueva liacutenea que cuelgadel diccionario

En esta nueva liacutenea dentro del campo key escribimos nombre el campo type lodejamos como de tipo String ya que va a ser una cadena de texto y en la columna deValue escribimos nuestro nombre por ejemplo Javi

Ahora vamos a antildeadir otro campo nuevo en nuestra configuracioacuten para ello hacemosclick sobre el siacutembolo + y nos apareceraacute una nueva liacutenea justo al mismo nivel que laanterior y colgando del diccionario Dentro del campo de Key escribimos ciudad yen Value escribimos Alicante El campo de Type lo dejamos como String

Persistencia

39Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora dentro de nuestro fichero plist vamos a indicar los dispositivos que dispone elusuario para ello creamos una nueva linea dentro del diccionario y le pondremos comoclave dispositivos Seleccionamos el tipo Array y seguimos los mismos pasosque al crear el diccionario para ello hacemos click sobre la flecha de la izquierda ydespueacutes sobre el siacutembolo + (maacutes) Ahora nos apareceraacute una nueva fila con clave Item 0Seleccionamos el tipo String y en el campo Value iPhone

Ahora repetimos el paso 3 veces indicando como values iPad Android y

Blackberry

Una vez seguidos todos estos pasos tendremos nuestro fichero de propiedades listo Debede quedar como se muestra a continuacioacuten

Fichero plist terminado

Este fichero se almacenaraacute dentro del directorio de bundle cuando compilemos laaplicacioacuten En el caso de que queramos escribir en el debermos almacenar el ficherodentro del directorio de Documents del binario

Ahora desde nuestro coacutedigo vamos a acceder a eacutel y a mostrarlo por pantalla para elloescribimos el siguiente coacutedigo dentro del meacutetodo didFinishLaunchingWithOptions dela clase delegada

Cargamos el fichero PLISTNSString mainBundlePath = [[NSBundle mainBundle] bundlePath]NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]NSDictionary diccionario = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionario delraiz

NSString nombre = [dicConfig objectForKeynombre]

Persistencia

40Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

[selfwindow makeKeyAndVisible]return YES

Si todo ha ido correctamente deberemos obtener la siguiente salida por la consola

Lista de dispositivos

522 Escribiendo datos en ficheros plist

Partiendo del ejemplo anterior vamos a escribir ahora en el fichero para ello primerodebemos comprobar que el fichero plist se encuentre dentro del directorio de Documents

del dispositivo en el caso de que no lo esteacute (por ejemplo si es la primera vez que arrancala aplicacioacuten) debemos copiarlo a ese directorio Una vez que tenemos el fichero dentrodel directorio de documentos ya podemos escribir en el

Para realizar todo el proceso de comprobacioacuten de la existencia del fichero dentro deldirectorio de documentos copiarlo en ese directorio y leer los datos debemos de sustituirel coacutedigo que hay dentro del meacutetodo didFinishLaunchingWithOptions de la clasedelegada por este otro

AtencioacutenPara que funcione la escritura en un fichero plist este debe de estar dentro del directorio deDocuments de nuestro dispositivo iOS no en el boundle

NSDictionary diccionarioRaiz

Ruta del directorio de documentos de nuestro dispositivoNSArray paths =

NSSearchPathForDirectoriesInDomains(NSDocumentDirectoryNSUserDomainMask YES)

if ([paths count] gt 0)

Persistencia

41Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

Primero comprobamos si existe el fichero en eldirectorio de documentos

BOOL fileExists = [[NSFileManager defaultManager]fileExistsAtPathdocumentsFilename]if (fileExists)

NSLog(Fichero encontrado en directorio de documentosOK

documentsFilename)

Si existe ya cargamos los datos desde aquidirectamente

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

else

NSLog(No se encuentra el fichero configUsuarioplisten

el directorio de documentos-gtLo creamos)

Si no existe primero cargamos el fichero PLISTdesde el Boundle

NSString mainBundlePath = [[NSBundle mainBundle]bundlePath]

NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Y despues escribimos los datos cargados (eldiccionario completo)

a un fichero nuevo de la carpeta de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

NSLog(plist de configUsuario creado)

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionariodel raiz

NSString nombre = [dicConfig objectForKeynombre]NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

Persistencia

42Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Por ultimo liberamos la memoria del diccionario raiz[diccionarioRaiz release]

[selfwindow makeKeyAndVisible]return YES

Para comprobar el funcionamiento arrancamos la aplicacioacuten y veremos que la primera vezse crearaacute el fichero dentro de la carpeta de Documents de nuestro dispositivo y en lassiguientes ya no se crearaacute y simplemente lo leeraacute desde la carpeta de Documents

Ahora que ya tenemos la gestioacuten de ficheros baacutesica completada vamos a escribir sobre elalguacuten dato Para ello simplemente escribimos el siguiente fragmento de coacutedigo justo antesde la liacutenea [selfwindow makeKeyAndVisible]

Cambiamos el nombre y lo guardamos en el fichero Ruta del directorio de documentos de nuestro dispositivo

if ([paths count] gt 0)

Cargamos el fichero desde el directorio dedocumentos del dispositivo

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cambiamos el valor del nombre en el diccionario[dicConfig setValueOtro nombre forKeynombre]

Escribimos todo el diccionario de nuevo al fichero del directorio de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

Por ultimo liberamos el diccionario de lamemoria

[diccionarioRaiz release]

Lo que hemos hecho en el coacutedigo anterior es cargar todo el diccionario de nuevo desde eldirectorio de documentos modificar los datos que queremos cambiar o incluso antildeadir ydespueacutes guardar el diccionario en el fichero

Volvemos a arrancar la aplicacioacuten y veremos que ha cambiado el campo de nombre

Nota

Persistencia

43Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

La lectura y escritura de datos en un fichero plist se debe de hacer de una soacutela vez nunca porpartes Siempre se debe de cargar o escribir un diccionario NSDictionary como elementoprincipal del fichero

53 SQLite

SQLite es una base de datos relacional ligera con la que se puede trabajar de formasencilla mediante sentencias SQL La base de datos se almacenaraacute en un fichero dentrode nuestra aplicacioacuten Este meacutetodo de persistencia puede ser el adecuado cuando los datosa almacenar esteacuten en un formato de tablas filas y columnas y se necesite que los datossean accesibles de forma raacutepida

531 Herramientas disponibles

Para explicar el funcionamiento de las librerias de SQLite de Cocoa Touch vamos a seguirun ejemplo completo Comenzamos creando la base de datos para ello podemos utilizaruno de los frontends que facilitan la gestioacuten del SQLite uno bastante sencillo yrecomendado es el SQLite Database Browser y otro es fmdb

Nosotros utilizaremos el primero (SQLite Database Browser) Abrimos la aplicacioacuten yhacemos click sobre el botoacuten de crear nueva base de datos que llamaremospersonasDBsqlite Seguidamente creamos las tablas para simplificar en nuestroejemplo crearemos una soacutela tabla llamada personas Esta tabla tendraacute las siguientescolumnas id (NUMERIC) nombre (TEXT) apellidos (TEXT) dni (TEXT) edad

(NUMERIC) y localidad (TEXT)

Persistencia

44Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Estructura de la Base de Datos

Una vez creada la tabla pasamos a insertar datos dentro de ella para ello abrimos lapestantildea de Browse Data y hacemos click en New Record Cuando tengamos al menos 3registros metidos dentro de la tabla ya podemos guardar la base de datos Hacemos clicksobre guardar le ponemos el nombre que queramos y elegimos cualquier directorio delMAC

Datos de la Base de Datos

532 Lectura de datos

Una vez que tenemos la base de datos SQLite creada ya podemos empezar a escribir elcoacutedigo para leer y mostrar los datos dentro de nuestra aplicacioacuten

Primero empezamos creando un proyecto nuevo en XCode usando la plantilla basada envistas View-based application y lo llamaremos sesion03-ejemplo2 Ahora antildeadimosel framework de sqlite a nuestro proyecto para ello hacemos click sobre el raiz delproyecto y seleccionamos la pestantildea Build Phases Desplegamos el listado de Link

Binary With Libraries y hacemos click sobre el botoacuten (+) El el listadoseleccionamos el framework libsqlite3dylib y hacemos click en Add

Persistencia

45Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Antildeadiendo el framework libsqlite3dylib

Ahora antildeadimos a nuestro proyecto la base de datos que hemos creado anteriormentepara ello arrastramos el fichero personasDBsqlite a nuestro directorio Supporting

Files Asegurate de seleccionar la opcioacuten de Copy items to destination groups

folder (if needed) para que se copie el fichero dentro de la carpeta del proyecto

Para acelerar el proceso de lectura de la base de datos y ahorrar memoria vamos a cargarprimero todos los datos en objetos para ello vamos a crear una clase que se encargue dealmacenar los datos Hacemos click derecho sobre el raiz del proyecto New file gtObjective-C class seleccionamos NSObject como clase principal y le damos a NextEscribimos como nombre de la clase Persona y hacemos click en save

Personah

imports iniciales

interface Persona NSObject int _uIdNSString _nombreNSString _apellidos

Persistencia

46Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSInteger _edadNSString _localidad

property (nonatomic assign) int uIdproperty (nonatomic copy) NSString nombreproperty (nonatomic copy) NSString apellidosproperty (nonatomic) NSInteger edadproperty (nonatomic copy) NSString localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidos edad(NSInteger)edadlocalidad(NSString )localidad

end

Personam

import Personah

implementation Persona

synthesize uId = _uIdsynthesize nombre = _nombresynthesize apellidos = _apellidossynthesize edad = _edadsynthesize localidad = _localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidosedad(NSInteger)edad localidad(NSString )localidad

if ((self = [super init])) selfuId = uIdselfnombre = nombreselfapellidos = apellidosselfedad = edadselflocalidad = localidad

return self

- (void) dealloc selfnombre = nilselfapellidos = nilselflocalidad = nil[super dealloc]

end

Una vez creada la clase Persona vamos a crear la clase encargada de manejar la basede datos sqlite3 Otra opcioacuten seriacutea cargar todos los datos directamente desde la clasedelegada al arrancar la aplicacioacuten pero no es recomendable ya que si en alguacuten momentotenemos que cambiar de motor de base de datos lo tendremos maacutes complicado

Persistencia

47Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creamos entonces una clase dentro de nuestro proyecto llamada BDPersistencia queherede de NSObject y escribimos el siguiente coacutedigo

BDPersistenciah

imports iniciales

interface BDPersistencia NSObject sqlite3 _database

+ (BDPersistencia)database- (NSArray )arrayPersonas

end

BDPersistenciam

import BDPersistenciahimport Personah

implementation BDPersistencia

static BDPersistencia _database

+ (BDPersistencia)database if (_database == nil)

_database = [[BDPersistencia alloc] init]return _database

- (id)init if ((self = [super init]))

NSString sqLiteDb = [[NSBundle mainBundle]pathForResourcepersonasDB

ofTypesqlite]

if (sqlite3_open([sqLiteDb UTF8String]amp_database) = SQLITE_OK)

NSLog(Fallo al abrir la BD)

return self

- (void)dealloc sqlite3_close(_database)[super dealloc]

- (NSArray )arrayPersonas

NSMutableArray arraySalida = [[[NSMutableArray alloc]init]

autorelease]

Persistencia

48Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString query = SELECT id nombre apellidoslocalidad edad

FROM personas ORDER BY apellidos DESC

sqlite3_stmt statementif (sqlite3_prepare_v2(_database [query UTF8String]

-1ampstatement nil)

== SQLITE_OK) while (sqlite3_step(statement) == SQLITE_ROW)

int uniqueId = sqlite3_column_int(statement0)

char nombreChar = (char )sqlite3_column_text(statement 1)

char apellidosChar = (char )sqlite3_column_text(statement 2)

char localidadChar = (char )sqlite3_column_text(statement 3)

int edad = sqlite3_column_int(statement 4)

NSString nombre = [[NSString alloc]initWithUTF8StringnombreChar]NSString apellidos = [[NSString alloc]initWithUTF8StringapellidosChar]NSString localidad = [[NSString alloc]initWithUTF8StringlocalidadChar]Persona persona = [[Persona alloc]initWithUiduniqueId nombrenombreapellidosapellidos edadedad

localidadlocalidad]

[arraySalida addObjectpersona][nombre release][apellidos release][localidad release][persona release]

sqlite3_finalize(statement)

return arraySalida

end

Con esto ya podemos leer de nuestra base de datos las personas Para ver que funcionatodo a la perfeccioacuten escribimos el siguiente coacutedigo dentro de la clase delegada en elmeacutetodo applicationDidFinishLaunching Antes debemos incluir en la parte superiorlas clases creadas

import BDPersistenciahimport Personah

NSArray personas = [BDPersistenciadatabase]arrayPersonas

for (Persona persona in personas) NSLog(d d personauId

personanombrepersonaapellidos personalocalidad personaedad)

Persistencia

49Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Si ejecutamos el proyecto obtendremos la siguiente salida por consola

Listado de personas

533 Mostrando los datos en una tabla

Una vez que tenemos el coacutedigo para obtener todas las filas de la tabla de personas de labase de datos queda lo maacutes faacutecil mostrarlas dentro de una vista de la aplicacioacuten En estecaso utilizaremos la vista de tabla UITableView

Comenzamos creando una nueva vista en nuestro proyecto para ello hacemos clickderecho sobre el raiz y seleccionamos New File gt UIVIewController SubclassAsegurate de marcar Subclass of UITableViewController y la opcoacuten de With

XIB for user interface Hacemos click en Next y guardamos el fichero comoPersonasViewController por ejemplo

Ahora abrimos el fichero PersonasViewControllerh y antildeadimos un NSArray queseraacute el que almacene las personas de la base de datos

imports iniciales

interface PersonasViewController UITableViewController

NSArray _listadoPersonas

property (nonatomic retain) NSArray listadoPersonas

end

Ahora abrimos el fichero PersonasViewControllerm antildeadimos el syntetizeimportamos los ficheros de gestioacuten de la base de datos la clase Persona y todo el coacutedigobaacutesico para la gestioacuten de la vista de la tabla

import PersonasViewControllerhimport Personahimport BDPersistenciah

implementation PersonasViewController

synthesize listadoPersonas = _listadoPersonas

Persistencia

50Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

- (id)initWithStyle(UITableViewStyle)style

self = [super initWithStylestyle]if (self)

Custom initializationreturn self

- (void)dealloc

[super dealloc]

selflistadoPersonas = nil

- (void)didReceiveMemoryWarning

Releases the view if it doesnt have a superview[super didReceiveMemoryWarning]

Release any cached data images etc that arent inuse

pragma mark - View lifecycle

- (void)viewDidLoad

[super viewDidLoad]

selftitle = Listado de personas

selflistadoPersonas = [BDPersistenciadatabase]arrayPersonas

- (void)viewDidUnload

[super viewDidUnload] Release any retained subviews of the main view eg selfmyOutlet = nil

- (void)viewWillAppear(BOOL)animated

[super viewWillAppearanimated]

- (void)viewDidAppear(BOOL)animated

[super viewDidAppearanimated]

- (void)viewWillDisappear(BOOL)animated

[super viewWillDisappearanimated]

- (void)viewDidDisappear(BOOL)animated

[super viewDidDisappearanimated]

- (BOOL)shouldAutorotateToInterfaceOrientation

Persistencia

51Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

(UIInterfaceOrientation)interfaceOrientation

Return YES for supported orientationsreturn (interfaceOrientation ==

UIInterfaceOrientationPortrait)

pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView(UITableView)tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection

(NSInteger)section

Return the number of rows in the sectionreturn [selflistadoPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath

(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]if (cell == nil)

cell = [[[UITableViewCell alloc]initWithStyleUITableViewCellStyleSubtitle

reuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona persona = (Persona )[selflistadoPersonas

objectAtIndexindexPathrow]celltextLabeltext = [NSString stringWithFormat

(d) personanombrepersonaapellidos personaedad]celldetailTextLabeltext = personalocalidad

return cell

pragma mark - Table view delegate

- (void)tableView(UITableView )tableViewdidSelectRowAtIndexPath

(NSIndexPath )indexPath

Navigation logic may go here Create and pushanother view controller

end

Persistencia

52Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora soacutelo nos queda incorporar la vista que acabamos de crear dentro de nuestraaplicacioacuten Para ello vamos a llamarla desde el Delegate y a meterla dentro de unNavigation Controller Primero debemos de antildeadir un Outlet en la clasedelegada hacia un Navigation Controller

imports iniciales

interface sesion03_ejemplo1AppDelegate NSObjectltUIApplicationDelegategt

UIWindow _windowUINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet

UINavigationController navController

end

Ahora hacemos click sobre la vista MainWindowxib y arrastramos un Navigation

Controller desde el listado de la columna de la derecha hacia la columna de laizquierda donde pone Objects Nos apareceraacute el Navigation Controller dibujadoen pantalla Ahora desplegamos el Navigation Controller y hacemos click sobre elView Controller que hay dentro Nos vamos a la pestantildea de Identity Inspector

de la columna de la derecha y escribimos dentro de Class el nombre de la vista dellistado de personas PersonasViewController

Finalmente hacemos click sobre el objeto Delegate vamos a la pestantildea de Show

Connections Inspector y arrastramos desde navController hasta el objetoNavigation Controller de la columna de la izquierda Ya tenemos los controladoresy las vistas conectadas

Una vez hecho esto nos queda indicar dentro de nuestro coacutedigo que la aplicacioacuten arranquecon el Navigation Controller para ello escribimos lo siguiente al final del meacutetododidFinishLaunchingWithOptions de la implementacioacuten de la clase delegada

selfwindowrootViewController = selfnavController[selfwindow makeKeyAndVisible]return YES

Ya estaacute listo todo y preparado para compilar Si ejecutamos el proyecto veremos que nosaparece el listado de personas en la vista de tabla

Persistencia

53Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios

61 Creando y leyendo un fichero plist (15 puntos)

En este primer ejercicio vamos a crear nuestra primera aplicacioacuten usando el meacutetodo depersistencia de ficheros La aplicacioacuten mostraraacute en una vista de tabla TableView unlistado de series en la que si seleccionamos una veremos su informacioacuten maacutes importanteen otra vista Comenzaremos creando un fichero de propiedades (plist) siguiendo unaestructura determinada y posteriormente mostraremos en una vista de tabla todos susdatos iexclComenzamos

1) Crear un proyecto usando la plantilla Master-Detail application Lo llamaremosejercicio-plist-ios como identificador de empresa escribiremos esuajtech y como prefijode clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremos UseAutomatic Reference Counting para olvidarnos de la gestioacuten de memoria El resto deopciones las dejaremos sin marcar

2) Crear un archivo de propiedades (plist) con el nombre de series

3) Editar el archivo plist creando la siguiente estructura de datos (5 series miacutenimo)

Plist series

4) Rellenar el archivo plist con datos de series reales

5) Escribir el coacutedigo necesario en UAMasterViewControllerm que recorra el archivoplist que acabamos de crear y muestre por consola los titulos de las series

6) Mostrar en la tabla que hay implementada los tiacutetulos de la serie Para ello deberemosde completar todos los meacutetodos del protocolo del TableView

7) Ejecutar la aplicacioacuten y comprobar que funciona correctamente

Persistencia

54Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

62 Implementar vista de detalle de la serie (05 puntos)

Dentro del proyecto anterior (no hace falta crear uno nuevo) vamos a mostrar toda lainformacioacuten adicional de cada una de las series en una vista de detalle Cuandoseleccionemos una fila de la tabla nos deberaacute aparecer una nueva vista con el resto deinformacioacuten (antildeo director principal sinopsis y duracioacuten)

63 Uso baacutesico de una base de datos SQLite (1 punto)

En este ejercicio vamos a crear la misma aplicacioacuten que en el ejercicio anterior perousando el meacutetodo de persistencia de SQLite Para completar el ejercicio deberemos seguirlos siguientes pasos

1) Crear un proyecto usando la plantilla Master-Detail application lo llamaremosejercicio-sqlite-ios como identificador de empresa escribiremos esuajtech y comoprefijo de clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremosUse Automatic Reference Counting para olvidarnos de la gestioacuten de memoria El restode opciones las dejaremos sin marcar

2) Nos descargamos e instalamos el programa SQLite Database Browser desde estadireccioacuten

3) Disentildear la estructura de la base de datos usando SQLite Database Browser tal y comose muestra en la imagen siguiente

Sqlite series

4) Insertar al menos 5 series en la base de datos usando el programa SQLite DatabaseBrowser

5) Cargar todos los datos de la base de datos desde el meacutetododidFinishLaunchingWithOptions de la clase UAAppDelegate Mostrar por consola losdatos cargados

6) Mostrar los datos cargados en el paso anterior en la vista de tabla UITableView quehay creada en la clase UAMasterViewController

Persistencia

55Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7) Arrancar la aplicacioacuten y comprobar que se muestran las series en la tabla de formacorrecta

Persistencia

56Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7 Persistencia de datos en iOS User Defaults y Core Data

71 Introduccioacuten

En esta sesioacuten veremos como utilizar dos nuevos meacutetodos de persistencia en iOS ambosmuy interesantes y usados muy frecuentemente

bull User Defaults Muy uacutetil para almacenar datos simples y de uso repetido dentro denuestras aplicaciones iOS

bull Core Data Meacutetodo avanzado de almacenamiento de objetos enteros dentro de lamemoria del dispositivo iOS Muy uacutetil para almacenar datos complejos conrelaciones etc

72 User Defaults

Este meacutetodo es muy uacutetil cuando queremos almacenar pequentildeas cantidades de datos comopuntuaciones informacioacuten de usuario el estado de nuestra aplicacioacuten configuracionesetc

Este meacutetodo es el maacutes raacutepido de usar ya que no requiere de una base de datos ni ficherosni ninguna otra capa intermedia Los datos se almacenan directamente en la memoria deldispositivo dentro de un objeto de la clase NSUserDefaults

Para aprender a usar este meacutetodo de almacenamiento de datos vamos a crear unaaplicacioacuten muy simple que lea un nombre y una edad de pantalla y las guarde en unobjeto NSUserDefaults Para ello seguimos los siguientes pasos

bull 1) Comenzamos creando un nuevo proyecto llamado sesion04-ejemplo1 dentrode XCode

bull 2) Abrimos la vista sesion04_ejemplo1ViewController y arrastramos dos TextField un botoacuten y dos labels quedando la vista de la siguiente manera

Persistencia

57Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista de la aplicacioacuten

bull 3) Abrimos el fichero sesion04_ejemplo1ViewControllerh y definimos el Outletdel evento onclick del botoacuten El fichero quedaraacute de la siguiente manera

imports iniciales

interface sesion04_ejemplo1ViewController UIViewController UITextField tfNombreUITextField tfEdad

property(nonatomic retain) IBOutlet UITextField tfNombreproperty(nonatomic retain) IBOutlet UITextField tfEdad

-(IBAction) guardaDatos

end

bull 4) Ahora implementamos el coacutedigo para el meacutetodo del botoacuten dentro desesion04_ejemplo1ViewControllerm

synthesize tfEdadsynthesize tfNombre

-(IBAction) guardaDatos NSLog(Guardamos los datos)

Persistencia

58Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creamos el objeto NSUserDefaultsNSUserDefaults datos = [NSUserDefaults standardUserDefaults]

guardamos el nombre (NSString)[datos setObjecttfNombretext forKeynombre]

guardamos la edad (Integer)[datos setInteger[tfEdadtext integerValue] forKeyedad]

almacenamos los datos en la memoria[datos synchronize]

bull 5) Tenemos que enlazar el botoacuten con el evento que hemos programado en el pasoanterior para ello abrimos la vista sesion04_ejemplo1ViewControllerxib y conla tecla ctlr apretada arrastramos desde el botoacuten hasta Files Owner (columna de laizquierda parte superior) y seleccionamos el meacutetodo que hemos creado(guardaDatos) De esta forma ya tenemos conectado el botoacuten

bull 6) Ahora hacemos lo mismo con los Text Field Tenemos que conectarlos alcontrolador para ello seleccionamos el objeto Files Owner y abrimos la pestantildeade conexiones (la que estaacute maacutes a la derecha en la columna de la derecha) Hacemosclick en tfEdad y arrastramos hasta el Text Field de edad Hacemos lo mismo para eltext field de nombre Ya tenemos todos los elementos de la vista conectados

Conexioacuten de Outlets

bull 7) Arrancamos la aplicacioacuten y la probamos

Persistencia

59Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Aplicacioacuten

bull 8) Ahora vamos a hacer que la aplicacioacuten cargue los datos que se han guardado alarrancar la vista para ello simplemente tenemos que escribir el siguiente coacutedigo en elmeacutetodo (void)viewDidLoad de la clase sesion04_ejemplo1ViewControllerm

- (void)viewDidLoad

[super viewDidLoad]

NSUserDefaults datos = [NSUserDefaults standardUserDefaults]

obtenemos un NSStringNSString nombre = [datos stringForKeynombre]

obtenemos un NSIntegerNSInteger edad = [datos integerForKeyedad]

if (nombre=nil ampamp edad=0)NSLog(Nombre cargado edad dnombreedad)

else

NSLog(Sin datos)

bull 9) Ya podemos arrancar de nuevo la aplicacioacuten y veremos que si escribimos cualquier

Persistencia

60Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

texto en los Text Fields estos se almacenaraacuten dentro del objeto NSUserDefaults Alapagar la aplicacioacuten y volver a arrancarla estos datos se cargaraacuten (apareceraacuten enconsola)

Consola

NotaLos datos permaneceraacuten en nuestro dispositivo hasta que se elimine la aplicacioacuten en ese casotodos los datos guardados en NSUserDefaults se borraraacuten

bull 10) Probar eliminar la aplicacioacuten del dispositivosimulador y volver arrancarComprobamos que los datos se han borrado

Consola

73 Core Data

De todas las formas de persistencia de datos que existen en iOS Core Data es la maacutesapropiada para usar en caso de manejar datos no triviales con relaciones algo maacutescomplejas entre ellos El uso de Core Data dentro de nuestra aplicacioacuten optimiza almaacuteximo el consumo de memoria mejora el tiempo de respuesta y reduce la cantidad decoacutedigo redundante a escribir

Core Data utiliza de fondo el meacutetodo de almacenamiento SQLite visto en la sesioacuten 3 deeste moacutedulo por tanto para explicar el funcionamiento de este meacutetodo de persistenciavamos a utilizar el mismo modelo de datos que usamos en esa sesioacuten Persona -gtDetallePersona

El el siguiente ejemplo haremos la misma aplicacioacuten que hicimos con SQLite perousando este nuevo meacutetodo de persistencia

731 Creando el proyecto

Para empezar tenemos que crearnos un nuevo proyecto en XCode Para ello abrimosXCode y seleccionamos crear una nueva aplicacioacuten basada en ventanas (Window-basedapplication) Hacemos click en Next escribimos como nombre del proyectosesion04-ejemplo2 y seleccionamos Use Core Data

Persistencia

61Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creando un proyecto nuevo

Con esto ya tenemos la estructura baacutesica para empezar a programar Antes de nadahechamos un vistazo raacutepido a toda la estructura de directorios creada y vemos que dentrodel directorio principal tenemos un archivo que se llamasesion04_ejemplo2xcdatamodeld Al hacer click sobre el archivo se nos abre uneditor especial que en principio estaacute vacio y que maacutes adelante utilizaremos para disentildearnuestro modelo de datos

Ahora hechamos un vistazo a la clase delegada sesion04_ejemplo2AppDelegatemvemos que hay unos cuantos meacutetodos nuevos que serviraacuten para configurar el Core DataVamos a explicar los maacutes importantes

bull - (NSManagedObjectModel )managedObjectModel NSManagedObjectModel esla clase que contiene la definicioacuten de cada uno de los objetos o entidades quealmacenamos en la base de datos Normalmente este meacutetodo no lo utilizaremos ya quenosotros vamos a utilizar el editor que hemos visto antes con el que podremos crearnuevas entidades crear sus atributos y relaciones

bull - (NSPersistentStoreCoordinator )persistentStoreCoordinator Aquiacute esdonde configuramos los nombres y las ubicaciones de las bases de datos que se usaraacutenpara almacenar los objetos Cuando un managed object necesite guardar algo pasaraacutepor este meacutetodo

bull - (NSManagedObjectContext )managedObjectContext Esta claseNSManagedObjectContext seraacute la maacutes usada con diferencia de las tres y por tanto la

Persistencia

62Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

maacutes importante La utilizaremos baacutesicamente para obtener objetos insertarlos oborrarlos

732 Definiendo el modelo de datos

En el ejemplo que hicimos en la sesioacuten anterior de SQLite creamos dos tablas una paralos datos baacutesicos de la persona y otra para el detalle Ahora haremos lo mismo perousando Core Data la diferencia principal es que mientras que con SQLite obteniamos soacutelolos datos que necesitaacutebamos de la base de datos aquiacute obtenemos el objeto completo

Para definir nuestro modelo de datos basado en dos objetos (Persona y DetallePersona)abrimos el editor visual haciendo click sobre el ficherosesion04_ejemplo2xcdatamodeld Empezaremos creando una nueva entidad para ellohacemos click sobre el botoacuten Add Entity (+) que se encuentra en la parte inferior dela pantalla A la nueva entidad le pondremos como nombre Persona

Dentro de la seccioacuten de Attributes hacemos click sobre (+) y creamos un nuevoatributo para nuestra entidad le llamamos nombre y le asignamos como tipo StringRealizamos este mismo paso 2 veces maacutes para antildeadir apellidos y edad este uacutetlimode tipo Integer 16

Atributos Persona

Ahora creamos la entidad de DetallePersona de la misma manera que la anterior perocreando los atributos localidad pais direccion cp y telefono Todasde tipo String

Atributos DetallePersona

Finalmente nos queda relacionar de alguacuten modo las dos entidades para ello antildeadimos una

Persistencia

63Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

relacioacuten dentro de la entidad de Persona hacia DetallePersona simplementehaciendo click sobre el botoacuten (+) de la seccioacuten de Relationships A la relacioacuten leponemos como nombre detalle seleccionamos como destino la entidadDetallePersona y seraacute No inverse Este tipo de relacioacuten es uno a uno Por dentroCore Data realizaraacute crearaacute una clave ajena en la tabla de persona que apunte aDetallePersona pero eso a nosotros no nos interesa

Vista general modelo de datos (1)

Apple recomienda que siempre que hagamos una relacioacuten de una entidad a otra hagamostambieacuten la relacioacuten inversa por lo tanto hacemos justo lo anterior pero al reveacutes (deDetallePersona a Persona) con la diferencia que en la columna de Inverse

seleccionamos datosPersona

Ya tenemos las entidades y las relaciones creadas Si hacemos click en el botoacuten deEditor Style en la parte de abajo a la derecha del editor podemos ver de una formamaacutes clara la estructura que hemos disentildeado

Persistencia

64Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista general modelo de datos (2)

733 Probando el modelo

Una vez que tenemos el modelo creado pasamos a probarlo para comprobar que estaacute todocorrecto para ello abrimos la clase delegada (sesion04_ejemplo2AppDelegatem) yantildeadimos el siguiente fragmento de coacutedigo arriba del meacutetodoapplicationDidFinishLaunching

NSManagedObjectContext context = [self managedObjectContext]NSManagedObject persona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

[persona setValueJuan forKeynombre][persona setValueMartinez forKeyapellidos][persona setValue[NSNumber numberWithInt28] forKeyedad]

NSManagedObject detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersonainManagedObjectContextcontext]

[detallePersona setValueCalle Falsa 123 forKeydireccion][detallePersona setValueAlicante forKeylocalidad][detallePersona setValueEspantildea forKeypais][detallePersona setValue965656565 forKeytelefono]

[detallePersona setValuepersona forKeydatosPersona][persona setValuedetallePersona forKeydetalle]

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Persistencia

65Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Con este coacutedigo hemos creado un objeto Persona enlazado con otro objetoDetallePersona iexclComo puedes ver no hemos necesitado nada de coacutedigo SQLPrimero creamos un objeto del tipo NSManagedObjectContext que almacena el conextode Core Data Despueacutes creamos el objeto Persona en siacute con NSManagedObject A esteobjeto le pondremos los valores para cada una de las propiedades definidas en el modeloHacemos lo mismo con DetallePersona Finalmente relacionamos el objetoPersona con el objeto DetallePersona

Compilamos el proyecto comprobamos que todo funciona bien y no salen errores

Para comprobar que los datos se han almacenado correctamente en nuestro dispositivo losmostramos en la consola de la siguiente manera

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

intValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(------------------)

[fetchRequest release]

El coacutedigo de arriba lo escribiremos dentro del meacutetododidFinishLaunchingWithOptions de la clase delegada Lo que hace este coacutedigo esrecorrer todos los objetos almacenados en memoria y mostrarlos por consola Siarrancamos la aplicacioacuten veremos que se muestran todos los datos de los objetosalmacenados Cada vez que arranquemos la aplicacioacuten se mostraraacuten maacutes objetos ya quetambieacuten los creamos

Para listar los objetos usamos la clase NSFetchRequest Esta clase es equivalente aejecutar una sentencia Select de SQL Al objeto NSFetchRequest le asignamos laentidad que queremos listar (SELECT FROM ) en nuestro caso seraacute PersonaEl listado de los objetos se obtiene mediante el meacutetodo executeFetchRequest de laclase NSManagedObjectContext que hemos definido en el bloque anterior de coacutedigoEste meacutetodo devolveraacute un NSArray con todos los objetos NSManagedObject

Persistencia

66Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Salida de consola

NotaSi queremos ver queacute sentencias SQL estaacute ejecutando Core Data por debajo tenemos que activarel flag de debug para ello seleccionamos la opcioacuten Product gt Edit Scheme Enla columna de la izquierda seleccionamos el esquema que estamos utilizando para Debug y en laparte de la derecha en el cuadro de Arguments Passed on Launch antildeadimos 2paraacutemetros -comappleCoreDataSQLDebug y 1

Ahora arrancamos de nuevo la aplicacioacuten y veremos que en la consola apareceraacuten lassentencias SQL utilizadas por Core Data

()

INSERT INTO ZDETALLEPERSONA(Z_PK Z_ENT Z_OPT ZDATOSPERSONA ZTELEFONOZLOCALIDAD ZDIRECCION ZPAIS)VALUES( )CoreData sql SELECT 0 t0Z_PK t0Z_OPT t0ZTELEFONO t0ZLOCALIDADt0ZDIRECCION t0ZPAIS t0ZDATOSPERSONAFROM ZDETALLEPERSONA t0 WHERE t0Z_PK =

()

734 Generando los modelos automaacuteticamente

Generar los modelos a mano directamente desde el editor no es lo maacutes recomendado yaque podemos cometer errores de escritura errores de tipo etc La mejor manera dehacerlo es creando un archivo para cada entidad Esto se puede hacer desde 0 peroXCode tiene un generador de clases que es muy faacutecil de usar y ahorremos bastantetiempo iexclVamos a usarlo

Hacemos click sobre el raiz de nuestro proyecto y seleccionamos New File En lacolumna de la izquierda seleccionamos iOS gt Core Data y en el cuadro de laderecha NSManagedObject subclass Click en next En la siguiente pantallaseleccionamos nuestro modelo de datos sesion04_ejemplo2 y click en next Ahoranos aseguramos de seleccionar las dos entidades que hemos creado anteriormentePersona y DetallePersona click en next Seleccionamos el raiz de nuestroproyecto para guardar los ficheros que se generen y aceptamos

Persistencia

67Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ventana de seleccioacuten de entidad

Ahora veremos que xsource ha creado automaacuteticamente 4 ficheros nuevos dentro denuestro proyecto Personamh y DetallePersonamh con todos los atributosespecificados Estas nuevas clases que heredan de NSManagedObject seraacuten las queutilicemos a partir de ahora

Cambiamos el coacutedigo de la clase delegada por el siguiente

NSManagedObjectContext context = [self managedObjectContext]Persona datosPersona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

datosPersonanombre = JuandatosPersonaapellidos = MartinezdatosPersonaedad = [NSNumber numberWithInt28]

DetallePersona detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersona

inManagedObjectContextcontext]detallePersonadireccion = Calle Falsa 123

Persistencia

68Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

detallePersonalocalidad = AlicantedetallePersonapais = EspantildeadetallePersonatelefono = 965656565

detallePersonadatosPersona = datosPersonadatosPersonadetalle = detallePersona

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Listamos los datosNSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

integerValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(-----------------)[fetchRequest release]

Ejecutamos el proyecto y comprobamos que en la consola aparece lo mismo que antespero ahora tenemos el coacutedigo bastante maacutes claro

735 Creando la vista de tabla

Ahora vamos a mostrar los objetos que antes hemos listado por consola en una vista detabla UITableView Para ello creamos un UITableViewController

Click en New file gt UIViewController subclass En la siguiente pantallanos aseguramos de seleccionar Subclass of UITableViewController de esta formanos apareceraacuten ya todos los meacutetodos de la tabla creados por defecto Guardamos elarchivo con el nombre que queramos por ejemplo PersonasViewController

Abrimos ahora PersonasViewControllerh y antildeadimos 2 atributos a la clase

bull Un NSArray que se seraacute el listado de personas (objetos de la clase Persona)bull Una referencia a NSManagedObjectContext

El fichero PersonasViewControllerh quedaraacute de la siguiente manera

Persistencia

69Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

imports iniciales

interface PersonasViewController UITableViewController NSArray _listaPersonasNSManagedObjectContext _context

property (nonatomic retain) NSArray listaPersonasproperty (nonatomic retain) NSManagedObjectContext context

end

Ahora en el m importamos en el encabezado las clases Personah yDetallePersonah y en el meacutetodo viewDidLoad creamos el array listaPersonas contodos los objetos Persona de forma similar a cuando lo hicimos en el apartado anterior

import PersonasViewControllerh

import Personahimport DetallePersonah

implementation PersonasViewController

synthesize listaPersonas = _listaPersonassynthesize context = _context

En viewDidLoad

- (void)viewDidLoad

[super viewDidLoad]

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContext_context]

[fetchRequest setEntityentity]NSError errorselflistaPersonas = [_context executeFetchRequestfetchRequest

erroramperror]selftitle = Listado Personas[fetchRequest release]

Ahora completamos el resto de meacutetodos heredados de UITableViewController de lamisma forma que hicimos en sesiones anteriores

- (NSInteger)numberOfSectionsInTableView(UITableView )tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection(NSInteger)section

Persistencia

70Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Return the number of rows in the sectionreturn [_listaPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]

if (cell == nil) cell = [[[UITableViewCell alloc]

initWithStyleUITableViewCellStyleSubtitlereuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona p = (Persona )[_listaPersonas objectAtIndexindexPathrow]celltextLabeltext = pnombrecelldetailTextLabeltext = [NSString stringWithFormatd antildeos

[pedad intValue]]

return cell

Ahora para poder mostrar la tabla dentro de nuestra aplicacioacuten debemos asignarla dentrode la clase delegada para ello abrimos la vista MainWindowxib y arrastramos unacontroladora de navegacioacuten Navigation Controller a la lista de objetos de laizquierda La desplegamos y dentro de View Controller en la pestantildea de la derechade Identity Inspector escribimos el nombre de nuestra clase que tiene la vista de latabla PersonasViewController

Pantalla del disentildeador

Ahora abrimos el fichero sesion04_ejemplo2AppDelegateh y creamos un

Persistencia

71Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

UINavigationController dentro de la declaracioacuten de variables dejando el ficherocomo sigue

imports iniciales

interface sesion04_ejemplo2AppDelegate NSObject ltUIApplicationDelegategt

UINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet UINavigationControllernavController

property (nonatomic retain readonly) NSManagedObjectContextmanagedObjectContextproperty (nonatomic retain readonly) NSManagedObjectModelmanagedObjectModelproperty (nonatomic retain readonly) NSPersistentStoreCoordinatorpersistentStoreCoordinator

- (void)saveContext- (NSURL )applicationDocumentsDirectory

end

Dentro del m importamos la clase PersonasViewControllerh y completamos elsynthetize

synthesize navController = _navController

Y por uacuteltimo justo antes de la llamada al meacutetodo makeKeyAndVisible escribimos elsiguiente coacutedigo para asignar el contexto de la clase delegada alPersonasViewController

PersonasViewController root = (PersonasViewController ) [_navControllertopViewController]

rootcontext = [self managedObjectContext][selfwindow addSubview_navControllerview]

Ahora compilamos y ejecutamos nuestro proyecto y si todo ha ido bien deberiacutea de salirla tabla con los datos de las personas

Persistencia

72Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Listado de personas

736 Migraciones de bases de datos con Core Data

Una vez que tenemos disentildeada nuestra base de datos inicial iquestqueacute pasariacutea si queremosmodificarla para antildeadirle un campo maacutes a una tabla iquestQueacute haraacute Core Data para realizar lamigracioacuten de la versioacuten anterior a la actual

Siguiendo con el ejemplo anterior si despueacutes de ejecutar la aplicacioacuten en nuestrodispositivo o simulador modificamos la estructura de la base de datos antildeadiendo unatributo nuevo en la entidad DetallePersona por ejemplo codigoPostal con el tipoInteger 16 veremos que al volver a arrancar la aplicacioacuten nos apareceraacute un error entiempo de ejecucioacuten similar al siguiente

Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 Theoperationcouldnrsquot be completed(Cocoa error 134100) UserInfo=0x6b6c680 metadata=ltCFBasicHash 0x6b68380[0x1337b38]gttype = immutable dict count = 7entries =gt

2 ltCFString 0x6b70320 [0x1337b38]gtcontents =NSStoreModelVersionIdentifiers

=ltCFArray 0x6b706e0 [0x1337b38]gttype = immutable count = 1

values = (0 ltCFString 0x1332cd8 [0x1337b38]gtcontents =

)4 ltCFString 0x6b70350 [0x1337b38]gtcontents =

NSPersistenceFrameworkVersion=

Persistencia

73Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltCFNumber 0x6b6f2c0 [0x1337b38]gtvalue = +386 type =kCFNumberSInt64Type

6 ltCFString 0x6b70380 [0x1337b38]gtcontents =NSStoreModelVersionHashes =

ltCFBasicHash 0x6b707e0 [0x1337b38]gttype = immutable dict count =2entries =gt

1 ltCFString 0x6b70700 [0x1337b38]gtcontents = Persona =ltCFData 0x6b70740

[0x1337b38]gtlength = 32 capacity = 32 bytes =0x1c50e5a379ff0ddc3cda7b82a3934c5e 75649ac3c919beea2 ltCFString 0x6b70720 [0x1337b38]gtcontents = DetallePersona

=ltCFData 0x6b70790[0x1337b38]gtlength = 32 capacity = 32 bytes =0xb7ba2eb498f9a1acdd6630d56f6cd12c e8963dd3e488ba95

7 ltCFString 0x10e3aa8 [0x1337b38]gtcontents = NSStoreUUID =ltCFString 0x6b70510[0x1337b38]gtcontents = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E8 ltCFString 0x10e3948 [0x1337b38]gtcontents = NSStoreType =ltCFString 0x10e3958[0x1337b38]gtcontents = SQLite9 ltCFString 0x6b703b0 [0x1337b38]gtcontents =

_NSAutoVacuumLevel = ltCFString0x6b70830 [0x1337b38]gtcontents = 210 ltFString 0x6b703d0 [0x1337b38]gtcontents =

NSStoreModelVersionHashesVersion=ltCFNumber 0x6b61950 [0x1337b38]gtvalue = +3 type =

kCFNumberSInt32Type reason=The model used to open the store is incompatible with the oneused tocreate the store

metadata = NSPersistenceFrameworkVersion = 386NSStoreModelVersionHashes =

DetallePersona = ltb7ba2eb4 98f9a1ac dd6630d5 6f6cd12c f890007746b16f00

e8963dd3 e488ba95gtPersona = lt1c50e5a3 79ff0ddc 3cda7b82 a3934c5e 5cb4ee4b

4ac98838 75649ac3c919beeagt

NSStoreModelVersionHashesVersion = 3NSStoreModelVersionIdentifiers = (

)NSStoreType = SQLiteNSStoreUUID = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E_NSAutoVacuumLevel = 2

reason = The model used to open the store is incompatible with the

one usedto create the store

Este error es muy comuacuten cuando estamos desarrollando aplicaciones con Core DataBaacutesicamente nos dice que el modelo de base de datos que estaacute intentando cargar dememoria es distinto al que se indica en la aplicacioacuten esto es porque no hemos indicadoninguacuten meacutetodo de migracioacuten Realmente existen dos formas de solucionar este error

Persistencia

74Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Por un lado y soacutelo cuando estemos desarrollando y no tengamos ninguna versioacutende nuestra aplicacioacuten lanzada en la App Store podremos eliminar la base de datosdel dispositivo simulador simplemente borrando la aplicacioacuten de el Tambieacutenpodemos ejecutar el siguiente coacutedigo una soacutela vez [[NSFileManagerdefaultManager] removeItemAtURLstoreURL errornil]

bull En el caso de que estemos realizando una migracioacuten sobre una versioacuten ya publicadaen la App Store el meacutetodo anterior no nos serviriacutea ya que obligariacuteamos al usuario aborrar su aplicacioacuten del dispositivo con lo que todos los datos que tuviera seperderiacutean Esto no es viable por lo que necesitaremos usar otro meacutetodo para realizarla migracioacuten y solucionar el error de una forma maacutes limpia este meacutetodo es el quevamos a comentar a continuacioacuten

XCode soporta Versioning para Core Data esto es que podemos crear versiones de labase de datos de una forma sencilla si seguimos una serie de pasos

1) Antildeadimos las siguientes opciones en forma de NSDictionary a nuestro coacutedigo delpersistentStoreCoordinator

NSDictionary options = [NSDictionary dictionaryWithObjectsAndKeys[NSNumber numberWithBoolYES]NSMigratePersistentStoresAutomaticallyOption[NSNumber numberWithBoolYES]NSInferMappingModelAutomaticallyOptionnil]

Y modificamos el meacutetodo addPersistentStoreWithType pasaacutendole como paraacutemetro eldiccionario options

if ([__persistentStoreCoordinatoraddPersistentStoreWithTypeNSSQLiteStoreTypeconfigurationnil URLstoreURL optionsoptions erroramperror])

Con el coacutedigo anterior indicamos al persistentStoreCoordinator que la migracioacuten enel caso de que se deba de realizar sea de forma automaacutetica

AtencioacutenDeberemos de tener en cuenta que este tipo de migracioacuten que estamos estudiando en este puntosoacutelo es vaacutelida para cambios simples en Core Data lo que Apple llama como lightweightmigration Este tipo de migracioacuten admite una serie de cambios que se indican dentro de ladocumentacioacuten de Apple para cambios maacutes complejos deberemos realizar migracionespersonalizadas y mucho maacutes complejas El documento en donde Apple explica el tipo demigraciones que existen para Core Data y su forma de implementarlas lo podemos consultardesde esta web

2) Ahora deberemos de crear la versioacuten del esquema de Core Data dentro de XCode para

Persistencia

75Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ello seleccionamos el fichero del esquema sesion04_ejemplo2xcdatamodeld yseleccionamos en el menuacute principal Editor gt Add Model Version A la nueva versioacuten lallamaremos sesion04_ejemplo_2 (versioacuten 2) Una vez que hayamos pulsado sobreFinish se nos habraacute creado la nueva versioacuten del esquema dentro de la principal y estaraacutemarcada con un tick la versioacuten anterior

3) Una vez que tenemos creada la nueva versioacuten deberemos modificarla antildeadiendole elnuevo atributo Seleccionamos la versioacuten recieacuten creadasesion04_ejemplo_2xcdatamodel y antildeadimos a la entidad DetallePersona elatributo codigoPostal Atencioacuten Si este atributo lo hemos antildeadido anteriormentedeberemos de borrarlo de la versioacuten anterior y antildeadirlo en la segunda versioacuten

4) Ahora nos queda indicar a Core Data que queremos usar la versioacuten 2 de la base dedatos para nuestra aplicacioacuten Para ello seleccionamos el esquema principal (el raiz)sesion04_ejemplo2xcdatamodeld y en la ventana de la derecha en la pestantildea de FileInspector seleccionamos como Versioacuten actual la versioacuten 2 esto lo hacemos dentro delapartado de Versioned Core Data Model Automaacuteticamente veremos que el siacutembolo detick cambia a la versioacuten 2

5) Ahora ya podemos volver a compilar y veremos que no nos aparece el error y funcionatodo seguacuten lo esperado

Magical RecordExisten varias librerias de coacutedigo libre que facilitan enormemente el uso de Core Data ennuestras aplicaciones iOS Una de ellas es Magical Record Mediante esta libreriacutea podremos usarCore Data sin tener que preocuparnos de todo el proceso de configuracioacuten en las controladorasde la configuracioacuten de migraciones etc Su instalacioacuten en un proyecto de XCode es muy faacutecil yaltamente recomendable ya que proporciona una API muy clara y sencilla de usar

Persistencia

76Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios

81 Usando User Defaults (1 punto)

En este ejercicio vamos a usar las variables de usuario para almacenar y mostrar los datosdel dispositivo que estamos usando y las veces que hemos arrancado la aplicacioacuten

Crear un proyecto nuevo usando la plantilla Single View Application con el nombreejercicio-userdefaults-ios Se debe de ejecutar en iPhone e iPad (Universal) Comoidentificador de empresa escribiremos esuajtech y como prefijo de clase UA Debe detener el ARC activado (desmarcar el resto de opciones)

Ayuda meacutetodos de UIDevicePara obtener los datos del dispositivo usaremos el singleton UIDevice estos son algunos de losmeacutetodos que podemos usar [[UIDevice currentDevice] name] [[UIDevicecurrentDevice] systemName] [[UIDevice currentDevice]systemVersion] [[UIDevice currentDevice] model] [[UIDevicecurrentDevice] localizedModel]

82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)

En este ejercicio disentildearemos un modelo de datos de dos tablas relacionadas entre ellasantildeadiremos datos por coacutedigo y los mostraremos en una vista de tabla La base de datosque vamos a implementar constaraacute de dos tablas por un lado una que contendraacute los tiacutetulosde series y otra que tendraacute los detalles A la primera la llamaremos Serie y la segundaDetalleSerie Para realizar correctamente el ejercicio deberemos seguir los siguientespasos

1) Crear un proyecto nuevo usando la plantilla Master-Detail Application con elnombre ejercicio-coredata-ios Se debe de ejecutar en iPhone como identificador deempresa escribiremos esuajtech y como prefijo de clase UA Debe de tener el ARCactivado y la opcioacuten de Use Core Data (desmarcar el resto de opciones)

2) Disentildear el siguiente modelo de datos

Persistencia

77Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esquema modelo de datos

3) Revisar el fichero UAAppDelegatem y comprobar que tenemos todos los meacutetodosnecesarios para que funcione Core Data en nuestra aplicacioacuten

4) Modificar el fichero UAMasterViewControllerm para que se muestren los tiacutetulos delas series en la tabla

5) Probar la aplicacioacuten antildeadiendo unos tiacutetulos de prueba

83 Migrando datos con Core Data (05 puntos)

Una vez que hemos disentildeado la Base de Datos con Core Data vamos a modificarlaantildeadiendo un atributo maacutes a la tabla de DetalleSerie valoracion

a) iquestQueacute pasa si cambiamos el modelo de datos y arrancamos la aplicacioacuten iquestQueacute errornos da y por queacute

b) iquestCoacutemo podemos evitar este tipo de errores cuando estemos desarrollando iquestY siestamos en produccioacuten con la aplicacioacuten ya a la venta

c) Crea una nueva versioacuten del modelo

d) Modifica el coacutedigo necesario dentro de la clase UAAppDelegate para evitar este tipo deerrores cuando estemos en un entorno de produccioacuten

e) Vuelve a arrancar la aplicacioacuten y comprueba que funciona bien

Persistencia

78Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Persistencia

79Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

  • 1 Persistencia en Android ficheros y SQLite
    • 11 Introduccioacuten
    • 12 Manejo de ficheros tradicionales en Android
      • 121 Apertura de ficheros
      • 122 Ficheros como recursos
      • 123 Operar con ficheros
      • 124 Almacenar datos en la tarjeta de memoria
        • 13 Base de datos SQLite
          • 131 Content Values
          • 132 Cursores
          • 133 Trabajar con bases de datos SQLite
          • 134 La clase SQLiteOpenHelper
          • 135 Crear una base de datos sin SQLiteHelper
          • 136 Realizar una consulta
          • 137 Extraer resultados de un cursor
          • 138 Antildeadir actualizar y borrar filas
              • 2 Ejercicios - Persistencia en Android ficheros y SQLite
                • 21 Uso de ficheros (05 puntos)
                • 22 Persistencia con ficheros (05 puntos)
                • 23 Base de datos SQLiteOpenHelper (05 puntos)
                • 24 Base de datos insercioacuten y borrado (05 puntos)
                • 25 Base de datos probar nuestro adaptador (05 puntos)
                • 26 Base de datos cambios en la base de datos (05 puntos)
                  • 3 Persistencia en Android proveedores de contenidos y SharedPreferences
                    • 31 Shared Preferences
                      • 311 Guardar Shared Preferences
                      • 312 Leer Shared Preferences
                      • 313 Interfaces para Shared Preferences
                      • 314 Definiendo una pantalla de preferencias con un layout en XML
                      • 315 Controles nativos para preferencias
                      • 316 Actividades de preferencias
                      • 317 Shared Preference Change Listeners
                        • 32 Proveedores de contenidos
                          • 321 Proveedores nativos
                          • 322 Proveedores propios crear un nuevo proveedor de contenidos
                          • 323 Proveedores propios crear la interfaz de consultas
                          • 324 Proveedores propios tipo MIME
                          • 325 Proveedores propios registrar el proveedor
                          • 326 Content Resolvers
                          • 327 Otras operaciones con Content Resolvers
                              • 4 Ejercicios - Persistencia en Android proveedores de contenidos y SharedPreferences
                                • 41 Compartir datos entre actividades con Shared Preferences (075 puntos)
                                • 42 Actividad de preferencias (075 puntos)
                                • 43 Proveedor de contenidos propio (075 puntos)
                                • 44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)
                                  • 5 Persistencia de datos en iOS Ficheros y SQLite
                                    • 51 Introduccioacuten
                                    • 52 Ficheros plist (Property Lists)
                                      • 521 Leyendo datos desde ficheros plist
                                      • 522 Escribiendo datos en ficheros plist
                                        • 53 SQLite
                                          • 531 Herramientas disponibles
                                          • 532 Lectura de datos
                                          • 533 Mostrando los datos en una tabla
                                              • 6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios
                                                • 61 Creando y leyendo un fichero plist (15 puntos)
                                                • 62 Implementar vista de detalle de la serie (05 puntos)
                                                • 63 Uso baacutesico de una base de datos SQLite (1 punto)
                                                  • 7 Persistencia de datos en iOS User Defaults y Core Data
                                                    • 71 Introduccioacuten
                                                    • 72 User Defaults
                                                    • 73 Core Data
                                                      • 731 Creando el proyecto
                                                      • 732 Definiendo el modelo de datos
                                                      • 733 Probando el modelo
                                                      • 734 Generando los modelos automaacuteticamente
                                                      • 735 Creando la vista de tabla
                                                      • 736 Migraciones de bases de datos con Core Data
                                                          • 8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios
                                                            • 81 Usando User Defaults (1 punto)
                                                            • 82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)
                                                            • 83 Migrando datos con Core Data (05 puntos)
Page 7: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de

Los cursores son una herramienta fundamental en el tratamiento de informacioacuten enAndroid y tambieacuten cuando se trabaja con bases de datos Por lo tanto en esta seccioacutenintroducimos este concepto tan importante

Cualquier consulta a una base de datos en Android devolveraacute un objeto de la claseCursor Los cursores no contienen una copia de todos los resultados de la consulta sinoque maacutes bien se trata de punteros al conjunto de resultados Estos objetos proporcionanun mecanismo para controlar nuestra posicioacuten (fila) en el conjunto de resultadosobtenidos tras la consulta

La clase Cursor incluye entre otras las siguientes funciones de navegacioacuten entreresultados

bull moveToFirst desplaza el cursor a la primera fila de los resultados de la consultabull moveToNext desplaza el cursor a la siguiente filabull moveToPrevious desplaza el cursor a la fila anteriorbull getCount devuelve el nuacutemero de filas del conjunto de resultados de la consultabull getColumnName devuelve el nombre de la columna especificada mediante un iacutendice

que se pasa como paraacutemetrobull getColumnNames devuelve un array de cadenas conteniendo el nombre de todas las

columnas en el cursorbull moveToPosition mueve el cursor a la fila especificadabull getPosition devuelve el iacutendice de la posicioacuten apuntada actualmente por el cursor

A lo largo de esta sesioacuten aprenderemos coacutemo realizar consultas a una base de datos ycomo extraer valores o nombres de columnas concretos a partir de los cursores obtenidospor medio de dichas consultas

133 Trabajar con bases de datos SQLite

Suele considerarse una buena praacutectica el crear una clase auxiliar que nos facilite lainteraccioacuten con la base de datos Se trataraacute de una clase de tipo adaptador que crearaacute unacapa de abstraccioacuten que encapsularaacute dichas interacciones De esta forma podremosantildeadir eliminar o actualizar elementos en la base de datos sin necesidad de introducir niuna uacutenica instruccioacuten en SQL en el resto del coacutedigo permitiendo ademaacutes que se hagan lascomprobaciones de tipos correspondientes

Una clase de este tipo deberiacutea incluir meacutetodos para crear abrir o cerrar la base de datosTambieacuten puede ser un lugar conveniente para publicar constantes estaacuteticas relacionadascon la base de datos como por ejemplo constantes que almacenen el nombre de la tabla olas diferentes columnas

A continuacioacuten se muestra el coacutedigo de lo que podriacutea ser un adaptador para una base dedatos estaacutendar Incluye una extensioacuten de la clase SQLiteOpenHelper que seraacute tratada enmaacutes detalle en la siguiente seccioacuten y que se utiliza para simplificar la apertura lacreacioacuten y la actualizacioacuten de la base de datos

Persistencia

7Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

import androidcontentContextimport androiddatabaseimport androiddatabasesqliteimport androiddatabasesqliteSQLiteDatabaseCursorFactoryimport androidutilLog

public class MiAdaptadorBD private static final String NOMBRE_BASE_DATOS =

mibasededatosdbprivate static final String TABLA_BASE_DATOS = mainTableprivate static final int VERSION_BASE_DATOS = 1

Nombre de la columna de clave primariapublic static final String CP_ID=_id El nombre e iacutendice de cada columna en la base de datospublic static final String COLUMNA_NOMBRE=nombrepublic static final int COLUMNA_INDICE = 1 PENDIENTE crear campos adicionales para el resto de columnas de la base de datos

Sentencia SQL para crear una nueva base de datosprivate static final String CREAR_BASE_DATOS = create table +

TABLA_BASE_DATOS + ( + CP_ID + integer primary key autoincrement +COLUMNA_NOMBRE + text not null)

Variable para almacenar la instancia de la base de datosprivate SQLiteDatabase db Contexto de la aplicacioacuten que estaacute usando la base de datosprivate final Context contexto Clase helper usada para crear o actualizar la base de datos (hablamos de ella en la siguiente seccioacuten)private miHelperBD dbHelper

Crea la base de datos con ayuda del helperpublic MiAdaptadorBD(Context _contexto)

contexto = _contextodbHelper = new miHelperBD(contexto NOMBRE_BASE_DATOS

nullVERSION_BASE_DATOS)

Abre una base de datos ya existentepublic MiAdaptadorBD open() throws SQLException

db = dbHelpergetWritableDatabase()return this

Cierra la base de datos cuando eacutesta ya no va a ser utilizadapublic void close()

dbclose()

Meacutetodo para insertar un elemento en la tabla de la base dedatos

public int insertar(MiObjeto _miObjeto) PENDIENTE crear nuevos elementos ContentValues para

representar al objeto e insertarlos en la base de datos

Devolvemos el iacutendice del nuevo elemento en la base dedatos

return index

Persistencia

8Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Meacutetodo para eliminar un elemento de la tabla de la base dedatos

Devuelve un valor booleano indicando el eacutexito o no de la operacioacutenpublic boolean eliminar(long _indiceFila)

return dbdelete(TABLA_BASE_DATOS CP_ID + = +_indiceFila null) gt 0

Meacutetodo para obtener un Cursor que apunte a todas las filascontenidas

en la tabla de la base de datospublic Cursor obtenerTodos ()

return dbquery(TABLA_BASE_DATOS new String[] CP_IDCOLUMNA_NOMBRE

null null null nullnull)

Meacutetodo para obtener un elemento determinado de la base de datos a partir de su iacutendice de filapublic MiObjeto getObjeto(long _indiceFila)

PENDIENTE obtener un cursor a una fila de la base de datos y usar sus valores para inicializar una instancia de MiObjeto

return objectInstance

Meacutetodo para actualizar un elemento de la tabla de la base dedatos

public boolean actualizar(long _indiceFila MiObjeto _miObjeto) PENDIENTE crear nuevos ContentValues a partir del

objeto y usarlos para actualizar una fila en la tabla

correspondiente

return true

Clase privada auxiliar para ayudar en la creacioacuten yactualizacioacuten

de la base de datosprivate static class miHelperBD extends SQLiteOpenHelper

public miHelperBD(Context contexto String nombreCursorFactory factory int version)

super(contexto nombre factory version)

Meacutetodo llamado cuando la base de datos no existe en eldisco

y la clase Helper necesita crearla desde ceroOverridepublic void onCreate(SQLiteDatabase _db)

_dbexecSQL(CREAR_BASE_DATOS)

Meacutetodo llamado cuando la versioacuten de la base de datos en disco es diferente a la indicada en este caso la base de datos en disco necesita ser actualizadaOverridepublic void onUpgrade(SQLiteDatabase _db int

_versionAnterior

Persistencia

9Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

int _versionNueva) Mostramos en LogCat la operacioacutenLogw(AdaptadorBD Actualizando desde la

versioacuten +_versionAnterior + a la versioacuten +_versionNueva + lo cual borraraacute los

datospreviamente almacenados)

Actualizamos la base de datos para que seadapte a la nueva versioacuten

La forma maacutes sencilla de llevar a cabo dichaactualizacioacuten es

eliminar la tabla antigua y crear una nueva_dbexecSQL(DROP TABLE IF EXISTS +

TABLA_BASE_DATOS)onCreate(_db)

134 La clase SQLiteOpenHelper

La clase SQLiteOpenHelper es una clase abstracta utilizada como medio paraimplementar un patroacuten de creacioacuten apertura y actualizacioacuten de una determinada base dedatos Al implementar una subclase de SQLiteOpenHelper podemos abstraernos de laloacutegica subyacente a la decisioacuten de crear o actualizar una base de datos de manera previa aque eacutesta deba ser abierta

En el ejemplo de coacutedigo anterior se vio coacutemo crear una subclase de SQLiteOpenHelper

por medio de la sobrecarga de sus meacutetodos onCreate y onUpgrade los cuales permitencrear una nueva base de datos o actualizar a una nueva versioacuten respectivamente

NotaEn el ejemplo anterior el meacutetodo onUpgrade simplemente eliminaba la tabla existente y lareemplazaba con la nueva definicioacuten de la misma En la praacutectica la mejor solucioacuten seriacutea migrarlos datos ya existentes a la nueva tabla

Mediante los meacutetodos getReadableDatabase y getWritableDatabase podemos abrir yobtener una instancia de la base de datos de soacutelo lectura o de lectura y escriturarespectivamente La llamada a getWritableDatabase podriacutea fallar debido a falta deespacio en disco o cuestiones relativas a permisos por lo que es una buena praacutectica teneren cuenta esta posibilidad por medio del mecanismo de manejo de excepciones de JavaEn caso de fallo la solucioacuten podriacutea pasar por al menos proporcionar una copia de soacutelolectura tal como se muestra en el siguiente ejemplo

dbHelper = new miHelperBD(contexto NOMBRE_BASE_DATOS nullVERSION_BASE_DATOS)

SQLiteDatabase dbtry

db = dbHelpergetWritableDatabase() catch (SQLiteException ex)

db = dbHelpergetReadableDatabase()

Persistencia

10Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Al hacer una llamada a cualquiera de los dos meacutetodos anteriores se invocaraacuten losmanejadores de evento adecuados Asiacute pues si por ejemplo la base de datos no existieraen disco se ejecutariacutea el coacutedigo de onCreate Si por otra parte la versioacuten de la base dedatos se hubiera modificado se disparariacutea el manejador onUpgrade Lo que sucedeentonces es que al hacer una llamada a getWritableDatabase o getReadableDatabase

se devolveraacute una base de datos nueva actualizada o ya existente seguacuten el caso

135 Crear una base de datos sin SQLiteHelper

Tambieacuten es posible crear y abrir bases de datos sin necesidad de utilizar una subclase deSQLiteHelper Para ello haremos uso del meacutetodo openOrCreateDatabase En este casoseraacute necesario tambieacuten hacer uso del meacutetodo execSQL sobre la instancia de la base dedatos devuelta por el meacutetodo anterior para ejecutar los comandos SQL que permitiraacutencrear las tablas de la base de datos y establecer las relaciones entre ellas El siguientecoacutedigo muestra un ejemplo

private static final String NOMBRE_BASE_DATOS = mibasededatosdbprivate static final String TABLA_BASE_DATOS = tablaPrincipal

private static final String CREAR_BASE_DATOS =create table + TABLA_BASE_DATOS + _id integer primare key

autoincrement +columna_uno text not null)

SQLiteDatabase db

private void crearBaseDatos() db = openOrCreateDatabase(NOMBRE_BASE_DATOS ContextMODE_PRIVATE

null)dbexecSQL(CREAR_BASE_DATOS)

NotaAunque no es necesariamente obligatorio es recomendable que cada tabla incluya un campo detipo clave primaria con autoincremento a modo de iacutendice para cada fila En el caso en el que sedesee compartir la base de datos mediante un proveedor de contenidos un campo uacutenico de estetipo es obligatorio

136 Realizar una consulta

El resultado de cualquier consulta a una base de datos seraacute un objeto de la clase CursorEsto permite a Android manejar los recursos de manera maacutes eficiente de tal forma que enlugar de devolver todos los resultados eacutestos se van proporcionando conforme senecesitan

Para realizar una consulta a una base de datos haremos uso del meacutetodo query de lainstancia correspondiente de la clase SQLiteDatabase Los paraacutemetros que requiere este

Persistencia

11Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

meacutetodo son los siguientes

bull Un booleano opcional que permite indicar si el resultado deberiacutea contener tan soacutelovalores uacutenicos

bull El nombre de la tabla sobre la que realizar la consultabull Un array de cadenas que contenta un listado de las columnas que deben incluirse en el

resultadobull Una claacuteusula where que defina las filas que se deben obtener de la tabla Se puede

utilizar el comodiacuten el cual seraacute reemplazado por los valores que se pasen a traveacutesdel siguiente paraacutemetro

bull Un array de cadenas correspondientes a argumentos de seleccioacuten que reemplazaraacutenlos siacutembolos en la claacuteusula where

bull Una claacuteusula group by que define coacutemo se agruparaacuten las filas obtenidas comoresultado

bull Un filtro having que indique que grupos incluir en el resultado en el caso en el que sehaya hecho uso del paraacutemetro anterior

bull Una cadena que describa la ordenacioacuten que se llevaraacute a cabo sobre las filas obtenidascomo resultado

bull Una cadena opcional para definir un liacutemite en el nuacutemero de resultados a devolver

NotaComo se puede observar la mayoriacutea de estos paraacutemetros hacen referencia al lenguaje SQLTratar este tema queda fuera de los objetivos de este curso por lo que en nuestros ejemplos ledaremos en la mayoriacutea de las ocasiones un valor null a estos paraacutemetros

El siguiente coacutedigo muestra un ejemplo de consultas utilizando diversos paraacutemetros

Devolver los valores de las columnas uno y tres para todas las filas sin duplicadosString[] columnas = new String[] CLAVE_ID CLAVE_COL1 CLAVE_COL3

Cursor todasFilas = dbquery(true TABLA_BASE_DATOS columnas null nullnull null

null null)

Devolvemos los valores de todas las columnas para aquellas filas cuyovalor de la columna 3 sea igual al de una determinada variable Ordenamos las filasseguacuten el valor de la columna 5 Como queremos los valores de todas las columnas le damos al paraacutemetro correspondiente a las columnas a devolver el valor null En este caso no se ha hecho uso del primer paraacutemetro booleano que esoptativoString where = CLAVE_COL3 + = + valorString orden = CLAVE_COL5Cursor miResultado = dbquery(TABLA_BASE_DATOS null where null nullnull orden)

137 Extraer resultados de un cursor

Persistencia

12Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El primer paso para obtener resultados de un cursor seraacute desplazarnos a la posicioacuten apartir de la cual queremos obtener los datos usando cualquiera de los meacutetodosespecificados anteriormente (como por ejemplo moveToFirst o moveToPosition) Unavez hecho esto hacemos uso de meacutetodos get[TIPO] pasando como paraacutemetro el iacutendice dela columna Esto tendraacute como resultado la devolucioacuten del valor para dicha columna de lafila actualmente apuntada por el cursor

String valor = miResultadogetString(indiceColumna)

A continuacioacuten podemos ver un ejemplo maacutes completo en el que se va iterando a lo largode los resultados apuntados por un cursor extrayendo y sumando los valores de unacolumna de flotantes

int COLUMNA_VALORES_FLOTANTES = 2Cursor miResultado = dbquery(Tabla null null null null nullnull)float total = 0

Nos aseguramos de que se haya devuelto al menos una filaif (miResultadomoveToFirst())

Iteramos sobre el cursordo

float valor =miResultadogetFloat(COLUMNA_VALORES_FLOTANTES)

total += valor while (miResultadomoveToNext())

float media = total miResultadogetCount()

NotaDebido a que las columnas de las bases de datos en SQLite son debilmente tipadas podemosrealizar castings cuando sea necesario Por ejemplo los valores guardados como flotantes en labase de datos podriacutean leerse maacutes adelante como cadenas

138 Antildeadir actualizar y borrar filas

La clase SQLiteDatabase proporciona los meacutetodos insert delete y update queencapsulan las instrucciones SQL requeridas para llevar a cabo estas acciones Ademaacutesdisponemos de la posibilidad de utilizar el meacutetodo execSQL el cual nos permite ejecutarcualquier sentencia SQL vaacutelida en nuestras tablas de la base de datos en el caso en el quequeramos llevar a cabo estas (u otras) operaciones manualmente

NotaCada vez que modifiques los valores de la base de datos puedes actualizar un cursor que se puedaver afectado por medio del meacutetodo refreshQuery de la clase Cursor

Para insertar una nueva fila es necesario construir un objeto de la clase ContentValues

y usar su meacutetodo put para proporcionar valor a cada una de sus columnas Para insertar la

Persistencia

13Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

nueva fila pasaremos como paraacutemetro este objeto del tipo ContentValues al meacutetodoinsert de la instancia de la base de datos junto por supuesto con el nombre de la tablaA continuacioacuten se muestra un ejemplo

Crear la nueva filaContentValues nuevaFila = new ContentValues()

Asignamos valores a cada columnanuevaFilaput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas ]

Insertar la nueva fila en la tabladbinsert(TABLA_BASE_DATOS null nuevaFila)

NotaLos ficheros como imaacutegenes o ficheros de audio no se suelen almacenar dentro de tablas en unabase de datos En estos casos se suele optar por almacenar en la base de datos una cadena con laruta al fichero o una URI

La operacioacuten de actualizar una fila tambieacuten puede ser llevada a cabo por medio del usode la clase ContentValues En este caso deberemos llamar al meacutetodo update de lainstancia de la base de datos pasando como paraacutemetro el nombre de la tabla el objetoContentValues y una claacuteusula where que especifique la fila o filas a actualizar tal comose puede ver en el siguiente ejemplo

Creamos el objeto utilizado para definir el contenido a actualizarContentValues valoresActualizar = new ContentValues()

Asignamos valores a las columnas correspondientesvaloresActualizarput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas a actualizar ]

String where = CLAVE_ID + = + idFila

Actualizamos la fila con el iacutendice especificado en la instruccioacutenanteriordbupdate(TABLA_BASE_DATOS valoresActualizar where null)

Por uacuteltimo para borrar una fila simplemente llamaremos al meacutetodo delete de lainstancia de la base de datos especificando en este caso el nombre de la tabla que se veraacuteafectada por la operacioacuten y una claacuteusula where que utilizaremos para especificar las filasque queremos borrar Un ejemplo podriacutea ser el siguiente

dbdelete(TABLA_BASE_DATOS CLAVE_ID + = + idFila null)

Persistencia

14Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

2 Ejercicios - Persistencia en Android ficheros y SQLite

21 Uso de ficheros (05 puntos)

En este ejercicio vamos a crear una aplicacioacuten que muestre un listado de cadenas porpantalla Estas cadenas se almacenaraacuten en un fichero de texto privado para la aplicacioacutenPodremos antildeadir nuevas cadenas a partir de un cuadro de edicioacuten presente en la interfazPartiremos del proyecto Ficheros proporcionado en las plantillas de la aplicacioacuten Debesseguir los siguientes pasos

bull Antildeadir un manejador al botoacuten de la actividad principal para que cada vez que seapulsado se guarde en un fichero de texto El fichero se llamaraacute ficherotxt Al abrirel fichero para escritura se utilizaraacute el paraacutemetro ContextMODE_APPEND con lo quecada nueva cadena se antildeadiraacute al final del fichero en el caso en el que eacuteste ya existieraPara escribir en el fichero utilizaremos el meacutetodo writeBytes del objetoDataOutputStream correspondiente

bull Al pulsar el botoacuten tambieacuten se deberaacute borrar el contenido del elemento EditText (leasignamos a la vista una cadena vaciacutea con el meacutetodo setText)

bull Ejecutamos la aplicacioacuten e introducimos algunas liacuteneas en el fichero Vamos acomprobar ahora que todo ha funcionado correctamente Para ello accedemos alsistema de ficheros de nuestro dispositivo ejecutando el comando adb shell

dentro de la carpeta platform-tools de nuestra carpeta de instalacioacuten del SDK deAndroid

bull Comprueba que se ha creado el fichero ficherotxt en la carpetadatadataesuajtechandroidficherosfiles Examina su contenido ejecutando cat

ficherotxt Si algo no ha salido bien siempre podraacutes eliminar el fichero con rm

ficherotxtbull En la interfaz de la actividad se ha incluido una vista de tipo TextView debajo del

botoacuten Antildeade un manejador para dicha vista de tal forma que cada vez que se hagaclick sobre ella se muestre el contenido del fichero ficherotxt Para ello leeremos elfichero liacutenea a liacutenea antildeadiendo cada una al TextView por medio del meacutetodo append

Persistencia

15Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Interfaz de la aplicacioacuten Ficheros

22 Persistencia con ficheros (05 puntos)

Seguramente habraacutes observado que si abandonas la aplicacioacuten anterior (pulsando el botoacutenBACK del dispositivo) al volver a ejecutarla el contenido del TextView ha desaparecidoDebes volver a pulsar sobre la vista para que se vuelva a mostrar el contenido del ficheroHaz las modificaciones pertinentes para que esto no sea asiacute es decir para que cuandovuelva a mostrarse la actividad tras haber sido eliminada de la memoria se muestre elTextView tal cual se veiacutea antes de abandonar la aplicacioacuten

AvisoEn este ejercicio no se estaacute pidiendo que cargues el contenido del fichero en el TextView nadamaacutes arrancar la aplicacioacuten Puede darse el caso de que lo que esteacute mostrando el TextViewcuando la actividad sea eliminada de memoria no se corresponda con el contenido actualizado delfichero

NotaPara poder completar el ejercicio deberaacutes repasar los manejadores de evento de la primera sesioacutendel moacutedulo de Android relacionados con el ciclo de vida de ejecucioacuten de actividades

23 Base de datos SQLiteOpenHelper (05 puntos)

En las plantillas de la aplicacioacuten se incluye la aplicacioacuten BaseDatos En el proyecto de laaplicacioacuten se incluye el esqueleto de una clase MiAdaptadorBD que tendremos quecompletar Se trata de un patroacuten que nos va a permitir acceder a una base de datos de

Persistencia

16Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

usuarios dentro de la aplicacioacuten sin necesidad de hacer uso de coacutedigo SQL

La clase MiAdaptadorBD incluye a su vez otro patroacuten en este caso implementado comouna subclase de SQLiteOpenHelper Eacuteste nos obliga a definir queacute ocurre cuando la basede datos todaviacutea no existe y debe ser creada y queacute ocurre si ya existe pero debe seractualizada porque ha cambiado de versioacuten Asiacute el SQLiteOpenHelper queimplementemos en este caso MiOpenHelper nos devolveraacute siempre una base de datosseparaacutendonos de la loacutegica encargada de comprobar si la base de datos existe o no

En este primer ejercicio se pide hacer lo siguiente

bull Ejecutar la sentencia de creacioacuten de bases de datos (la tenemos declarada comoconstante de la clase) en el meacutetodo MiOpenHelperonCreate

bull Implementar tambieacuten el meacutetodo onUpgrade Idealmente eacuteste deberiacutea portar las tablasde la versioacuten antigua a la versioacuten nueva copiando todos los datos Nosotros vamos aeliminar directamente la tabla que tenemos con la sentencia SQL DROP TABLE IF

EXISTS + NOMBRE_TABLA y volveremos a crearlabull En el constructor de MiAdaptadorBD debemos obtener en el campo db la base de

datos utilizando MiOpenHelper

24 Base de datos insercioacuten y borrado (05 puntos)

Continuamos trabajando con el proyecto anterior En este ejercicio completaremos elcoacutedigo relacionado con las sentencias de insercioacuten y borrado Para realizar la insercioacutenvamos a hacer uso de un mecanismo que no hemos visto en la sesioacuten de teoriacutea y queconsiste en utilizar una sentencia de insercioacuten SQL precompilada que se completaraacute conlos valores concretos a insertar en la base de datos justo antes de realizar dicha insercioacutenLa ventaja de las sentencias compiladas es que evitan que se produzcan ataques deinyeccioacuten de coacutedigo

Como se puede observar se ha incluido un atributo a la clase que representa la insercioacutenSQL

private static final String INSERT = insert into + NOMBRE_TABLA +(+COLUMNAS[1]+) values ()

En esta sentencia no se indica ninguacuten valor concreto para la columna nombre En el lugaren el que deberiacutean aparecer los valores de dicho campo se ha escrito simplemente unsiacutembolo de interrogacioacuten Tambieacuten se ha antildeadido una instancia de la claseSQLiteStatement como parte de los atributos de la clase

private SQLiteStatement insertStatement

Realizamos los siguientes pasos

bull En el constructor de la clase MiAdaptadorBD llevamos a cabo la compilacioacuten de lasentencia

thisinsertStatement = thisdbcompileStatement(INSERT)

Persistencia

17Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Una vez hecho esto cada vez que deseemos insertar un nuevo usuario en la base dedatos deberemos dar valores concretos a la columna nombre y ejecutar la sentenciaPara ello debemos antildeadir el siguiente coacutedigo dentro del meacutetodo insert de la claseMiAdaptadorBD

thisinsertStatementbindString(1 nombreUsuario)return thisinsertStatementexecuteInsert()

bull Por uacuteltimo completamos el coacutedigo del meacutetodo deleteAll cuyo contenido es eliminara todos los usuarios de la base de datos Para que ademaacutes se devuelva el nuacutemero defilas afectadas podemos insertar la siguiente liacutenea en dicho meacutetodo

return dbdelete(NOMBRE_TABLA null null)

Una vez hechos todos estos cambios podremos utilizar la clase MiAdaptadorBD parahacer operaciones con la base de datos de usuarios de manera transparente

25 Base de datos probar nuestro adaptador (05 puntos)

Antildeade coacutedigo en la actividad Main para eliminar todos los usuarios de la base de datosantildeadir dos cualesquiera y listarlos por medio de la vista de tipo TextView que seencuentra en el layout de dicha actividad

Podemos comprobar mediante la liacutenea de comandos (comando adb shell) que la basede datos ha sido creada Para ello puede ser uacutetil hacer uso de los siguientes comandos

cd datadataesuajtechandroidbasedatosdatabasessqlite3 misusuariosdbsqlitegt schemasqlitegt tablessqlitegt select from usuarios

26 Base de datos cambios en la base de datos (05 puntos)

Ahora vamos a cambiar en la clase MiAdaptadorBD el nombre de la segunda columnaque en lugar de nombre se va a llamar nombres Ejecutamos la aplicacioacuten y comprobamosque sale con una excepcioacuten Comprueba por medio de LogCat cuaacutel ha sido el erroriquestCoacutemo lo podemos solucionar

NotaPista conforme hemos programado la clase MiAdaptadorBD y siguiendo el patroacuten de disentildeode SQLiteOpenHelper es posible arreglar el problema simplemente modificando el valor deun campo

Persistencia

18Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

3 Persistencia en Android proveedores de contenidos ySharedPreferences

31 Shared Preferences

Comenzamos esta sesioacuten hablando de una de las teacutecnicas maacutes simples de persistencia enAndroid junto al uso de ficheros Shared Preferences Se trata de un mecanismo ligeropara almacenar datos basado en pares clave-valor utilizado normalmente para guardarpreferencias u opciones de la aplicacioacuten Tambieacuten puede ser utilizado para almacenar elestado de la interfaz graacutefica para contemplar el caso de que nuestra actividad debafinalizar abruptamente su ejecucioacuten Otro posible uso es el de compartir informacioacutenentre componentes de una misma aplicacioacuten

NotaLos Shared Preferences nunca son compartidos entre diferentes aplicaciones

Este meacutetodo se basa en el uso de la clase SharedPreferences Es posible utilizarla paraguardar datos en muy diversos formatos booleanos cadenas flotantes y enteros

311 Guardar Shared Preferences

Para crear o modificar un Shared Preference llamamos al meacutetodogetSharedPreferences del contexto de la aplicacioacuten pasando como paraacutemetro elidentificador del conjunto de valores de preferencias con el que queremos trabajar Pararealizar la modificacioacuten del valor de un Shared Preference hacemos uso de la claseSharedPreferencesEditor Para obtener el objeto editor invocamos el meacutetodo edit

del objeto Shared Preferences que queremos modificar Los cambios se guardan medianteel meacutetodo commit Veamos un ejemplo

int modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences = getSharedPreferences(Mispreferencias modo)

Obtenemos un editor para modificar las preferenciasSharedPreferencesEditor editor = misSharedPreferencesedit()

Guardamos nuevos valores en el objeto SharedPreferenceseditorputBoolean(isTruetrue)editorputFloat(unFloat10)editorputInt(numeroEntero12)editorputLong(otroNumero2)editorputString(unaCadenavalor)

Guardamos los cambioseditorcommit()

Persistencia

19Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

312 Leer Shared Preferences

Para leer Shared Preferences utilizamos el meacutetodo getSharedPreferences tal como seha comentado anteriormente Pasamos como paraacutemetro el identificador del conjunto deShared Preferences al que deseamos acceder Una vez hecho esto ya podemos hacer usode meacutetodos de tipo get para acceder a preferencias individuales Hay diferentes meacutetodosget para diferentes tipos de datos Cada uno de ellos requiere como paraacutemetro la claveque identifica la preferencia cuyo valor deseamos obtener y un valor por defecto el cualse usaraacute en el caso concreto en el que no existiera la preferencia con la clave pasada comoprimer paraacutemetro Podemos ver un ejemplo a continuacioacuten

public static String MIS_PREFS = MIS_PREFS

public void cargarPreferences() Obtener las preferencias almacenadasint modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences =

getSharedPreferences(MIS_PREFS modo)

boolean isTrue = misSharedPreferencesgetBoolean(isTruefalse)float unFloat = misSharedPreferencesgetFloat(unFloat0)int numeroEntero = misSharedPreferencesgetInt(numeroEntero0)long otroNumero = misSharedPreferencesgetLong(otroNumero0)String unaCadena = misSharedPreferencesgetString(unaCadena)

313 Interfaces para Shared Preferences

Android proporciona una plataforma basada en XML para la creacioacuten de interfacesgraacuteficas para el manejo de preferencias Estas interfaces tendraacuten un aspecto similar al delas del resto de aplicaciones del sistema Al utilizarla nos estaremos asegurando de quenuestras actividades para el manejo de preferencias seraacuten consistentes con las del resto deaplicaciones del sistema De esta forma los usuarios estaraacuten familiarizados con el manejode esta parte de nuestra aplicacioacuten

La plataforma consiste en tres elementos

bull Layout de la actividad de preferencias se trata de un archivo XML que definiraacute lainterfaz graacutefica de la actividad que muestre los controles para modificar los valores delas preferencias Permite especificar las vistas a mostrar los posibles valores que sepueden introducir y a queacute clave de Shared Preferences se corresponde cada vista

bull Actividad de preferencias una subclase de PreferenceActivity que secorresponderaacute con la pantalla de preferencias de nuestra aplicacioacuten

bull Shared Preference Change Listener una implementacioacuten de la claseonSharedPreferenceChangeListener que actuaraacute en el caso en el que cambie elvalor de alguna Shared Preference

314 Definiendo una pantalla de preferencias con un layout en XML

Persistencia

20Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esta es sin duda la parte maacutes importante de una actividad de preferencias Se trata de unarchivo XML utilizado para definir varios aspectos de dicha actividad Al contrario queen el caso de los archivos de layout para interfaces graacuteficas que vimos en la sesioacutencorrespondiente del moacutedulo de Android estos archivos de layout se guardan en la carpetaresxml de los recursos de la aplicacioacuten

Aunque conceptualmente estos layouts son similares a los utilizados para definirinterfaces graacuteficas los layouts de actividades de preferencias utilizan un conjuntoespecializado de componentes especiacuteficos para este tipo de actividades Estaacuten pensadospara proporcionar interfaces con un aspecto similar al del resto de actividades depreferencias del sistema Estos controles se describen en maacutes detalle en la siguienteseccioacuten

Los layout de preferencias contendraacuten un elemento PreferenceScreen

ltxml version=10 encoding=utf-8gtltPreferenceScreen

xmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceScreengt

Se pueden antildeadir maacutes elementos de este tipo si se desea Al hacerlo eacutestos serepresentaraacuten como un elemento seleccionable en un listado que mostraraacute una nuevaventana de preferencias en caso de que sea seleccionado

Dentro de cada elemento PreferenceScreen podemos incluir tantos elementos de tipoPreferenceCategory y elementos especiacuteficos para antildeadir controles como se desee Loselementos PreferenceCategory son utilizados para dividir cada pantalla de preferenciasen subcategoriacuteas mediante una barra de tiacutetulo Su sintaxis es la siguiente

ltPreferenceCategoryandroidtitle=My Preference Categorygt

ltPreferenceCategorygt

Una vez dividida la pantalla en subcategoriacuteas tan soacutelo quedariacutea antildeadir los elementoscorrespondientes a los controles especiacuteficos que veremos en la siguiente seccioacuten Losatributos de los elementos XML para estos controles pueden variar aunque existe unconjunto de atributos comunes

bull androidkey la clave de Shared Preference que se usaraacute para guardar el valor delcontrol

bull androidtitle texto a mostrar para describir la preferenciabull androidsummary una descripcioacuten maacutes larga a mostrar debajo del texto definido con

el campo anteriorbull androiddefaultValue el valor por defecto que se mostraraacute inicialmente y tambieacuten

el que finalmente se guardaraacute si ninguacuten otro valor fue asignado a este campo

El siguiente listado muestra una pantalla de preferencias muy simple con una uacutenicacategoriacutea y un uacutenico control (un check box)

ltxml version=10 encoding=utf-8gt

Persistencia

21Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltPreferenceScreenxmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceCategory

androidtitle=Mi categoriacuteagtltCheckBoxPreference

androidkey=MI_CHECK_BOXandroidtitle=Preferencia Check Boxandroidsummary=Descripcioacuten de la preferencia

Check BoxandroiddefaultValue=true

gtltPreferenceCategorygt

ltPreferenceScreengt

Esta pantalla de preferencias se mostrariacutea de la siguiente forma

Ejemplo sencillo de ventana de preferencias

315 Controles nativos para preferencias

Android incluye diferentes controles que podemos antildeadir a nuestras ventanas depreferencias por medio de los elementos XML que se indican a continuacioacuten

bull CheckBoxPreference un check box estaacutendar que puede ser utilizado parapreferencias de tipo booleano

bull EditTextPreference permite al usuario introducir una cadena como valor para unapreferencia Al seleccionar el texto se mostraraacute un diaacutelogo con el que introducir elnuevo valor

bull ListPreference el equivalente a un Spinner Seleccionar este elemento de lainterfaz mostraraacute un diaacutelogo con las diferentes opciones que es posible seleccionar Seutilizan arrays para asociar valores y textos a las opciones de la lista

bull RingtonePreference un tipo especiacutefico de preferencia de tipo listado que permiteseleccionar entre diferentes tonos de teleacutefono Esta opcioacuten es particularmente uacutetil en

Persistencia

22Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

el caso en el que se esteacute creando una pantalla de preferencias para configurar lasopciones de notificacioacuten de una aplicacioacuten o componente de la misma

Tambieacuten es posible crear nuestros propios controles personalizados definiendo unasubclase de Preference (o cualquiera de sus subclases) Se puede encontrar maacutesinformacioacuten en la documentacioacuten de AndroidhttpdeveloperandroidcomreferenceandroidpreferencePreferencehtml

316 Actividades de preferencias

Para mostrar una pantalla de preferencias debemos definir una subclase dePreferenceActivity

public class MisPreferencias extends PreferenceActivity

La interfaz graacutefica de la interfaz se puede crear a partir del layout en el meacutetodo onCreatemediante una llamada a addPreferencesFromResource

Overridepublic void onCreate(Bundle savedInstaceState)

superonCreate(savedInstanceState)addPreferencesFromResource(Rxmlmilayout)

Como en el caso de cualquier otra actividad la actividad de preferencias que hayamosdefinido debe ser incluida en el Manifest de la aplicacioacuten

ltactivity androidname=MisPreferenciasandroidlabel=Mis preferenciasgt

ltactivitygt

Eso es todo lo que necesitamos para crear este tipo de actividades y mostrar una ventanacon las preferencias definidas en el layout Esta actividad puede ser iniciada comocualquier otra mediante un Intent ya sea a traveacutes de una llamada a startActivity obien a startActivityForResult

Intent i = new Intent(this MisPreferenciasclass)startActivityForResult(i MOSTRAR_PREFERENCIAS)

Los valores para las diferentes preferencias de la actividad son almacenadas en elcontexto de la aplicacioacuten Esto permite a cualquier componente de dicha aplicacioacutenincluyendo a cualquier actividad y servicio de la misma acceder a los valores tal como semuestra en el siguiente coacutedigo de ejemplo

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto) PENDIENTE hacer uso de meacutetodos get para obtener los valores

317 Shared Preference Change Listeners

Persistencia

23Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El uacuteltimo elemento que nos queda por examinar en esta plataforma de Shared Preferenceses la interfaz onSharedPreferenceChangeListener que es utilizada para invocar unevento cada vez que una preferencia es antildeadida eliminada o modificada Para registrarListeners usamos un coacutedigo como el que se muestra a continuacioacuten en el que la parte maacutesimportante es sin duda el meacutetodo onSharedPreferenceChanged dentro del cualdeberemos determinar queacute preferencia ha cambiado de estado a partir de sus paraacutemetrospara llevar a cabo las acciones oportunas

public class MiActividad extends Activity implementsOnSharedPreferenceChangeListener

Overridepublic void onCreate(Bundle SavedInstanceState)

Registramos este objetoOnSharedPreferenceChangeListener

Context contexto = getApplicationContext()SharedPreferences prefs =

PreferenceManagergetDefaultSharedPreferences(contexto)prefsregisterOnSharedPreferenceChangeListener(this)

public void onSharedPreferenceChanged(SharedPreferences prefsString clave)

PENDIENTE comprobar queacute clave concreta ha cambiado ysu nuevo valor

para modificar la interfaz de una actividad o elcomportamiento de

alguacuten componente

Otra forma maacutes sencilla de controlar los cambios en las preferencias es registrar unlistener en el constructor de la actividad principal o aquella que haga uso de los valores depreferencias de la forma

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto)

mListener = new OnSharedPreferenceChangeListener() public void onSharedPreferenceChanged(SharedPreferences

sharedPreferences String key)

if( keyequals(MICLAVE) )boolean valor =

sharedPreferencesgetBoolean(MICLAVE false)

prefsregisterOnSharedPreferenceChangeListener(mListener)

NotaEn este segundo ejemplo mListener es una variable miembro de la clase Para este caso esnecesario registrar el listener de esta forma porque sino el garbage collector lo eliminariacutea (debidoa particularidades en la programacioacuten de los listeners asociados a las SharedPreferences)

Persistencia

24Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

32 Proveedores de contenidos

Los proveedores de contenidos o ContentProvider proporcionan una interfaz parapublicar y consumir datos identificando la fuente de datos con una direccioacuten URI queempieza por content Son una forma maacutes estaacutendar que los adaptadores (como el quevimos en la sesioacuten anterior en la seccioacuten dedicada a SQLite) de desacoplar la capa deaplicacioacuten de la capa de datos

Podemos encontrar dos tipos principales de proveedores de contenidos en Android Losproveedores nativos son proveedores que el propio sistema nos proporciona para poderacceder a datos del dispostivo incluyendo audio viacutedeo imaacutegenes informacioacuten personaletc Por otra parte tambieacuten es posible que creemos nuestros propios proveedores con talde permitir a nuestra aplicacioacuten compartir datos con el resto del sistema En esta sesioacutenhablaremos de ambos tipos

321 Proveedores nativos

Android incluye una serie de proveedores de contenidos que nos pueden proporcionarinformacioacuten de diferentes tipos informacioacuten sobre nuestros contactos informacioacuten delcalendario e incluso ficheros multimedia Ejemplos de estos proveedores de contenidosnativos son el Browser CallLog ContactsContract MediaStore Settings y elUserDictionary Para poder hacer uso de estos proveedores de contenidos y acceder alos datos que nos proporcionan debemos antildeadir los permisos adecuados al ficheroAndroidManifestxml Por ejemplo para acceder al listiacuten telefoacutenico podemos antildeadir elpermiso correspondiente de la siguiente forma

ltuses-sdk androidminSdkVersion=8 gtltuses-permission androidname=androidpermissionREAD_CONTACTSgt

ltmanifestgt

Para obtener informacioacuten de un ContentProvider debemos hacer uso del meacutetodo query

de la clase ContentResolver El primero de los paraacutemetros de dicho meacutetodo seraacute la URIdel proveedor de contenidos al que deseamos acceder Este meacutetodo devuelve un cursorque nos permitiraacute iterar entre los resultados tal como se vio en la sesioacuten anterior en elapartado de cursores

ContentResolverquery(Uri uriString[] projectionString selectionString[] selectionArgsString sortOrder)

Por ejemplo la siguiente llamada nos proporcionaraacute un cursor que nos permitiraacute acceder atodos los contactos de nuestro teleacutefono moacutevil Para ello hemos hecho uso de la constante

Persistencia

25Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ContactsContractContactsCONTENT_URI que almacena la URI del proveedor decontenidos correspondiente Al resto de paraacutemetros se le ha asignado el valor null

ContentResolver cr = getContentResolver()Cursor cursor = crquery(ContactsContractContactsCONTENT_URI

null null null null)

NotaEn versiones anteriores a Android 20 las estructuras de datos de los contactos son diferentes y nose accede a esta URI

Una vez obtenido el cursor es posible mapear sus campos con alguacuten componente de lainterfaz graacutefica de la actividad De esta forma cualquier cambio que se produzca en elcursor se reflejaraacute automaacuteticamente en el componente graacutefico sin que sea necesario queprogramemos el coacutedigo que lo refresque Para esto tenemos que llamar al meacutetodosetNotificationUri del cursor con lo cual nos aseguraremos de que los listeners seraacutennotificados cuando se produzca alguacuten cambio en los datos referenciados por dicho cursor

cursorsetNotificationUri(crContactsContractContactsCONTENT_URI)

Para asignar un adaptador a una lista de la interfaz graacutefica de la actividad maacutesconcretamente una ListView utilizamos el meacutetodo setAdapter(Adapter)

ListView lv = (ListView)findViewById(RidListView01)SimpleCursorAdapter adapter = new SimpleCursorAdapter(

getApplicationContext()Rlayouttextviewlayoutcursornew String[]

ContactsContractContacts_IDContactsContractContactsDISPLAY_NAME

new int[]RidTextView1RidTextView2)

lvsetAdapter(adapter)

En este ejemplo los identificadores RidTextView1 y RidTextView2 se correspondencon views del layout que define cada fila de la ListView como se puede ver en elarchivo textviewlayoutxml que tendriacuteamos que crear

ltxml version=10 encoding=utf-8gt

ltLinearLayout xmlnsandroid=httpschemasandroidcomapkresandroidandroidorientation=horizontalandroidlayout_width=fill_parentandroidlayout_height=wrap_contentgt

ltTextView androidid=+idTextView1androidtextStyle=bold

Persistencia

26Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

androidems=2androidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltTextView androidid=+idTextView2androidtextStyle=boldandroidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltLinearLayoutgt

La lista en siacute (identificada por RidListView01 en el presente ejemplo) estariacutea en otroXML layout por ejemplo en mainxml

Se debe tener en cuenta que en el caso de este ejemplo si quisieacuteramos obtener losnuacutemeros de teleacutefono de cada uno de nuestros contactos tendriacuteamos que recorrer el cursorobtenido a partir del proveedor de contenidos y por cada persona en nuestra agenda crearun nuevo cursor que recorriera los teleacutefonos ya que una persona puede tener asignadosvarios teleacutefonos

Puedes acceder a un listado completo de los proveedores de contenidos nativos existentesen Android consultando su manual de desarrollo Concretamente leyendo la seccioacutendedicada al paquete androidprovider enhttpdeveloperandroidcomreferenceandroidproviderpackage-summaryhtml

322 Proveedores propios crear un nuevo proveedor de contenidos

Para acceder a nuestras fuentes de datos propias de manera estaacutendar nos interesaimplementar nuestros propios ContentProvider Gracias a que proporcionamos nuestrosdatos a partir de una URI quien use nuestro proveedor de contenidos no deberaacutepreocuparse de doacutende provienen los datos indicados por dicha URI podriacutean provenir deficheros locales en la tarjeta de memoria de un servidor en Internet o de una base dedatos SQLite Dada una URI nuestro proveedor de contenidos deberaacute proporcionar en suinterfaz los meacutetodos necesarios para realizar las operaciones baacutesicas en una base de datosinsercioacuten lectura actualizacioacuten y borrado

Para crear un nuevo proveedor de contenidos definimos una subclase deContentProvider Utilizaremos una sobrecarga del meacutetodo onCreate para crear einicializar la fuente de datos que queramos hacer puacuteblica a traveacutes de dicho proveedor Unesqueleto de subclase de ContentProvide podriacutea ser el siguiente

public class MiProveedor extends ContentProvider

Overridepublic boolean onCreate()

Inicializar la base de datosreturn true

Dentro de la clase deberemos crear un atributo puacuteblico y estaacutetico de nombreCONTENT_URI en el cual almacenaremos el URI completo del proveedor que estamos

Persistencia

27Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creando Este URI debe ser uacutenico por lo que una buena idea puede ser tomar como baseel nombre de paquete de nuestra aplicacioacuten La forma general de un URI es la siguiente

content[NOMBRE_PAQUETE]provider[NOMBRE_APLICACION][RUTA_DATOS]

Por ejemplo

contentesuajtechandroidprovidermiaplicacionelementos

En general (y por simplicidad) no es necesario antildeadir la cadena provider al nombre delpaquete por lo que podemos utilizar directamente el nombre del paquete seguido de laruta de datos de la forma

contentesuajtechandroidmiaplicacionelementos

Estos URIs se pueden presentar en dos formas La URI anterior representa una consultadirigida a obtener todos los valores de ese tipo (en este caso todos los elementos) Si a laURI anterior antildeadimos un sufijo [NUMERO_FILA] estamos representando una consultadestinada a obtener un uacutenico elemento (por ejemplo el quinto elemento en el siguienteejemplo)

contentesuajtechandroidprovidermiaplicacionelementos5

NotaSe considera una buena praacutectica de programacioacuten el proporcionar acceso a nuestro proveedor decontenidos utilizando cualquiera de estas dos formas

La forma maacutes simple de determinar cuaacutel de las dos formas anteriores de URI se utilizoacutepara hacer una peticioacuten a nuestro proveedor de contenidos es mediante un Uri MatcherCrearemos y configuramos un elemento Uri Matcher para analizar una URI y determinara cuaacutel de las dos formas se corresponde En el siguiente coacutedigo se muestra el coacutedigobaacutesico que deberemos emplear para implementar este patroacuten

public class MiProveedor extends ContentProvider

private static final String miURI =contentesuajtechandroidprovidermiaplicacionelementos

public static final Uri CONTENT_URI = Uriparse(miUri)

Creamos las constantes que nos van a permitir diferenciar entre los dos tipos distintos de peticiones a partir de la URIprivate static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

Inicializamos el objeto UriMatcher Una URI que termine en elementos se corresponderaacute con una consulta para todas las filas mientras que una URI con el sufijo elementos[FILA] representaraacute una uacutenica filastatic

uriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidprovidermiaplicacion

elementos TODAS_FILAS)

Persistencia

28Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

uriMatcheraddURI(esuajtechandroidprovidermiaplicacionelementos UNA_FILA)

Overridepublic boolean onCreate()

PENDIENTE inicializar la base de datosreturn true

Esta misma teacutecnica se puede emplear para proporcionar diferentes alternativas de URIpara diferentes subconjuntos de datos como por ejemplo diferentes tablas en una base dedatos a traveacutes del mismo proveedor de contenidos

323 Proveedores propios crear la interfaz de consultas

Para permitir realizar operaciones con los datos publicados a traveacutes de nuestro proveedorde contenidos deberemos implementar los meacutetodos delete insert update y query

para el borrado insercioacuten actualizacioacuten y realizacioacuten de consultas respectivamenteEstos meacutetodos son la interfaz estaacutendar utilizada con proveedores de contenidos de formaque para compartir datos entre aplicaciones no se tendraacuten interfaces distintos paradiferentes fuentes de datos

Lo maacutes habitual suele ser implementar un proveedor de contenidos como un mecanismopara permitir el acceso a una base de datos SQLite privada a una aplicacioacuten aunque atraveacutes de estos meacutetodos indicados se podriacutea en principio acceder a cualquier fuente dedatos

El siguiente coacutedigo extiende el ejemplo anterior (MiProveedor) y muestra el esqueleto deestos meacutetodos dentro de una subclase de ContentProvider Obseacutervese que se hace usodel objeto UriMatcher para determinar que tipo de consulta se estaacute realizando

Overridepublic Cursor query(Uri uri

String[] projectionString selectionString[] selectionArgsString sort)

Si se trata de una consulta para obtener una uacutenica filalimitamos

los resultados a obtener de la fuente de datos por medio de una claacuteusula whereswitch(UriMatchermatch(uri))

case UNA_FILA PENDIENTE modifica la sentencia SELECT

mediante una claacuteusula where obteniendo el identificador de fila

como numeroFila = urigetPathSegments()get(1))

return null

Override

Persistencia

29Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public Uri insert(Uri _uri ContentValues _valores) long idFila = [ Antildeadir un nuevo elemento ]

Devolvemos la URI del elemento recieacuten antildeadidoif (idFila gt 0)

return ContentUriswithAppendendId(CONTENT_URI idFila)

throw new SQLException(No se pudo antildeadir un nuevo elemento a +_uri)

Overridepublic int delete(Uri uri String where String[] whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

Overridepublic int update(Uri uri ContentValues valores String where String[]whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

324 Proveedores propios tipo MIME

El uacuteltimo paso para crear un proveedor de contenidos es definir el tipo MIME queidentifica el tipo de datos que devuelve el proveedor Debemos sobrecargar el meacutetodogetType para que devuelva una cadena que identifique nuestro tipo de datos de manerauacutenica Se deberiacutean definir dos tipos posibles uno para el caso de una uacutenica fila y otro parael caso de devolver todos los resultados Se debe utilizar una sintaxis similar a la delsiguiente ejemplo

bull Un uacutenico elementovndesuajtechandroidmiaplicacioncursoritemmiproveedorcontent

bull Todos los elementosvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

Como se puede observar la cadena comienza siempre por vnd A continuacioacutentendriacuteamos el nombre del paquete de nuestra aplicacioacuten Lo siguiente es cursor ya quenuestro proveedor de contenidos estaacute devolviendo datos de tipo Cursor Justo antes de labarra pondremos item en el caso del tipo MIME para un uacutenico elemento y dir para elcaso del tipo MIME para todos los elementos Finalmente tras la barra pondremos elnombre de nuestra clase (en minuacutesculas) seguido de content En el siguiente coacutedigo deejemplo se muestra coacutemo sobrecargar getType seguacuten la URI

Persistencia

30Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Overridepublic String getType(Uri _uri)

switch (uriMatchermatch(_uri)) case TODAS_FILAS

returnvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

case UNA_FILAreturn

vndesuajtechandroidmiaplicacioncursoritemmiproveedorcontentdefault

throw new IllegalArgumentException(URI nosoportada + _uri)

325 Proveedores propios registrar el proveedor

No debemos olvidar incorporar nuestro proveedor de contenidos al Manifest de laaplicacioacuten una vez que lo hemos completado Esto se haraacute por medio del elementoprovider cuyo atributo authorities podremos utilizar para especificar la URI base talcomo se puede ver en el siguiente ejemplo

ltprovider androidname=MiProveedorandroidauthorities=esuajtechandroidmiaplicaciongt

326 Content Resolvers

Aunque en la seccioacuten de proveedores nativos ya se ha dado alguacuten ejemplo de coacutemo haceruso de un Content Resolver para realizar una consulta ahora que hemos podido observaren detalle la estructura de un proveedor de contenidos propio es un buen momento paraver en detalle coacutemo realizar cada una de las operaciones que eacutestos permiten

El contexto de una aplicacioacuten incluye siempre una instancia de la claseContentResolver a la cual se puede acceder mediante el meacutetodo getContentResolver

ContentResolver cr = getContentResolver()

Esta clase incorpora diversos meacutetodos para realizar consultas a proveedores decontenidos Cada uno de estos meacutetodos acepta como paraacutemetro una URI que indica conqueacute proveedor de contenidos se desea interactuar

Las consultas realizadas sobre un proveedor de contenidos recuerdan a las consultasrealizadas sobre bases de datos Los resultados de las consultas se devuelven comoobjetos de la clase Cursor Se pueden extraer valores del cursor de la misma forma en laque se explicoacute en el apartado de bases de datos de la sesioacuten anterior El meacutetodo query dela clase ContentResolver acepta los siguientes paraacutemetros

bull La URI del proveedor del contenidos al que se desea realizar la consultabull Una proyeccioacuten que enumera las columnas que se quieren incluir en los resultados

obtenidosbull Una claacuteusula where que define las filas a obtener Se pueden utilizar comodines que

Persistencia

31Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

se reemplazaraacuten por valores incluidos en el siguiente paraacutemetrobull Una matriz de cadenas que se pueden utilizar para reemplazar los comodines en la

claacuteusula where anteriorbull Una cadena que describa la ordenacioacuten de los elementos devueltos

El siguiente coacutedigo muestra un ejemplo

ContentResolver cr = getContentResolver() Devolver todas las filasCursor todasFilas = crquery(MiProveedorCONTENT_URI null null nullnull) Devolver todas columnas de las filas para las que la columna 3 tiene un valor determinado ordenadas seguacuten el valor de la columna 5String where = COL3 + = + valorString orden = COL5Cursor algunasFilas = crquery(MiProveedorCONTENT_URI null where nullorden)

327 Otras operaciones con Content Resolvers

Para realizar otro tipo de operaciones con los datos de un proveedor de contenidosharemos uso de los meacutetodos delete update e insert de un objeto ContentResolver

Con respecto a la insercioacuten la clase ContentResolver proporciona en su interfaz dosmeacutetodos diferentes insert y bulkInsert Ambos aceptan como paraacutemetro la URI delproveedor de contenidos pero mientras que el primer meacutetodo tan solo toma comoparaacutemetro un objeto de la clase ContentValues (ya vistos en la seccioacuten de bases dedatos) el segundo toma como paraacutemetro una matriz de elementos de este tipo El meacutetodoinsert devolveraacute la URI del elemento recieacuten antildeadido mientras que bulkInsert

devolveraacute el nuacutemero de filas correctamente insertadas

Obtener el Content ResolverContentResolver cr = getContentResolver()

Crear una nueva fila de valores a insertarContentValues nuevosValores = new ContentValues()

Asignar valores a cada columnanuevosValoresput(NOMBRE_COLUMNA nuevoValor)[ Repetir para cada columna ]

Uri miFilaUri = crinsert(MiProveedorCONTENT_URI nuevosValores)

Insertamos ahora una matriz de nuevas filasContentValues[] arrayValores = new ContentValues[5] PENDIENTE rellenar el array con los valores correspondientesint count = crbulkInsert(MiProveedorCONTENT_URI arrayValores)

Para el caso del borrado hacemos uso del meacutetodo delete del Content Resolver pasandocomo paraacutemetro la URI de la fila que deseamos eliminar de la fuente de datos Otraposibilidad es incluir una claacuteusula where para eliminar muacuteltiples filas Ambas teacutecnicas semuestran en el siguiente ejemplo

ContentResolver cr = getContentResolver()

Persistencia

32Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Eliminar una fila especiacuteficacrdelete(miFilaUri null null) Eliminar las primeras cinco filasString where = _id lt 5crdelete(MiProveedorCONTENT_URI where null)

Una actualizacioacuten se lleva a cabo mediante el meacutetodo update del objeto ContentResolver Este meacutetodo toma como paraacutemetro la URI del proveedor de contenidosobjetivo un objeto de la clase ContentValues que empareja nombres de columna convalores actualizados y una claacuteusula where que indica queacute filas deben ser actualizadas Elresultado de la actualizacioacuten seraacute que en cada fila en la que se cumpla la condicioacuten de laclaacuteusula where se cambiaraacuten los valores de las columnas especificados por el objeto de laclase ContentValues tambieacuten se devolveraacute el nuacutemero de filas correctamenteactualizadas A continuacioacuten se muestra un ejemplo

ContentValues nuevosValores = new ContentValues()

Utilizamos el objeto ContentValues para especificar en queacute columnas queremos que se produzca un cambio y cuaacutel debe ser el nuevo valor de dichas columnasnuevosValuesput(NOMBRE_COLUMNA nuevoValor)

Aplicamos los cambios a las primeras cinco filasString where = _id lt 5

getContentResolver()update(MiProveedorCONTENT_URI nuevosValores wherenull)

Persistencia

33Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

4 Ejercicios - Persistencia en Android proveedores de contenidos ySharedPreferences

41 Compartir datos entre actividades con Shared Preferences (075 puntos)

Descarga de las plantillas el proyecto Dni Dicho proyecto estaacute compuesto de dosactividades Formulario y Resumen El objetivo del ejercicio es conseguir que al pulsar elbotoacuten Siguiente en la actividad Formulario se muestre en la actividad Resumen cuaacutelesfueron los datos introducidos

En este ejercicio vamos a hacer uso de SharedPreferences para pasar los datos de unaactividad a la siguiente Al pulsar en Siguiente en Formulario deberemos guardar el valorde todos los campos mediante esta plataforma Al mostrarse la actividad Resumendeberemos leer estos datos tambieacuten a partir de SharedPreferences para mostrarlos porpantalla

Por uacuteltimo crea un meacutetodo que valide el DNI (debe tratarse de una secuencia de ochodiacutegitos entre 0 y 9 y una letra al final) Si se pulsa Siguiente y el DNI no tiene el formatocorrecto no se deberaacute pasar a la actividad Resumen En lugar de esto se mostraraacute unToast en pantalla con un mensaje de error

42 Actividad de preferencias (075 puntos)

En este ejercicio seguimos trabajando con el proyecto del ejercicio anterior Vamos aantildeadir un menuacute de opciones que permita escoger en queacute momento se comprobaraacute lavalidez del DNI Para ello antildeade en primer lugar un menuacute a la actividad Formulario conuna uacutenica opcioacuten cuyo nombre seraacute Opciones

Al seleccionar esta opcioacuten se deberaacute mostrar una actividad de preferencias cuyo aspectoseraacute el siguiente

Persistencia

34Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Preferencias de la aplicacioacuten Dni

El significado de las opciones seraacute el siguiente

bull En el campo con esta opcioacuten seleccionada al pulsar sobre el campo de DNI en elformulario deberaacute mostrarse un Toast indicando si la sintaxis del DNI es correcta ono

bull Al pulsar si se desactiva esta opcioacuten (que deberaacute estar activada por defecto) no seharaacute la comprobacioacuten del DNI al pulsar el botoacuten Siguiente por lo que siempre seraacuteposible pasar de la actividad Formulario a la actividad Resumen sea cual sea elformato del DNI introducido

bull El uacuteltimo check box se encontraraacute siempre deshabilitado iquestQueacute atributo debemosutilizar para conseguir esto

Crea dos variables booleanas llamadas enElCampo y alPulsar Su valor dependeraacute delestado de los checkboxes anteriores y se actualizaraacute por medio del eventoonSharedPreferenceChange Utiliza el valor de estas variables en el coacutedigo de tuactividad para que esta tenga el comportamiento indicado

43 Proveedor de contenidos propio (075 puntos)

Vamos a implementar otra forma de acceder a la base de datos de usuarios de laaplicacioacuten BaseDatos desarrollada durante los ejercicios de la sesioacuten anterior siguiendoesta vez el patroacuten de disentildeo ContentProvider de Android Seguiremos trabajando puescon dicho proyecto

bull Creamos una nueva clase llamada UsuariosProvider que herede deContentProvider Esto nos obligaraacute a sobrecargar una serie de meacutetodos abstractosAntes de implementar la query vamos a configurar el provider

bull Antildeadimos algunos campos tiacutepicos de los content provider

Persistencia

35Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public static final Uri CONTENT_URI =Uriparse(contentesuajtechandroidbasedatosusuarios)

private static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

staticuriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

TODAS_FILAS)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

UNA_FILA)

bull Vamos a acceder a la misma base de datos de usuarios utilizada en los ejercicios de lasesioacuten anterior pero no vamos a hacerlo a traveacutes del adaptador que tuvimos queimplementar sino que vamos a copiar de eacutel el coacutedigo que nos haga falta Copia loscampos que definen el nombre de la base de datos de la tabla de las columnas laversioacuten asiacute como la referencia al contexto y a la base de datos La sentenciacompilada del insert ya no va a hacer falta Inicializa los valores necesarios en elconstructor Para inicializar la referencia a la base de datos vamos a utilizar una vezmaacutes MiOpenHelper Podemos copiarlo del adaptador que ya implementamos en losejercicios de la sesioacuten anterior

bull Implementa de forma apropiada el getType para devolver un tipo MIME diferenteseguacuten si se trata de una URI de una fila o de todas las filas Para ello ayuacutedate deluriMatcher

bull Implementa el meacutetodo query Simplemente se trata de devolver el cursor queobtenemos al hacer una consulta a la base de datos SQLite Algunos de los paraacutemetrosque le pasaremos los recibimos como paraacutemetros del meacutetodo del provider Los que notengamos iraacuten con valor null

bull Aunque no tenemos todos los meacutetodos del UsuariosProvider implementadospodemos probarlo Para ello debemos registarlo en el AndroidManifestxml

ltprovider androidname=UsuariosProvider

androidauthorities=esuajtechandroidbasedatosgtltapplicationgt

ltmanifestgt

bull En la clase Main se antildeadioacute codigo para insertar una serie de valores en la base dedatos y mostrarlos en el campo de texto Manteniendo este coacutedigo vamos a antildeadir alcampo de texto el resultado obtenido con la consulta del UsuariosProvider paracomprobar que tanto el adaptador de la base de datos como el proveedor nosdevuelven el mismo resultado

44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)

Porque es la forma estaacutendar que establece Android de acceder a contenidos Ademaacutes elproveedor de contenidos nos permitiraacute notificar al ContentResolver de los cambiosocurridos Asiacute componentes en la pantalla podraacuten refrescarse de forma automaacutetica

Persistencia

36Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Utiliza el proyecto ProveedorContenidos de las plantillas Implementa la insercioacuten enel proveedor de contenidos Prueacutebala insertando algunos usuarios de ejemplo en laclase Main Implementa tambieacuten el OnClickListener del botoacuten que inserta nuevosusuarios El nombre del nuevo usuario iraacute indicado en el EditText

bull Comprueba que la insercioacuten funciona y que gracias a la siguiente liacutenea y al estarusando un proveedor de contenidos la lista se actualiza automaacuteticamente cuandoocurre alguacuten cambio sin necesidad de pedir expliacutecitamente la actualizacioacuten al pulsarel botoacuten

cursorsetNotificationUri(cr UsuariosProviderCONTENT_URI)

NotaEsto no va a funcionar si se notifica que se ha producido un cambio al insertar o borrar unelemento Para ello usamosgetContext()getContentResolver()notifyChange(UsuariosProviderCONTENT_URI null)

bull Implementa el meacutetodo delete del proveedor de contenidos Prueacutebalo en la claseMain Termina de implementar el onCreateContextMenuListener que se ejecutaraacutecada vez que se haga una pulsacioacuten larga sobre alguna entrada de la lista Compruebaque funciona (eliminando el usuario correspondiente y no otro)

Persistencia

37Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

5 Persistencia de datos en iOS Ficheros y SQLite

51 Introduccioacuten

La persistencia de datos en iOS se realiza en una pequentildea memoria flash que tienenuestro dispositivo la cual es equivalente a un disco duro de tamantildeo limitado Los datosque ahiacute se almacenen se conservaraacuten aunque el dispositivo se apague Por motivos deseguridad el SO de iOS no permite que las aplicaciones accedan directamente a lamemoria interna pero a cambio existe una API completa de acceso a la porcioacuten dememoria que le corresponde a la app que desarrollemos

En una aplicacioacuten iOS encontraremos baacutesicamente 4 modos de almacenamiento deinformacioacuten de forma persistente

bull Ficheros de propiedades (plist)bull Bases de datos embebidas SQLitebull Datos de usuario (User Defaults)bull Core Data

En esta primera sesioacuten veremos coacutemo usar los ficheros de texto (Property Lists) y elalmacenamiento en base de datos de tipo SQLite

52 Ficheros plist (Property Lists)

El almacenamiento de datos en ficheros de propiedades es uno de los maacutes usados en eldesarrollo de las aplicaciones para iOS Se usa principalmente para guardar datos deconfiguracioacuten de la aplicacioacuten u otra informacioacuten poco compleja y acceder a ella deforma sencilla Si estamos desarrollando un juego podemos usarlos para la definicioacuten delos niveles almacenar las puntuaciones del jugador etc

521 Leyendo datos desde ficheros plist

Los ficheros plist (Property Lists) tienen un formato XML aunque XCode nosproporciona una interfaz que nos permite acceder a ellos de forma muy sencilla Paraentender el funcionamiento de este tipo de ficheros vamos a realizar un ejemplo en el queprimero crearemos un fichero plist lo completaremos con datos de ejemplo y despueacutes loleeremos para mostrarlo por pantalla

Un ejemplo de un fichero plist podriacutea ser el siguiente

Persistencia

38Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Fichero plist

Antes de nada creamos un proyecto nuevo en XCode de tipo Window-based

Application al que llamaremos sesion03-ejemplo1 Para crear un fichero plistdebemos hacer click derecho sobre el directorio Supporting Files de nuestro proyectoy seleccionamos iOS gt Resource gt Property List Lo guardaremos con el nombreconfigUsuario Seguidamente ya podemos acceder al fichero recieacuten creado haciendoclick sobre el en este momento XCode nos mostraraacute un pequentildeo editor totalmente vacioen el que iremos rellenando con datos Podemos verlo tambieacuten en formato XML sihacemos click derecho sobre el y seleccionamos Open As gt Preview

Para empezar a rellenar el fichero que acabamos de crear hacemos click derecho dentrode su contenido (ahora vacio) y seleccionamos Add row Entonces nos apareceraacute unafila vaciacutea Dentro del campo Key escribimos la clave un nombre descriptivo quedeberemos utilizar maacutes adelante para acceder a los datos En nuestro caso vamos aescribir config como clave Dentro de la columna Type seleccionamosDictionary Veremos que aparece una flecha en el lado izquierdo que indica quepueden haber subniveles dentro de nuestro diccionario

Ahora antildeadimos un nuevo item que cuelgue del diccionario que acabamos de crear paraello hacemos click sobre la flecha de la izquierda esta se giraraacute hacia abajo entonceshacemos click ahora sobre el siacutembolo + (maacutes) Nos apareceraacute una nueva liacutenea que cuelgadel diccionario

En esta nueva liacutenea dentro del campo key escribimos nombre el campo type lodejamos como de tipo String ya que va a ser una cadena de texto y en la columna deValue escribimos nuestro nombre por ejemplo Javi

Ahora vamos a antildeadir otro campo nuevo en nuestra configuracioacuten para ello hacemosclick sobre el siacutembolo + y nos apareceraacute una nueva liacutenea justo al mismo nivel que laanterior y colgando del diccionario Dentro del campo de Key escribimos ciudad yen Value escribimos Alicante El campo de Type lo dejamos como String

Persistencia

39Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora dentro de nuestro fichero plist vamos a indicar los dispositivos que dispone elusuario para ello creamos una nueva linea dentro del diccionario y le pondremos comoclave dispositivos Seleccionamos el tipo Array y seguimos los mismos pasosque al crear el diccionario para ello hacemos click sobre la flecha de la izquierda ydespueacutes sobre el siacutembolo + (maacutes) Ahora nos apareceraacute una nueva fila con clave Item 0Seleccionamos el tipo String y en el campo Value iPhone

Ahora repetimos el paso 3 veces indicando como values iPad Android y

Blackberry

Una vez seguidos todos estos pasos tendremos nuestro fichero de propiedades listo Debede quedar como se muestra a continuacioacuten

Fichero plist terminado

Este fichero se almacenaraacute dentro del directorio de bundle cuando compilemos laaplicacioacuten En el caso de que queramos escribir en el debermos almacenar el ficherodentro del directorio de Documents del binario

Ahora desde nuestro coacutedigo vamos a acceder a eacutel y a mostrarlo por pantalla para elloescribimos el siguiente coacutedigo dentro del meacutetodo didFinishLaunchingWithOptions dela clase delegada

Cargamos el fichero PLISTNSString mainBundlePath = [[NSBundle mainBundle] bundlePath]NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]NSDictionary diccionario = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionario delraiz

NSString nombre = [dicConfig objectForKeynombre]

Persistencia

40Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

[selfwindow makeKeyAndVisible]return YES

Si todo ha ido correctamente deberemos obtener la siguiente salida por la consola

Lista de dispositivos

522 Escribiendo datos en ficheros plist

Partiendo del ejemplo anterior vamos a escribir ahora en el fichero para ello primerodebemos comprobar que el fichero plist se encuentre dentro del directorio de Documents

del dispositivo en el caso de que no lo esteacute (por ejemplo si es la primera vez que arrancala aplicacioacuten) debemos copiarlo a ese directorio Una vez que tenemos el fichero dentrodel directorio de documentos ya podemos escribir en el

Para realizar todo el proceso de comprobacioacuten de la existencia del fichero dentro deldirectorio de documentos copiarlo en ese directorio y leer los datos debemos de sustituirel coacutedigo que hay dentro del meacutetodo didFinishLaunchingWithOptions de la clasedelegada por este otro

AtencioacutenPara que funcione la escritura en un fichero plist este debe de estar dentro del directorio deDocuments de nuestro dispositivo iOS no en el boundle

NSDictionary diccionarioRaiz

Ruta del directorio de documentos de nuestro dispositivoNSArray paths =

NSSearchPathForDirectoriesInDomains(NSDocumentDirectoryNSUserDomainMask YES)

if ([paths count] gt 0)

Persistencia

41Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

Primero comprobamos si existe el fichero en eldirectorio de documentos

BOOL fileExists = [[NSFileManager defaultManager]fileExistsAtPathdocumentsFilename]if (fileExists)

NSLog(Fichero encontrado en directorio de documentosOK

documentsFilename)

Si existe ya cargamos los datos desde aquidirectamente

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

else

NSLog(No se encuentra el fichero configUsuarioplisten

el directorio de documentos-gtLo creamos)

Si no existe primero cargamos el fichero PLISTdesde el Boundle

NSString mainBundlePath = [[NSBundle mainBundle]bundlePath]

NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Y despues escribimos los datos cargados (eldiccionario completo)

a un fichero nuevo de la carpeta de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

NSLog(plist de configUsuario creado)

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionariodel raiz

NSString nombre = [dicConfig objectForKeynombre]NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

Persistencia

42Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Por ultimo liberamos la memoria del diccionario raiz[diccionarioRaiz release]

[selfwindow makeKeyAndVisible]return YES

Para comprobar el funcionamiento arrancamos la aplicacioacuten y veremos que la primera vezse crearaacute el fichero dentro de la carpeta de Documents de nuestro dispositivo y en lassiguientes ya no se crearaacute y simplemente lo leeraacute desde la carpeta de Documents

Ahora que ya tenemos la gestioacuten de ficheros baacutesica completada vamos a escribir sobre elalguacuten dato Para ello simplemente escribimos el siguiente fragmento de coacutedigo justo antesde la liacutenea [selfwindow makeKeyAndVisible]

Cambiamos el nombre y lo guardamos en el fichero Ruta del directorio de documentos de nuestro dispositivo

if ([paths count] gt 0)

Cargamos el fichero desde el directorio dedocumentos del dispositivo

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cambiamos el valor del nombre en el diccionario[dicConfig setValueOtro nombre forKeynombre]

Escribimos todo el diccionario de nuevo al fichero del directorio de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

Por ultimo liberamos el diccionario de lamemoria

[diccionarioRaiz release]

Lo que hemos hecho en el coacutedigo anterior es cargar todo el diccionario de nuevo desde eldirectorio de documentos modificar los datos que queremos cambiar o incluso antildeadir ydespueacutes guardar el diccionario en el fichero

Volvemos a arrancar la aplicacioacuten y veremos que ha cambiado el campo de nombre

Nota

Persistencia

43Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

La lectura y escritura de datos en un fichero plist se debe de hacer de una soacutela vez nunca porpartes Siempre se debe de cargar o escribir un diccionario NSDictionary como elementoprincipal del fichero

53 SQLite

SQLite es una base de datos relacional ligera con la que se puede trabajar de formasencilla mediante sentencias SQL La base de datos se almacenaraacute en un fichero dentrode nuestra aplicacioacuten Este meacutetodo de persistencia puede ser el adecuado cuando los datosa almacenar esteacuten en un formato de tablas filas y columnas y se necesite que los datossean accesibles de forma raacutepida

531 Herramientas disponibles

Para explicar el funcionamiento de las librerias de SQLite de Cocoa Touch vamos a seguirun ejemplo completo Comenzamos creando la base de datos para ello podemos utilizaruno de los frontends que facilitan la gestioacuten del SQLite uno bastante sencillo yrecomendado es el SQLite Database Browser y otro es fmdb

Nosotros utilizaremos el primero (SQLite Database Browser) Abrimos la aplicacioacuten yhacemos click sobre el botoacuten de crear nueva base de datos que llamaremospersonasDBsqlite Seguidamente creamos las tablas para simplificar en nuestroejemplo crearemos una soacutela tabla llamada personas Esta tabla tendraacute las siguientescolumnas id (NUMERIC) nombre (TEXT) apellidos (TEXT) dni (TEXT) edad

(NUMERIC) y localidad (TEXT)

Persistencia

44Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Estructura de la Base de Datos

Una vez creada la tabla pasamos a insertar datos dentro de ella para ello abrimos lapestantildea de Browse Data y hacemos click en New Record Cuando tengamos al menos 3registros metidos dentro de la tabla ya podemos guardar la base de datos Hacemos clicksobre guardar le ponemos el nombre que queramos y elegimos cualquier directorio delMAC

Datos de la Base de Datos

532 Lectura de datos

Una vez que tenemos la base de datos SQLite creada ya podemos empezar a escribir elcoacutedigo para leer y mostrar los datos dentro de nuestra aplicacioacuten

Primero empezamos creando un proyecto nuevo en XCode usando la plantilla basada envistas View-based application y lo llamaremos sesion03-ejemplo2 Ahora antildeadimosel framework de sqlite a nuestro proyecto para ello hacemos click sobre el raiz delproyecto y seleccionamos la pestantildea Build Phases Desplegamos el listado de Link

Binary With Libraries y hacemos click sobre el botoacuten (+) El el listadoseleccionamos el framework libsqlite3dylib y hacemos click en Add

Persistencia

45Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Antildeadiendo el framework libsqlite3dylib

Ahora antildeadimos a nuestro proyecto la base de datos que hemos creado anteriormentepara ello arrastramos el fichero personasDBsqlite a nuestro directorio Supporting

Files Asegurate de seleccionar la opcioacuten de Copy items to destination groups

folder (if needed) para que se copie el fichero dentro de la carpeta del proyecto

Para acelerar el proceso de lectura de la base de datos y ahorrar memoria vamos a cargarprimero todos los datos en objetos para ello vamos a crear una clase que se encargue dealmacenar los datos Hacemos click derecho sobre el raiz del proyecto New file gtObjective-C class seleccionamos NSObject como clase principal y le damos a NextEscribimos como nombre de la clase Persona y hacemos click en save

Personah

imports iniciales

interface Persona NSObject int _uIdNSString _nombreNSString _apellidos

Persistencia

46Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSInteger _edadNSString _localidad

property (nonatomic assign) int uIdproperty (nonatomic copy) NSString nombreproperty (nonatomic copy) NSString apellidosproperty (nonatomic) NSInteger edadproperty (nonatomic copy) NSString localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidos edad(NSInteger)edadlocalidad(NSString )localidad

end

Personam

import Personah

implementation Persona

synthesize uId = _uIdsynthesize nombre = _nombresynthesize apellidos = _apellidossynthesize edad = _edadsynthesize localidad = _localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidosedad(NSInteger)edad localidad(NSString )localidad

if ((self = [super init])) selfuId = uIdselfnombre = nombreselfapellidos = apellidosselfedad = edadselflocalidad = localidad

return self

- (void) dealloc selfnombre = nilselfapellidos = nilselflocalidad = nil[super dealloc]

end

Una vez creada la clase Persona vamos a crear la clase encargada de manejar la basede datos sqlite3 Otra opcioacuten seriacutea cargar todos los datos directamente desde la clasedelegada al arrancar la aplicacioacuten pero no es recomendable ya que si en alguacuten momentotenemos que cambiar de motor de base de datos lo tendremos maacutes complicado

Persistencia

47Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creamos entonces una clase dentro de nuestro proyecto llamada BDPersistencia queherede de NSObject y escribimos el siguiente coacutedigo

BDPersistenciah

imports iniciales

interface BDPersistencia NSObject sqlite3 _database

+ (BDPersistencia)database- (NSArray )arrayPersonas

end

BDPersistenciam

import BDPersistenciahimport Personah

implementation BDPersistencia

static BDPersistencia _database

+ (BDPersistencia)database if (_database == nil)

_database = [[BDPersistencia alloc] init]return _database

- (id)init if ((self = [super init]))

NSString sqLiteDb = [[NSBundle mainBundle]pathForResourcepersonasDB

ofTypesqlite]

if (sqlite3_open([sqLiteDb UTF8String]amp_database) = SQLITE_OK)

NSLog(Fallo al abrir la BD)

return self

- (void)dealloc sqlite3_close(_database)[super dealloc]

- (NSArray )arrayPersonas

NSMutableArray arraySalida = [[[NSMutableArray alloc]init]

autorelease]

Persistencia

48Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString query = SELECT id nombre apellidoslocalidad edad

FROM personas ORDER BY apellidos DESC

sqlite3_stmt statementif (sqlite3_prepare_v2(_database [query UTF8String]

-1ampstatement nil)

== SQLITE_OK) while (sqlite3_step(statement) == SQLITE_ROW)

int uniqueId = sqlite3_column_int(statement0)

char nombreChar = (char )sqlite3_column_text(statement 1)

char apellidosChar = (char )sqlite3_column_text(statement 2)

char localidadChar = (char )sqlite3_column_text(statement 3)

int edad = sqlite3_column_int(statement 4)

NSString nombre = [[NSString alloc]initWithUTF8StringnombreChar]NSString apellidos = [[NSString alloc]initWithUTF8StringapellidosChar]NSString localidad = [[NSString alloc]initWithUTF8StringlocalidadChar]Persona persona = [[Persona alloc]initWithUiduniqueId nombrenombreapellidosapellidos edadedad

localidadlocalidad]

[arraySalida addObjectpersona][nombre release][apellidos release][localidad release][persona release]

sqlite3_finalize(statement)

return arraySalida

end

Con esto ya podemos leer de nuestra base de datos las personas Para ver que funcionatodo a la perfeccioacuten escribimos el siguiente coacutedigo dentro de la clase delegada en elmeacutetodo applicationDidFinishLaunching Antes debemos incluir en la parte superiorlas clases creadas

import BDPersistenciahimport Personah

NSArray personas = [BDPersistenciadatabase]arrayPersonas

for (Persona persona in personas) NSLog(d d personauId

personanombrepersonaapellidos personalocalidad personaedad)

Persistencia

49Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Si ejecutamos el proyecto obtendremos la siguiente salida por consola

Listado de personas

533 Mostrando los datos en una tabla

Una vez que tenemos el coacutedigo para obtener todas las filas de la tabla de personas de labase de datos queda lo maacutes faacutecil mostrarlas dentro de una vista de la aplicacioacuten En estecaso utilizaremos la vista de tabla UITableView

Comenzamos creando una nueva vista en nuestro proyecto para ello hacemos clickderecho sobre el raiz y seleccionamos New File gt UIVIewController SubclassAsegurate de marcar Subclass of UITableViewController y la opcoacuten de With

XIB for user interface Hacemos click en Next y guardamos el fichero comoPersonasViewController por ejemplo

Ahora abrimos el fichero PersonasViewControllerh y antildeadimos un NSArray queseraacute el que almacene las personas de la base de datos

imports iniciales

interface PersonasViewController UITableViewController

NSArray _listadoPersonas

property (nonatomic retain) NSArray listadoPersonas

end

Ahora abrimos el fichero PersonasViewControllerm antildeadimos el syntetizeimportamos los ficheros de gestioacuten de la base de datos la clase Persona y todo el coacutedigobaacutesico para la gestioacuten de la vista de la tabla

import PersonasViewControllerhimport Personahimport BDPersistenciah

implementation PersonasViewController

synthesize listadoPersonas = _listadoPersonas

Persistencia

50Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

- (id)initWithStyle(UITableViewStyle)style

self = [super initWithStylestyle]if (self)

Custom initializationreturn self

- (void)dealloc

[super dealloc]

selflistadoPersonas = nil

- (void)didReceiveMemoryWarning

Releases the view if it doesnt have a superview[super didReceiveMemoryWarning]

Release any cached data images etc that arent inuse

pragma mark - View lifecycle

- (void)viewDidLoad

[super viewDidLoad]

selftitle = Listado de personas

selflistadoPersonas = [BDPersistenciadatabase]arrayPersonas

- (void)viewDidUnload

[super viewDidUnload] Release any retained subviews of the main view eg selfmyOutlet = nil

- (void)viewWillAppear(BOOL)animated

[super viewWillAppearanimated]

- (void)viewDidAppear(BOOL)animated

[super viewDidAppearanimated]

- (void)viewWillDisappear(BOOL)animated

[super viewWillDisappearanimated]

- (void)viewDidDisappear(BOOL)animated

[super viewDidDisappearanimated]

- (BOOL)shouldAutorotateToInterfaceOrientation

Persistencia

51Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

(UIInterfaceOrientation)interfaceOrientation

Return YES for supported orientationsreturn (interfaceOrientation ==

UIInterfaceOrientationPortrait)

pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView(UITableView)tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection

(NSInteger)section

Return the number of rows in the sectionreturn [selflistadoPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath

(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]if (cell == nil)

cell = [[[UITableViewCell alloc]initWithStyleUITableViewCellStyleSubtitle

reuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona persona = (Persona )[selflistadoPersonas

objectAtIndexindexPathrow]celltextLabeltext = [NSString stringWithFormat

(d) personanombrepersonaapellidos personaedad]celldetailTextLabeltext = personalocalidad

return cell

pragma mark - Table view delegate

- (void)tableView(UITableView )tableViewdidSelectRowAtIndexPath

(NSIndexPath )indexPath

Navigation logic may go here Create and pushanother view controller

end

Persistencia

52Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora soacutelo nos queda incorporar la vista que acabamos de crear dentro de nuestraaplicacioacuten Para ello vamos a llamarla desde el Delegate y a meterla dentro de unNavigation Controller Primero debemos de antildeadir un Outlet en la clasedelegada hacia un Navigation Controller

imports iniciales

interface sesion03_ejemplo1AppDelegate NSObjectltUIApplicationDelegategt

UIWindow _windowUINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet

UINavigationController navController

end

Ahora hacemos click sobre la vista MainWindowxib y arrastramos un Navigation

Controller desde el listado de la columna de la derecha hacia la columna de laizquierda donde pone Objects Nos apareceraacute el Navigation Controller dibujadoen pantalla Ahora desplegamos el Navigation Controller y hacemos click sobre elView Controller que hay dentro Nos vamos a la pestantildea de Identity Inspector

de la columna de la derecha y escribimos dentro de Class el nombre de la vista dellistado de personas PersonasViewController

Finalmente hacemos click sobre el objeto Delegate vamos a la pestantildea de Show

Connections Inspector y arrastramos desde navController hasta el objetoNavigation Controller de la columna de la izquierda Ya tenemos los controladoresy las vistas conectadas

Una vez hecho esto nos queda indicar dentro de nuestro coacutedigo que la aplicacioacuten arranquecon el Navigation Controller para ello escribimos lo siguiente al final del meacutetododidFinishLaunchingWithOptions de la implementacioacuten de la clase delegada

selfwindowrootViewController = selfnavController[selfwindow makeKeyAndVisible]return YES

Ya estaacute listo todo y preparado para compilar Si ejecutamos el proyecto veremos que nosaparece el listado de personas en la vista de tabla

Persistencia

53Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios

61 Creando y leyendo un fichero plist (15 puntos)

En este primer ejercicio vamos a crear nuestra primera aplicacioacuten usando el meacutetodo depersistencia de ficheros La aplicacioacuten mostraraacute en una vista de tabla TableView unlistado de series en la que si seleccionamos una veremos su informacioacuten maacutes importanteen otra vista Comenzaremos creando un fichero de propiedades (plist) siguiendo unaestructura determinada y posteriormente mostraremos en una vista de tabla todos susdatos iexclComenzamos

1) Crear un proyecto usando la plantilla Master-Detail application Lo llamaremosejercicio-plist-ios como identificador de empresa escribiremos esuajtech y como prefijode clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremos UseAutomatic Reference Counting para olvidarnos de la gestioacuten de memoria El resto deopciones las dejaremos sin marcar

2) Crear un archivo de propiedades (plist) con el nombre de series

3) Editar el archivo plist creando la siguiente estructura de datos (5 series miacutenimo)

Plist series

4) Rellenar el archivo plist con datos de series reales

5) Escribir el coacutedigo necesario en UAMasterViewControllerm que recorra el archivoplist que acabamos de crear y muestre por consola los titulos de las series

6) Mostrar en la tabla que hay implementada los tiacutetulos de la serie Para ello deberemosde completar todos los meacutetodos del protocolo del TableView

7) Ejecutar la aplicacioacuten y comprobar que funciona correctamente

Persistencia

54Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

62 Implementar vista de detalle de la serie (05 puntos)

Dentro del proyecto anterior (no hace falta crear uno nuevo) vamos a mostrar toda lainformacioacuten adicional de cada una de las series en una vista de detalle Cuandoseleccionemos una fila de la tabla nos deberaacute aparecer una nueva vista con el resto deinformacioacuten (antildeo director principal sinopsis y duracioacuten)

63 Uso baacutesico de una base de datos SQLite (1 punto)

En este ejercicio vamos a crear la misma aplicacioacuten que en el ejercicio anterior perousando el meacutetodo de persistencia de SQLite Para completar el ejercicio deberemos seguirlos siguientes pasos

1) Crear un proyecto usando la plantilla Master-Detail application lo llamaremosejercicio-sqlite-ios como identificador de empresa escribiremos esuajtech y comoprefijo de clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremosUse Automatic Reference Counting para olvidarnos de la gestioacuten de memoria El restode opciones las dejaremos sin marcar

2) Nos descargamos e instalamos el programa SQLite Database Browser desde estadireccioacuten

3) Disentildear la estructura de la base de datos usando SQLite Database Browser tal y comose muestra en la imagen siguiente

Sqlite series

4) Insertar al menos 5 series en la base de datos usando el programa SQLite DatabaseBrowser

5) Cargar todos los datos de la base de datos desde el meacutetododidFinishLaunchingWithOptions de la clase UAAppDelegate Mostrar por consola losdatos cargados

6) Mostrar los datos cargados en el paso anterior en la vista de tabla UITableView quehay creada en la clase UAMasterViewController

Persistencia

55Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7) Arrancar la aplicacioacuten y comprobar que se muestran las series en la tabla de formacorrecta

Persistencia

56Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7 Persistencia de datos en iOS User Defaults y Core Data

71 Introduccioacuten

En esta sesioacuten veremos como utilizar dos nuevos meacutetodos de persistencia en iOS ambosmuy interesantes y usados muy frecuentemente

bull User Defaults Muy uacutetil para almacenar datos simples y de uso repetido dentro denuestras aplicaciones iOS

bull Core Data Meacutetodo avanzado de almacenamiento de objetos enteros dentro de lamemoria del dispositivo iOS Muy uacutetil para almacenar datos complejos conrelaciones etc

72 User Defaults

Este meacutetodo es muy uacutetil cuando queremos almacenar pequentildeas cantidades de datos comopuntuaciones informacioacuten de usuario el estado de nuestra aplicacioacuten configuracionesetc

Este meacutetodo es el maacutes raacutepido de usar ya que no requiere de una base de datos ni ficherosni ninguna otra capa intermedia Los datos se almacenan directamente en la memoria deldispositivo dentro de un objeto de la clase NSUserDefaults

Para aprender a usar este meacutetodo de almacenamiento de datos vamos a crear unaaplicacioacuten muy simple que lea un nombre y una edad de pantalla y las guarde en unobjeto NSUserDefaults Para ello seguimos los siguientes pasos

bull 1) Comenzamos creando un nuevo proyecto llamado sesion04-ejemplo1 dentrode XCode

bull 2) Abrimos la vista sesion04_ejemplo1ViewController y arrastramos dos TextField un botoacuten y dos labels quedando la vista de la siguiente manera

Persistencia

57Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista de la aplicacioacuten

bull 3) Abrimos el fichero sesion04_ejemplo1ViewControllerh y definimos el Outletdel evento onclick del botoacuten El fichero quedaraacute de la siguiente manera

imports iniciales

interface sesion04_ejemplo1ViewController UIViewController UITextField tfNombreUITextField tfEdad

property(nonatomic retain) IBOutlet UITextField tfNombreproperty(nonatomic retain) IBOutlet UITextField tfEdad

-(IBAction) guardaDatos

end

bull 4) Ahora implementamos el coacutedigo para el meacutetodo del botoacuten dentro desesion04_ejemplo1ViewControllerm

synthesize tfEdadsynthesize tfNombre

-(IBAction) guardaDatos NSLog(Guardamos los datos)

Persistencia

58Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creamos el objeto NSUserDefaultsNSUserDefaults datos = [NSUserDefaults standardUserDefaults]

guardamos el nombre (NSString)[datos setObjecttfNombretext forKeynombre]

guardamos la edad (Integer)[datos setInteger[tfEdadtext integerValue] forKeyedad]

almacenamos los datos en la memoria[datos synchronize]

bull 5) Tenemos que enlazar el botoacuten con el evento que hemos programado en el pasoanterior para ello abrimos la vista sesion04_ejemplo1ViewControllerxib y conla tecla ctlr apretada arrastramos desde el botoacuten hasta Files Owner (columna de laizquierda parte superior) y seleccionamos el meacutetodo que hemos creado(guardaDatos) De esta forma ya tenemos conectado el botoacuten

bull 6) Ahora hacemos lo mismo con los Text Field Tenemos que conectarlos alcontrolador para ello seleccionamos el objeto Files Owner y abrimos la pestantildeade conexiones (la que estaacute maacutes a la derecha en la columna de la derecha) Hacemosclick en tfEdad y arrastramos hasta el Text Field de edad Hacemos lo mismo para eltext field de nombre Ya tenemos todos los elementos de la vista conectados

Conexioacuten de Outlets

bull 7) Arrancamos la aplicacioacuten y la probamos

Persistencia

59Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Aplicacioacuten

bull 8) Ahora vamos a hacer que la aplicacioacuten cargue los datos que se han guardado alarrancar la vista para ello simplemente tenemos que escribir el siguiente coacutedigo en elmeacutetodo (void)viewDidLoad de la clase sesion04_ejemplo1ViewControllerm

- (void)viewDidLoad

[super viewDidLoad]

NSUserDefaults datos = [NSUserDefaults standardUserDefaults]

obtenemos un NSStringNSString nombre = [datos stringForKeynombre]

obtenemos un NSIntegerNSInteger edad = [datos integerForKeyedad]

if (nombre=nil ampamp edad=0)NSLog(Nombre cargado edad dnombreedad)

else

NSLog(Sin datos)

bull 9) Ya podemos arrancar de nuevo la aplicacioacuten y veremos que si escribimos cualquier

Persistencia

60Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

texto en los Text Fields estos se almacenaraacuten dentro del objeto NSUserDefaults Alapagar la aplicacioacuten y volver a arrancarla estos datos se cargaraacuten (apareceraacuten enconsola)

Consola

NotaLos datos permaneceraacuten en nuestro dispositivo hasta que se elimine la aplicacioacuten en ese casotodos los datos guardados en NSUserDefaults se borraraacuten

bull 10) Probar eliminar la aplicacioacuten del dispositivosimulador y volver arrancarComprobamos que los datos se han borrado

Consola

73 Core Data

De todas las formas de persistencia de datos que existen en iOS Core Data es la maacutesapropiada para usar en caso de manejar datos no triviales con relaciones algo maacutescomplejas entre ellos El uso de Core Data dentro de nuestra aplicacioacuten optimiza almaacuteximo el consumo de memoria mejora el tiempo de respuesta y reduce la cantidad decoacutedigo redundante a escribir

Core Data utiliza de fondo el meacutetodo de almacenamiento SQLite visto en la sesioacuten 3 deeste moacutedulo por tanto para explicar el funcionamiento de este meacutetodo de persistenciavamos a utilizar el mismo modelo de datos que usamos en esa sesioacuten Persona -gtDetallePersona

El el siguiente ejemplo haremos la misma aplicacioacuten que hicimos con SQLite perousando este nuevo meacutetodo de persistencia

731 Creando el proyecto

Para empezar tenemos que crearnos un nuevo proyecto en XCode Para ello abrimosXCode y seleccionamos crear una nueva aplicacioacuten basada en ventanas (Window-basedapplication) Hacemos click en Next escribimos como nombre del proyectosesion04-ejemplo2 y seleccionamos Use Core Data

Persistencia

61Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creando un proyecto nuevo

Con esto ya tenemos la estructura baacutesica para empezar a programar Antes de nadahechamos un vistazo raacutepido a toda la estructura de directorios creada y vemos que dentrodel directorio principal tenemos un archivo que se llamasesion04_ejemplo2xcdatamodeld Al hacer click sobre el archivo se nos abre uneditor especial que en principio estaacute vacio y que maacutes adelante utilizaremos para disentildearnuestro modelo de datos

Ahora hechamos un vistazo a la clase delegada sesion04_ejemplo2AppDelegatemvemos que hay unos cuantos meacutetodos nuevos que serviraacuten para configurar el Core DataVamos a explicar los maacutes importantes

bull - (NSManagedObjectModel )managedObjectModel NSManagedObjectModel esla clase que contiene la definicioacuten de cada uno de los objetos o entidades quealmacenamos en la base de datos Normalmente este meacutetodo no lo utilizaremos ya quenosotros vamos a utilizar el editor que hemos visto antes con el que podremos crearnuevas entidades crear sus atributos y relaciones

bull - (NSPersistentStoreCoordinator )persistentStoreCoordinator Aquiacute esdonde configuramos los nombres y las ubicaciones de las bases de datos que se usaraacutenpara almacenar los objetos Cuando un managed object necesite guardar algo pasaraacutepor este meacutetodo

bull - (NSManagedObjectContext )managedObjectContext Esta claseNSManagedObjectContext seraacute la maacutes usada con diferencia de las tres y por tanto la

Persistencia

62Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

maacutes importante La utilizaremos baacutesicamente para obtener objetos insertarlos oborrarlos

732 Definiendo el modelo de datos

En el ejemplo que hicimos en la sesioacuten anterior de SQLite creamos dos tablas una paralos datos baacutesicos de la persona y otra para el detalle Ahora haremos lo mismo perousando Core Data la diferencia principal es que mientras que con SQLite obteniamos soacutelolos datos que necesitaacutebamos de la base de datos aquiacute obtenemos el objeto completo

Para definir nuestro modelo de datos basado en dos objetos (Persona y DetallePersona)abrimos el editor visual haciendo click sobre el ficherosesion04_ejemplo2xcdatamodeld Empezaremos creando una nueva entidad para ellohacemos click sobre el botoacuten Add Entity (+) que se encuentra en la parte inferior dela pantalla A la nueva entidad le pondremos como nombre Persona

Dentro de la seccioacuten de Attributes hacemos click sobre (+) y creamos un nuevoatributo para nuestra entidad le llamamos nombre y le asignamos como tipo StringRealizamos este mismo paso 2 veces maacutes para antildeadir apellidos y edad este uacutetlimode tipo Integer 16

Atributos Persona

Ahora creamos la entidad de DetallePersona de la misma manera que la anterior perocreando los atributos localidad pais direccion cp y telefono Todasde tipo String

Atributos DetallePersona

Finalmente nos queda relacionar de alguacuten modo las dos entidades para ello antildeadimos una

Persistencia

63Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

relacioacuten dentro de la entidad de Persona hacia DetallePersona simplementehaciendo click sobre el botoacuten (+) de la seccioacuten de Relationships A la relacioacuten leponemos como nombre detalle seleccionamos como destino la entidadDetallePersona y seraacute No inverse Este tipo de relacioacuten es uno a uno Por dentroCore Data realizaraacute crearaacute una clave ajena en la tabla de persona que apunte aDetallePersona pero eso a nosotros no nos interesa

Vista general modelo de datos (1)

Apple recomienda que siempre que hagamos una relacioacuten de una entidad a otra hagamostambieacuten la relacioacuten inversa por lo tanto hacemos justo lo anterior pero al reveacutes (deDetallePersona a Persona) con la diferencia que en la columna de Inverse

seleccionamos datosPersona

Ya tenemos las entidades y las relaciones creadas Si hacemos click en el botoacuten deEditor Style en la parte de abajo a la derecha del editor podemos ver de una formamaacutes clara la estructura que hemos disentildeado

Persistencia

64Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista general modelo de datos (2)

733 Probando el modelo

Una vez que tenemos el modelo creado pasamos a probarlo para comprobar que estaacute todocorrecto para ello abrimos la clase delegada (sesion04_ejemplo2AppDelegatem) yantildeadimos el siguiente fragmento de coacutedigo arriba del meacutetodoapplicationDidFinishLaunching

NSManagedObjectContext context = [self managedObjectContext]NSManagedObject persona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

[persona setValueJuan forKeynombre][persona setValueMartinez forKeyapellidos][persona setValue[NSNumber numberWithInt28] forKeyedad]

NSManagedObject detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersonainManagedObjectContextcontext]

[detallePersona setValueCalle Falsa 123 forKeydireccion][detallePersona setValueAlicante forKeylocalidad][detallePersona setValueEspantildea forKeypais][detallePersona setValue965656565 forKeytelefono]

[detallePersona setValuepersona forKeydatosPersona][persona setValuedetallePersona forKeydetalle]

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Persistencia

65Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Con este coacutedigo hemos creado un objeto Persona enlazado con otro objetoDetallePersona iexclComo puedes ver no hemos necesitado nada de coacutedigo SQLPrimero creamos un objeto del tipo NSManagedObjectContext que almacena el conextode Core Data Despueacutes creamos el objeto Persona en siacute con NSManagedObject A esteobjeto le pondremos los valores para cada una de las propiedades definidas en el modeloHacemos lo mismo con DetallePersona Finalmente relacionamos el objetoPersona con el objeto DetallePersona

Compilamos el proyecto comprobamos que todo funciona bien y no salen errores

Para comprobar que los datos se han almacenado correctamente en nuestro dispositivo losmostramos en la consola de la siguiente manera

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

intValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(------------------)

[fetchRequest release]

El coacutedigo de arriba lo escribiremos dentro del meacutetododidFinishLaunchingWithOptions de la clase delegada Lo que hace este coacutedigo esrecorrer todos los objetos almacenados en memoria y mostrarlos por consola Siarrancamos la aplicacioacuten veremos que se muestran todos los datos de los objetosalmacenados Cada vez que arranquemos la aplicacioacuten se mostraraacuten maacutes objetos ya quetambieacuten los creamos

Para listar los objetos usamos la clase NSFetchRequest Esta clase es equivalente aejecutar una sentencia Select de SQL Al objeto NSFetchRequest le asignamos laentidad que queremos listar (SELECT FROM ) en nuestro caso seraacute PersonaEl listado de los objetos se obtiene mediante el meacutetodo executeFetchRequest de laclase NSManagedObjectContext que hemos definido en el bloque anterior de coacutedigoEste meacutetodo devolveraacute un NSArray con todos los objetos NSManagedObject

Persistencia

66Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Salida de consola

NotaSi queremos ver queacute sentencias SQL estaacute ejecutando Core Data por debajo tenemos que activarel flag de debug para ello seleccionamos la opcioacuten Product gt Edit Scheme Enla columna de la izquierda seleccionamos el esquema que estamos utilizando para Debug y en laparte de la derecha en el cuadro de Arguments Passed on Launch antildeadimos 2paraacutemetros -comappleCoreDataSQLDebug y 1

Ahora arrancamos de nuevo la aplicacioacuten y veremos que en la consola apareceraacuten lassentencias SQL utilizadas por Core Data

()

INSERT INTO ZDETALLEPERSONA(Z_PK Z_ENT Z_OPT ZDATOSPERSONA ZTELEFONOZLOCALIDAD ZDIRECCION ZPAIS)VALUES( )CoreData sql SELECT 0 t0Z_PK t0Z_OPT t0ZTELEFONO t0ZLOCALIDADt0ZDIRECCION t0ZPAIS t0ZDATOSPERSONAFROM ZDETALLEPERSONA t0 WHERE t0Z_PK =

()

734 Generando los modelos automaacuteticamente

Generar los modelos a mano directamente desde el editor no es lo maacutes recomendado yaque podemos cometer errores de escritura errores de tipo etc La mejor manera dehacerlo es creando un archivo para cada entidad Esto se puede hacer desde 0 peroXCode tiene un generador de clases que es muy faacutecil de usar y ahorremos bastantetiempo iexclVamos a usarlo

Hacemos click sobre el raiz de nuestro proyecto y seleccionamos New File En lacolumna de la izquierda seleccionamos iOS gt Core Data y en el cuadro de laderecha NSManagedObject subclass Click en next En la siguiente pantallaseleccionamos nuestro modelo de datos sesion04_ejemplo2 y click en next Ahoranos aseguramos de seleccionar las dos entidades que hemos creado anteriormentePersona y DetallePersona click en next Seleccionamos el raiz de nuestroproyecto para guardar los ficheros que se generen y aceptamos

Persistencia

67Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ventana de seleccioacuten de entidad

Ahora veremos que xsource ha creado automaacuteticamente 4 ficheros nuevos dentro denuestro proyecto Personamh y DetallePersonamh con todos los atributosespecificados Estas nuevas clases que heredan de NSManagedObject seraacuten las queutilicemos a partir de ahora

Cambiamos el coacutedigo de la clase delegada por el siguiente

NSManagedObjectContext context = [self managedObjectContext]Persona datosPersona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

datosPersonanombre = JuandatosPersonaapellidos = MartinezdatosPersonaedad = [NSNumber numberWithInt28]

DetallePersona detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersona

inManagedObjectContextcontext]detallePersonadireccion = Calle Falsa 123

Persistencia

68Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

detallePersonalocalidad = AlicantedetallePersonapais = EspantildeadetallePersonatelefono = 965656565

detallePersonadatosPersona = datosPersonadatosPersonadetalle = detallePersona

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Listamos los datosNSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

integerValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(-----------------)[fetchRequest release]

Ejecutamos el proyecto y comprobamos que en la consola aparece lo mismo que antespero ahora tenemos el coacutedigo bastante maacutes claro

735 Creando la vista de tabla

Ahora vamos a mostrar los objetos que antes hemos listado por consola en una vista detabla UITableView Para ello creamos un UITableViewController

Click en New file gt UIViewController subclass En la siguiente pantallanos aseguramos de seleccionar Subclass of UITableViewController de esta formanos apareceraacuten ya todos los meacutetodos de la tabla creados por defecto Guardamos elarchivo con el nombre que queramos por ejemplo PersonasViewController

Abrimos ahora PersonasViewControllerh y antildeadimos 2 atributos a la clase

bull Un NSArray que se seraacute el listado de personas (objetos de la clase Persona)bull Una referencia a NSManagedObjectContext

El fichero PersonasViewControllerh quedaraacute de la siguiente manera

Persistencia

69Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

imports iniciales

interface PersonasViewController UITableViewController NSArray _listaPersonasNSManagedObjectContext _context

property (nonatomic retain) NSArray listaPersonasproperty (nonatomic retain) NSManagedObjectContext context

end

Ahora en el m importamos en el encabezado las clases Personah yDetallePersonah y en el meacutetodo viewDidLoad creamos el array listaPersonas contodos los objetos Persona de forma similar a cuando lo hicimos en el apartado anterior

import PersonasViewControllerh

import Personahimport DetallePersonah

implementation PersonasViewController

synthesize listaPersonas = _listaPersonassynthesize context = _context

En viewDidLoad

- (void)viewDidLoad

[super viewDidLoad]

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContext_context]

[fetchRequest setEntityentity]NSError errorselflistaPersonas = [_context executeFetchRequestfetchRequest

erroramperror]selftitle = Listado Personas[fetchRequest release]

Ahora completamos el resto de meacutetodos heredados de UITableViewController de lamisma forma que hicimos en sesiones anteriores

- (NSInteger)numberOfSectionsInTableView(UITableView )tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection(NSInteger)section

Persistencia

70Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Return the number of rows in the sectionreturn [_listaPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]

if (cell == nil) cell = [[[UITableViewCell alloc]

initWithStyleUITableViewCellStyleSubtitlereuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona p = (Persona )[_listaPersonas objectAtIndexindexPathrow]celltextLabeltext = pnombrecelldetailTextLabeltext = [NSString stringWithFormatd antildeos

[pedad intValue]]

return cell

Ahora para poder mostrar la tabla dentro de nuestra aplicacioacuten debemos asignarla dentrode la clase delegada para ello abrimos la vista MainWindowxib y arrastramos unacontroladora de navegacioacuten Navigation Controller a la lista de objetos de laizquierda La desplegamos y dentro de View Controller en la pestantildea de la derechade Identity Inspector escribimos el nombre de nuestra clase que tiene la vista de latabla PersonasViewController

Pantalla del disentildeador

Ahora abrimos el fichero sesion04_ejemplo2AppDelegateh y creamos un

Persistencia

71Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

UINavigationController dentro de la declaracioacuten de variables dejando el ficherocomo sigue

imports iniciales

interface sesion04_ejemplo2AppDelegate NSObject ltUIApplicationDelegategt

UINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet UINavigationControllernavController

property (nonatomic retain readonly) NSManagedObjectContextmanagedObjectContextproperty (nonatomic retain readonly) NSManagedObjectModelmanagedObjectModelproperty (nonatomic retain readonly) NSPersistentStoreCoordinatorpersistentStoreCoordinator

- (void)saveContext- (NSURL )applicationDocumentsDirectory

end

Dentro del m importamos la clase PersonasViewControllerh y completamos elsynthetize

synthesize navController = _navController

Y por uacuteltimo justo antes de la llamada al meacutetodo makeKeyAndVisible escribimos elsiguiente coacutedigo para asignar el contexto de la clase delegada alPersonasViewController

PersonasViewController root = (PersonasViewController ) [_navControllertopViewController]

rootcontext = [self managedObjectContext][selfwindow addSubview_navControllerview]

Ahora compilamos y ejecutamos nuestro proyecto y si todo ha ido bien deberiacutea de salirla tabla con los datos de las personas

Persistencia

72Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Listado de personas

736 Migraciones de bases de datos con Core Data

Una vez que tenemos disentildeada nuestra base de datos inicial iquestqueacute pasariacutea si queremosmodificarla para antildeadirle un campo maacutes a una tabla iquestQueacute haraacute Core Data para realizar lamigracioacuten de la versioacuten anterior a la actual

Siguiendo con el ejemplo anterior si despueacutes de ejecutar la aplicacioacuten en nuestrodispositivo o simulador modificamos la estructura de la base de datos antildeadiendo unatributo nuevo en la entidad DetallePersona por ejemplo codigoPostal con el tipoInteger 16 veremos que al volver a arrancar la aplicacioacuten nos apareceraacute un error entiempo de ejecucioacuten similar al siguiente

Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 Theoperationcouldnrsquot be completed(Cocoa error 134100) UserInfo=0x6b6c680 metadata=ltCFBasicHash 0x6b68380[0x1337b38]gttype = immutable dict count = 7entries =gt

2 ltCFString 0x6b70320 [0x1337b38]gtcontents =NSStoreModelVersionIdentifiers

=ltCFArray 0x6b706e0 [0x1337b38]gttype = immutable count = 1

values = (0 ltCFString 0x1332cd8 [0x1337b38]gtcontents =

)4 ltCFString 0x6b70350 [0x1337b38]gtcontents =

NSPersistenceFrameworkVersion=

Persistencia

73Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltCFNumber 0x6b6f2c0 [0x1337b38]gtvalue = +386 type =kCFNumberSInt64Type

6 ltCFString 0x6b70380 [0x1337b38]gtcontents =NSStoreModelVersionHashes =

ltCFBasicHash 0x6b707e0 [0x1337b38]gttype = immutable dict count =2entries =gt

1 ltCFString 0x6b70700 [0x1337b38]gtcontents = Persona =ltCFData 0x6b70740

[0x1337b38]gtlength = 32 capacity = 32 bytes =0x1c50e5a379ff0ddc3cda7b82a3934c5e 75649ac3c919beea2 ltCFString 0x6b70720 [0x1337b38]gtcontents = DetallePersona

=ltCFData 0x6b70790[0x1337b38]gtlength = 32 capacity = 32 bytes =0xb7ba2eb498f9a1acdd6630d56f6cd12c e8963dd3e488ba95

7 ltCFString 0x10e3aa8 [0x1337b38]gtcontents = NSStoreUUID =ltCFString 0x6b70510[0x1337b38]gtcontents = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E8 ltCFString 0x10e3948 [0x1337b38]gtcontents = NSStoreType =ltCFString 0x10e3958[0x1337b38]gtcontents = SQLite9 ltCFString 0x6b703b0 [0x1337b38]gtcontents =

_NSAutoVacuumLevel = ltCFString0x6b70830 [0x1337b38]gtcontents = 210 ltFString 0x6b703d0 [0x1337b38]gtcontents =

NSStoreModelVersionHashesVersion=ltCFNumber 0x6b61950 [0x1337b38]gtvalue = +3 type =

kCFNumberSInt32Type reason=The model used to open the store is incompatible with the oneused tocreate the store

metadata = NSPersistenceFrameworkVersion = 386NSStoreModelVersionHashes =

DetallePersona = ltb7ba2eb4 98f9a1ac dd6630d5 6f6cd12c f890007746b16f00

e8963dd3 e488ba95gtPersona = lt1c50e5a3 79ff0ddc 3cda7b82 a3934c5e 5cb4ee4b

4ac98838 75649ac3c919beeagt

NSStoreModelVersionHashesVersion = 3NSStoreModelVersionIdentifiers = (

)NSStoreType = SQLiteNSStoreUUID = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E_NSAutoVacuumLevel = 2

reason = The model used to open the store is incompatible with the

one usedto create the store

Este error es muy comuacuten cuando estamos desarrollando aplicaciones con Core DataBaacutesicamente nos dice que el modelo de base de datos que estaacute intentando cargar dememoria es distinto al que se indica en la aplicacioacuten esto es porque no hemos indicadoninguacuten meacutetodo de migracioacuten Realmente existen dos formas de solucionar este error

Persistencia

74Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Por un lado y soacutelo cuando estemos desarrollando y no tengamos ninguna versioacutende nuestra aplicacioacuten lanzada en la App Store podremos eliminar la base de datosdel dispositivo simulador simplemente borrando la aplicacioacuten de el Tambieacutenpodemos ejecutar el siguiente coacutedigo una soacutela vez [[NSFileManagerdefaultManager] removeItemAtURLstoreURL errornil]

bull En el caso de que estemos realizando una migracioacuten sobre una versioacuten ya publicadaen la App Store el meacutetodo anterior no nos serviriacutea ya que obligariacuteamos al usuario aborrar su aplicacioacuten del dispositivo con lo que todos los datos que tuviera seperderiacutean Esto no es viable por lo que necesitaremos usar otro meacutetodo para realizarla migracioacuten y solucionar el error de una forma maacutes limpia este meacutetodo es el quevamos a comentar a continuacioacuten

XCode soporta Versioning para Core Data esto es que podemos crear versiones de labase de datos de una forma sencilla si seguimos una serie de pasos

1) Antildeadimos las siguientes opciones en forma de NSDictionary a nuestro coacutedigo delpersistentStoreCoordinator

NSDictionary options = [NSDictionary dictionaryWithObjectsAndKeys[NSNumber numberWithBoolYES]NSMigratePersistentStoresAutomaticallyOption[NSNumber numberWithBoolYES]NSInferMappingModelAutomaticallyOptionnil]

Y modificamos el meacutetodo addPersistentStoreWithType pasaacutendole como paraacutemetro eldiccionario options

if ([__persistentStoreCoordinatoraddPersistentStoreWithTypeNSSQLiteStoreTypeconfigurationnil URLstoreURL optionsoptions erroramperror])

Con el coacutedigo anterior indicamos al persistentStoreCoordinator que la migracioacuten enel caso de que se deba de realizar sea de forma automaacutetica

AtencioacutenDeberemos de tener en cuenta que este tipo de migracioacuten que estamos estudiando en este puntosoacutelo es vaacutelida para cambios simples en Core Data lo que Apple llama como lightweightmigration Este tipo de migracioacuten admite una serie de cambios que se indican dentro de ladocumentacioacuten de Apple para cambios maacutes complejos deberemos realizar migracionespersonalizadas y mucho maacutes complejas El documento en donde Apple explica el tipo demigraciones que existen para Core Data y su forma de implementarlas lo podemos consultardesde esta web

2) Ahora deberemos de crear la versioacuten del esquema de Core Data dentro de XCode para

Persistencia

75Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ello seleccionamos el fichero del esquema sesion04_ejemplo2xcdatamodeld yseleccionamos en el menuacute principal Editor gt Add Model Version A la nueva versioacuten lallamaremos sesion04_ejemplo_2 (versioacuten 2) Una vez que hayamos pulsado sobreFinish se nos habraacute creado la nueva versioacuten del esquema dentro de la principal y estaraacutemarcada con un tick la versioacuten anterior

3) Una vez que tenemos creada la nueva versioacuten deberemos modificarla antildeadiendole elnuevo atributo Seleccionamos la versioacuten recieacuten creadasesion04_ejemplo_2xcdatamodel y antildeadimos a la entidad DetallePersona elatributo codigoPostal Atencioacuten Si este atributo lo hemos antildeadido anteriormentedeberemos de borrarlo de la versioacuten anterior y antildeadirlo en la segunda versioacuten

4) Ahora nos queda indicar a Core Data que queremos usar la versioacuten 2 de la base dedatos para nuestra aplicacioacuten Para ello seleccionamos el esquema principal (el raiz)sesion04_ejemplo2xcdatamodeld y en la ventana de la derecha en la pestantildea de FileInspector seleccionamos como Versioacuten actual la versioacuten 2 esto lo hacemos dentro delapartado de Versioned Core Data Model Automaacuteticamente veremos que el siacutembolo detick cambia a la versioacuten 2

5) Ahora ya podemos volver a compilar y veremos que no nos aparece el error y funcionatodo seguacuten lo esperado

Magical RecordExisten varias librerias de coacutedigo libre que facilitan enormemente el uso de Core Data ennuestras aplicaciones iOS Una de ellas es Magical Record Mediante esta libreriacutea podremos usarCore Data sin tener que preocuparnos de todo el proceso de configuracioacuten en las controladorasde la configuracioacuten de migraciones etc Su instalacioacuten en un proyecto de XCode es muy faacutecil yaltamente recomendable ya que proporciona una API muy clara y sencilla de usar

Persistencia

76Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios

81 Usando User Defaults (1 punto)

En este ejercicio vamos a usar las variables de usuario para almacenar y mostrar los datosdel dispositivo que estamos usando y las veces que hemos arrancado la aplicacioacuten

Crear un proyecto nuevo usando la plantilla Single View Application con el nombreejercicio-userdefaults-ios Se debe de ejecutar en iPhone e iPad (Universal) Comoidentificador de empresa escribiremos esuajtech y como prefijo de clase UA Debe detener el ARC activado (desmarcar el resto de opciones)

Ayuda meacutetodos de UIDevicePara obtener los datos del dispositivo usaremos el singleton UIDevice estos son algunos de losmeacutetodos que podemos usar [[UIDevice currentDevice] name] [[UIDevicecurrentDevice] systemName] [[UIDevice currentDevice]systemVersion] [[UIDevice currentDevice] model] [[UIDevicecurrentDevice] localizedModel]

82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)

En este ejercicio disentildearemos un modelo de datos de dos tablas relacionadas entre ellasantildeadiremos datos por coacutedigo y los mostraremos en una vista de tabla La base de datosque vamos a implementar constaraacute de dos tablas por un lado una que contendraacute los tiacutetulosde series y otra que tendraacute los detalles A la primera la llamaremos Serie y la segundaDetalleSerie Para realizar correctamente el ejercicio deberemos seguir los siguientespasos

1) Crear un proyecto nuevo usando la plantilla Master-Detail Application con elnombre ejercicio-coredata-ios Se debe de ejecutar en iPhone como identificador deempresa escribiremos esuajtech y como prefijo de clase UA Debe de tener el ARCactivado y la opcioacuten de Use Core Data (desmarcar el resto de opciones)

2) Disentildear el siguiente modelo de datos

Persistencia

77Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esquema modelo de datos

3) Revisar el fichero UAAppDelegatem y comprobar que tenemos todos los meacutetodosnecesarios para que funcione Core Data en nuestra aplicacioacuten

4) Modificar el fichero UAMasterViewControllerm para que se muestren los tiacutetulos delas series en la tabla

5) Probar la aplicacioacuten antildeadiendo unos tiacutetulos de prueba

83 Migrando datos con Core Data (05 puntos)

Una vez que hemos disentildeado la Base de Datos con Core Data vamos a modificarlaantildeadiendo un atributo maacutes a la tabla de DetalleSerie valoracion

a) iquestQueacute pasa si cambiamos el modelo de datos y arrancamos la aplicacioacuten iquestQueacute errornos da y por queacute

b) iquestCoacutemo podemos evitar este tipo de errores cuando estemos desarrollando iquestY siestamos en produccioacuten con la aplicacioacuten ya a la venta

c) Crea una nueva versioacuten del modelo

d) Modifica el coacutedigo necesario dentro de la clase UAAppDelegate para evitar este tipo deerrores cuando estemos en un entorno de produccioacuten

e) Vuelve a arrancar la aplicacioacuten y comprueba que funciona bien

Persistencia

78Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Persistencia

79Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

  • 1 Persistencia en Android ficheros y SQLite
    • 11 Introduccioacuten
    • 12 Manejo de ficheros tradicionales en Android
      • 121 Apertura de ficheros
      • 122 Ficheros como recursos
      • 123 Operar con ficheros
      • 124 Almacenar datos en la tarjeta de memoria
        • 13 Base de datos SQLite
          • 131 Content Values
          • 132 Cursores
          • 133 Trabajar con bases de datos SQLite
          • 134 La clase SQLiteOpenHelper
          • 135 Crear una base de datos sin SQLiteHelper
          • 136 Realizar una consulta
          • 137 Extraer resultados de un cursor
          • 138 Antildeadir actualizar y borrar filas
              • 2 Ejercicios - Persistencia en Android ficheros y SQLite
                • 21 Uso de ficheros (05 puntos)
                • 22 Persistencia con ficheros (05 puntos)
                • 23 Base de datos SQLiteOpenHelper (05 puntos)
                • 24 Base de datos insercioacuten y borrado (05 puntos)
                • 25 Base de datos probar nuestro adaptador (05 puntos)
                • 26 Base de datos cambios en la base de datos (05 puntos)
                  • 3 Persistencia en Android proveedores de contenidos y SharedPreferences
                    • 31 Shared Preferences
                      • 311 Guardar Shared Preferences
                      • 312 Leer Shared Preferences
                      • 313 Interfaces para Shared Preferences
                      • 314 Definiendo una pantalla de preferencias con un layout en XML
                      • 315 Controles nativos para preferencias
                      • 316 Actividades de preferencias
                      • 317 Shared Preference Change Listeners
                        • 32 Proveedores de contenidos
                          • 321 Proveedores nativos
                          • 322 Proveedores propios crear un nuevo proveedor de contenidos
                          • 323 Proveedores propios crear la interfaz de consultas
                          • 324 Proveedores propios tipo MIME
                          • 325 Proveedores propios registrar el proveedor
                          • 326 Content Resolvers
                          • 327 Otras operaciones con Content Resolvers
                              • 4 Ejercicios - Persistencia en Android proveedores de contenidos y SharedPreferences
                                • 41 Compartir datos entre actividades con Shared Preferences (075 puntos)
                                • 42 Actividad de preferencias (075 puntos)
                                • 43 Proveedor de contenidos propio (075 puntos)
                                • 44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)
                                  • 5 Persistencia de datos en iOS Ficheros y SQLite
                                    • 51 Introduccioacuten
                                    • 52 Ficheros plist (Property Lists)
                                      • 521 Leyendo datos desde ficheros plist
                                      • 522 Escribiendo datos en ficheros plist
                                        • 53 SQLite
                                          • 531 Herramientas disponibles
                                          • 532 Lectura de datos
                                          • 533 Mostrando los datos en una tabla
                                              • 6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios
                                                • 61 Creando y leyendo un fichero plist (15 puntos)
                                                • 62 Implementar vista de detalle de la serie (05 puntos)
                                                • 63 Uso baacutesico de una base de datos SQLite (1 punto)
                                                  • 7 Persistencia de datos en iOS User Defaults y Core Data
                                                    • 71 Introduccioacuten
                                                    • 72 User Defaults
                                                    • 73 Core Data
                                                      • 731 Creando el proyecto
                                                      • 732 Definiendo el modelo de datos
                                                      • 733 Probando el modelo
                                                      • 734 Generando los modelos automaacuteticamente
                                                      • 735 Creando la vista de tabla
                                                      • 736 Migraciones de bases de datos con Core Data
                                                          • 8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios
                                                            • 81 Usando User Defaults (1 punto)
                                                            • 82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)
                                                            • 83 Migrando datos con Core Data (05 puntos)
Page 8: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de

import androidcontentContextimport androiddatabaseimport androiddatabasesqliteimport androiddatabasesqliteSQLiteDatabaseCursorFactoryimport androidutilLog

public class MiAdaptadorBD private static final String NOMBRE_BASE_DATOS =

mibasededatosdbprivate static final String TABLA_BASE_DATOS = mainTableprivate static final int VERSION_BASE_DATOS = 1

Nombre de la columna de clave primariapublic static final String CP_ID=_id El nombre e iacutendice de cada columna en la base de datospublic static final String COLUMNA_NOMBRE=nombrepublic static final int COLUMNA_INDICE = 1 PENDIENTE crear campos adicionales para el resto de columnas de la base de datos

Sentencia SQL para crear una nueva base de datosprivate static final String CREAR_BASE_DATOS = create table +

TABLA_BASE_DATOS + ( + CP_ID + integer primary key autoincrement +COLUMNA_NOMBRE + text not null)

Variable para almacenar la instancia de la base de datosprivate SQLiteDatabase db Contexto de la aplicacioacuten que estaacute usando la base de datosprivate final Context contexto Clase helper usada para crear o actualizar la base de datos (hablamos de ella en la siguiente seccioacuten)private miHelperBD dbHelper

Crea la base de datos con ayuda del helperpublic MiAdaptadorBD(Context _contexto)

contexto = _contextodbHelper = new miHelperBD(contexto NOMBRE_BASE_DATOS

nullVERSION_BASE_DATOS)

Abre una base de datos ya existentepublic MiAdaptadorBD open() throws SQLException

db = dbHelpergetWritableDatabase()return this

Cierra la base de datos cuando eacutesta ya no va a ser utilizadapublic void close()

dbclose()

Meacutetodo para insertar un elemento en la tabla de la base dedatos

public int insertar(MiObjeto _miObjeto) PENDIENTE crear nuevos elementos ContentValues para

representar al objeto e insertarlos en la base de datos

Devolvemos el iacutendice del nuevo elemento en la base dedatos

return index

Persistencia

8Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Meacutetodo para eliminar un elemento de la tabla de la base dedatos

Devuelve un valor booleano indicando el eacutexito o no de la operacioacutenpublic boolean eliminar(long _indiceFila)

return dbdelete(TABLA_BASE_DATOS CP_ID + = +_indiceFila null) gt 0

Meacutetodo para obtener un Cursor que apunte a todas las filascontenidas

en la tabla de la base de datospublic Cursor obtenerTodos ()

return dbquery(TABLA_BASE_DATOS new String[] CP_IDCOLUMNA_NOMBRE

null null null nullnull)

Meacutetodo para obtener un elemento determinado de la base de datos a partir de su iacutendice de filapublic MiObjeto getObjeto(long _indiceFila)

PENDIENTE obtener un cursor a una fila de la base de datos y usar sus valores para inicializar una instancia de MiObjeto

return objectInstance

Meacutetodo para actualizar un elemento de la tabla de la base dedatos

public boolean actualizar(long _indiceFila MiObjeto _miObjeto) PENDIENTE crear nuevos ContentValues a partir del

objeto y usarlos para actualizar una fila en la tabla

correspondiente

return true

Clase privada auxiliar para ayudar en la creacioacuten yactualizacioacuten

de la base de datosprivate static class miHelperBD extends SQLiteOpenHelper

public miHelperBD(Context contexto String nombreCursorFactory factory int version)

super(contexto nombre factory version)

Meacutetodo llamado cuando la base de datos no existe en eldisco

y la clase Helper necesita crearla desde ceroOverridepublic void onCreate(SQLiteDatabase _db)

_dbexecSQL(CREAR_BASE_DATOS)

Meacutetodo llamado cuando la versioacuten de la base de datos en disco es diferente a la indicada en este caso la base de datos en disco necesita ser actualizadaOverridepublic void onUpgrade(SQLiteDatabase _db int

_versionAnterior

Persistencia

9Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

int _versionNueva) Mostramos en LogCat la operacioacutenLogw(AdaptadorBD Actualizando desde la

versioacuten +_versionAnterior + a la versioacuten +_versionNueva + lo cual borraraacute los

datospreviamente almacenados)

Actualizamos la base de datos para que seadapte a la nueva versioacuten

La forma maacutes sencilla de llevar a cabo dichaactualizacioacuten es

eliminar la tabla antigua y crear una nueva_dbexecSQL(DROP TABLE IF EXISTS +

TABLA_BASE_DATOS)onCreate(_db)

134 La clase SQLiteOpenHelper

La clase SQLiteOpenHelper es una clase abstracta utilizada como medio paraimplementar un patroacuten de creacioacuten apertura y actualizacioacuten de una determinada base dedatos Al implementar una subclase de SQLiteOpenHelper podemos abstraernos de laloacutegica subyacente a la decisioacuten de crear o actualizar una base de datos de manera previa aque eacutesta deba ser abierta

En el ejemplo de coacutedigo anterior se vio coacutemo crear una subclase de SQLiteOpenHelper

por medio de la sobrecarga de sus meacutetodos onCreate y onUpgrade los cuales permitencrear una nueva base de datos o actualizar a una nueva versioacuten respectivamente

NotaEn el ejemplo anterior el meacutetodo onUpgrade simplemente eliminaba la tabla existente y lareemplazaba con la nueva definicioacuten de la misma En la praacutectica la mejor solucioacuten seriacutea migrarlos datos ya existentes a la nueva tabla

Mediante los meacutetodos getReadableDatabase y getWritableDatabase podemos abrir yobtener una instancia de la base de datos de soacutelo lectura o de lectura y escriturarespectivamente La llamada a getWritableDatabase podriacutea fallar debido a falta deespacio en disco o cuestiones relativas a permisos por lo que es una buena praacutectica teneren cuenta esta posibilidad por medio del mecanismo de manejo de excepciones de JavaEn caso de fallo la solucioacuten podriacutea pasar por al menos proporcionar una copia de soacutelolectura tal como se muestra en el siguiente ejemplo

dbHelper = new miHelperBD(contexto NOMBRE_BASE_DATOS nullVERSION_BASE_DATOS)

SQLiteDatabase dbtry

db = dbHelpergetWritableDatabase() catch (SQLiteException ex)

db = dbHelpergetReadableDatabase()

Persistencia

10Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Al hacer una llamada a cualquiera de los dos meacutetodos anteriores se invocaraacuten losmanejadores de evento adecuados Asiacute pues si por ejemplo la base de datos no existieraen disco se ejecutariacutea el coacutedigo de onCreate Si por otra parte la versioacuten de la base dedatos se hubiera modificado se disparariacutea el manejador onUpgrade Lo que sucedeentonces es que al hacer una llamada a getWritableDatabase o getReadableDatabase

se devolveraacute una base de datos nueva actualizada o ya existente seguacuten el caso

135 Crear una base de datos sin SQLiteHelper

Tambieacuten es posible crear y abrir bases de datos sin necesidad de utilizar una subclase deSQLiteHelper Para ello haremos uso del meacutetodo openOrCreateDatabase En este casoseraacute necesario tambieacuten hacer uso del meacutetodo execSQL sobre la instancia de la base dedatos devuelta por el meacutetodo anterior para ejecutar los comandos SQL que permitiraacutencrear las tablas de la base de datos y establecer las relaciones entre ellas El siguientecoacutedigo muestra un ejemplo

private static final String NOMBRE_BASE_DATOS = mibasededatosdbprivate static final String TABLA_BASE_DATOS = tablaPrincipal

private static final String CREAR_BASE_DATOS =create table + TABLA_BASE_DATOS + _id integer primare key

autoincrement +columna_uno text not null)

SQLiteDatabase db

private void crearBaseDatos() db = openOrCreateDatabase(NOMBRE_BASE_DATOS ContextMODE_PRIVATE

null)dbexecSQL(CREAR_BASE_DATOS)

NotaAunque no es necesariamente obligatorio es recomendable que cada tabla incluya un campo detipo clave primaria con autoincremento a modo de iacutendice para cada fila En el caso en el que sedesee compartir la base de datos mediante un proveedor de contenidos un campo uacutenico de estetipo es obligatorio

136 Realizar una consulta

El resultado de cualquier consulta a una base de datos seraacute un objeto de la clase CursorEsto permite a Android manejar los recursos de manera maacutes eficiente de tal forma que enlugar de devolver todos los resultados eacutestos se van proporcionando conforme senecesitan

Para realizar una consulta a una base de datos haremos uso del meacutetodo query de lainstancia correspondiente de la clase SQLiteDatabase Los paraacutemetros que requiere este

Persistencia

11Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

meacutetodo son los siguientes

bull Un booleano opcional que permite indicar si el resultado deberiacutea contener tan soacutelovalores uacutenicos

bull El nombre de la tabla sobre la que realizar la consultabull Un array de cadenas que contenta un listado de las columnas que deben incluirse en el

resultadobull Una claacuteusula where que defina las filas que se deben obtener de la tabla Se puede

utilizar el comodiacuten el cual seraacute reemplazado por los valores que se pasen a traveacutesdel siguiente paraacutemetro

bull Un array de cadenas correspondientes a argumentos de seleccioacuten que reemplazaraacutenlos siacutembolos en la claacuteusula where

bull Una claacuteusula group by que define coacutemo se agruparaacuten las filas obtenidas comoresultado

bull Un filtro having que indique que grupos incluir en el resultado en el caso en el que sehaya hecho uso del paraacutemetro anterior

bull Una cadena que describa la ordenacioacuten que se llevaraacute a cabo sobre las filas obtenidascomo resultado

bull Una cadena opcional para definir un liacutemite en el nuacutemero de resultados a devolver

NotaComo se puede observar la mayoriacutea de estos paraacutemetros hacen referencia al lenguaje SQLTratar este tema queda fuera de los objetivos de este curso por lo que en nuestros ejemplos ledaremos en la mayoriacutea de las ocasiones un valor null a estos paraacutemetros

El siguiente coacutedigo muestra un ejemplo de consultas utilizando diversos paraacutemetros

Devolver los valores de las columnas uno y tres para todas las filas sin duplicadosString[] columnas = new String[] CLAVE_ID CLAVE_COL1 CLAVE_COL3

Cursor todasFilas = dbquery(true TABLA_BASE_DATOS columnas null nullnull null

null null)

Devolvemos los valores de todas las columnas para aquellas filas cuyovalor de la columna 3 sea igual al de una determinada variable Ordenamos las filasseguacuten el valor de la columna 5 Como queremos los valores de todas las columnas le damos al paraacutemetro correspondiente a las columnas a devolver el valor null En este caso no se ha hecho uso del primer paraacutemetro booleano que esoptativoString where = CLAVE_COL3 + = + valorString orden = CLAVE_COL5Cursor miResultado = dbquery(TABLA_BASE_DATOS null where null nullnull orden)

137 Extraer resultados de un cursor

Persistencia

12Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El primer paso para obtener resultados de un cursor seraacute desplazarnos a la posicioacuten apartir de la cual queremos obtener los datos usando cualquiera de los meacutetodosespecificados anteriormente (como por ejemplo moveToFirst o moveToPosition) Unavez hecho esto hacemos uso de meacutetodos get[TIPO] pasando como paraacutemetro el iacutendice dela columna Esto tendraacute como resultado la devolucioacuten del valor para dicha columna de lafila actualmente apuntada por el cursor

String valor = miResultadogetString(indiceColumna)

A continuacioacuten podemos ver un ejemplo maacutes completo en el que se va iterando a lo largode los resultados apuntados por un cursor extrayendo y sumando los valores de unacolumna de flotantes

int COLUMNA_VALORES_FLOTANTES = 2Cursor miResultado = dbquery(Tabla null null null null nullnull)float total = 0

Nos aseguramos de que se haya devuelto al menos una filaif (miResultadomoveToFirst())

Iteramos sobre el cursordo

float valor =miResultadogetFloat(COLUMNA_VALORES_FLOTANTES)

total += valor while (miResultadomoveToNext())

float media = total miResultadogetCount()

NotaDebido a que las columnas de las bases de datos en SQLite son debilmente tipadas podemosrealizar castings cuando sea necesario Por ejemplo los valores guardados como flotantes en labase de datos podriacutean leerse maacutes adelante como cadenas

138 Antildeadir actualizar y borrar filas

La clase SQLiteDatabase proporciona los meacutetodos insert delete y update queencapsulan las instrucciones SQL requeridas para llevar a cabo estas acciones Ademaacutesdisponemos de la posibilidad de utilizar el meacutetodo execSQL el cual nos permite ejecutarcualquier sentencia SQL vaacutelida en nuestras tablas de la base de datos en el caso en el quequeramos llevar a cabo estas (u otras) operaciones manualmente

NotaCada vez que modifiques los valores de la base de datos puedes actualizar un cursor que se puedaver afectado por medio del meacutetodo refreshQuery de la clase Cursor

Para insertar una nueva fila es necesario construir un objeto de la clase ContentValues

y usar su meacutetodo put para proporcionar valor a cada una de sus columnas Para insertar la

Persistencia

13Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

nueva fila pasaremos como paraacutemetro este objeto del tipo ContentValues al meacutetodoinsert de la instancia de la base de datos junto por supuesto con el nombre de la tablaA continuacioacuten se muestra un ejemplo

Crear la nueva filaContentValues nuevaFila = new ContentValues()

Asignamos valores a cada columnanuevaFilaput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas ]

Insertar la nueva fila en la tabladbinsert(TABLA_BASE_DATOS null nuevaFila)

NotaLos ficheros como imaacutegenes o ficheros de audio no se suelen almacenar dentro de tablas en unabase de datos En estos casos se suele optar por almacenar en la base de datos una cadena con laruta al fichero o una URI

La operacioacuten de actualizar una fila tambieacuten puede ser llevada a cabo por medio del usode la clase ContentValues En este caso deberemos llamar al meacutetodo update de lainstancia de la base de datos pasando como paraacutemetro el nombre de la tabla el objetoContentValues y una claacuteusula where que especifique la fila o filas a actualizar tal comose puede ver en el siguiente ejemplo

Creamos el objeto utilizado para definir el contenido a actualizarContentValues valoresActualizar = new ContentValues()

Asignamos valores a las columnas correspondientesvaloresActualizarput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas a actualizar ]

String where = CLAVE_ID + = + idFila

Actualizamos la fila con el iacutendice especificado en la instruccioacutenanteriordbupdate(TABLA_BASE_DATOS valoresActualizar where null)

Por uacuteltimo para borrar una fila simplemente llamaremos al meacutetodo delete de lainstancia de la base de datos especificando en este caso el nombre de la tabla que se veraacuteafectada por la operacioacuten y una claacuteusula where que utilizaremos para especificar las filasque queremos borrar Un ejemplo podriacutea ser el siguiente

dbdelete(TABLA_BASE_DATOS CLAVE_ID + = + idFila null)

Persistencia

14Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

2 Ejercicios - Persistencia en Android ficheros y SQLite

21 Uso de ficheros (05 puntos)

En este ejercicio vamos a crear una aplicacioacuten que muestre un listado de cadenas porpantalla Estas cadenas se almacenaraacuten en un fichero de texto privado para la aplicacioacutenPodremos antildeadir nuevas cadenas a partir de un cuadro de edicioacuten presente en la interfazPartiremos del proyecto Ficheros proporcionado en las plantillas de la aplicacioacuten Debesseguir los siguientes pasos

bull Antildeadir un manejador al botoacuten de la actividad principal para que cada vez que seapulsado se guarde en un fichero de texto El fichero se llamaraacute ficherotxt Al abrirel fichero para escritura se utilizaraacute el paraacutemetro ContextMODE_APPEND con lo quecada nueva cadena se antildeadiraacute al final del fichero en el caso en el que eacuteste ya existieraPara escribir en el fichero utilizaremos el meacutetodo writeBytes del objetoDataOutputStream correspondiente

bull Al pulsar el botoacuten tambieacuten se deberaacute borrar el contenido del elemento EditText (leasignamos a la vista una cadena vaciacutea con el meacutetodo setText)

bull Ejecutamos la aplicacioacuten e introducimos algunas liacuteneas en el fichero Vamos acomprobar ahora que todo ha funcionado correctamente Para ello accedemos alsistema de ficheros de nuestro dispositivo ejecutando el comando adb shell

dentro de la carpeta platform-tools de nuestra carpeta de instalacioacuten del SDK deAndroid

bull Comprueba que se ha creado el fichero ficherotxt en la carpetadatadataesuajtechandroidficherosfiles Examina su contenido ejecutando cat

ficherotxt Si algo no ha salido bien siempre podraacutes eliminar el fichero con rm

ficherotxtbull En la interfaz de la actividad se ha incluido una vista de tipo TextView debajo del

botoacuten Antildeade un manejador para dicha vista de tal forma que cada vez que se hagaclick sobre ella se muestre el contenido del fichero ficherotxt Para ello leeremos elfichero liacutenea a liacutenea antildeadiendo cada una al TextView por medio del meacutetodo append

Persistencia

15Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Interfaz de la aplicacioacuten Ficheros

22 Persistencia con ficheros (05 puntos)

Seguramente habraacutes observado que si abandonas la aplicacioacuten anterior (pulsando el botoacutenBACK del dispositivo) al volver a ejecutarla el contenido del TextView ha desaparecidoDebes volver a pulsar sobre la vista para que se vuelva a mostrar el contenido del ficheroHaz las modificaciones pertinentes para que esto no sea asiacute es decir para que cuandovuelva a mostrarse la actividad tras haber sido eliminada de la memoria se muestre elTextView tal cual se veiacutea antes de abandonar la aplicacioacuten

AvisoEn este ejercicio no se estaacute pidiendo que cargues el contenido del fichero en el TextView nadamaacutes arrancar la aplicacioacuten Puede darse el caso de que lo que esteacute mostrando el TextViewcuando la actividad sea eliminada de memoria no se corresponda con el contenido actualizado delfichero

NotaPara poder completar el ejercicio deberaacutes repasar los manejadores de evento de la primera sesioacutendel moacutedulo de Android relacionados con el ciclo de vida de ejecucioacuten de actividades

23 Base de datos SQLiteOpenHelper (05 puntos)

En las plantillas de la aplicacioacuten se incluye la aplicacioacuten BaseDatos En el proyecto de laaplicacioacuten se incluye el esqueleto de una clase MiAdaptadorBD que tendremos quecompletar Se trata de un patroacuten que nos va a permitir acceder a una base de datos de

Persistencia

16Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

usuarios dentro de la aplicacioacuten sin necesidad de hacer uso de coacutedigo SQL

La clase MiAdaptadorBD incluye a su vez otro patroacuten en este caso implementado comouna subclase de SQLiteOpenHelper Eacuteste nos obliga a definir queacute ocurre cuando la basede datos todaviacutea no existe y debe ser creada y queacute ocurre si ya existe pero debe seractualizada porque ha cambiado de versioacuten Asiacute el SQLiteOpenHelper queimplementemos en este caso MiOpenHelper nos devolveraacute siempre una base de datosseparaacutendonos de la loacutegica encargada de comprobar si la base de datos existe o no

En este primer ejercicio se pide hacer lo siguiente

bull Ejecutar la sentencia de creacioacuten de bases de datos (la tenemos declarada comoconstante de la clase) en el meacutetodo MiOpenHelperonCreate

bull Implementar tambieacuten el meacutetodo onUpgrade Idealmente eacuteste deberiacutea portar las tablasde la versioacuten antigua a la versioacuten nueva copiando todos los datos Nosotros vamos aeliminar directamente la tabla que tenemos con la sentencia SQL DROP TABLE IF

EXISTS + NOMBRE_TABLA y volveremos a crearlabull En el constructor de MiAdaptadorBD debemos obtener en el campo db la base de

datos utilizando MiOpenHelper

24 Base de datos insercioacuten y borrado (05 puntos)

Continuamos trabajando con el proyecto anterior En este ejercicio completaremos elcoacutedigo relacionado con las sentencias de insercioacuten y borrado Para realizar la insercioacutenvamos a hacer uso de un mecanismo que no hemos visto en la sesioacuten de teoriacutea y queconsiste en utilizar una sentencia de insercioacuten SQL precompilada que se completaraacute conlos valores concretos a insertar en la base de datos justo antes de realizar dicha insercioacutenLa ventaja de las sentencias compiladas es que evitan que se produzcan ataques deinyeccioacuten de coacutedigo

Como se puede observar se ha incluido un atributo a la clase que representa la insercioacutenSQL

private static final String INSERT = insert into + NOMBRE_TABLA +(+COLUMNAS[1]+) values ()

En esta sentencia no se indica ninguacuten valor concreto para la columna nombre En el lugaren el que deberiacutean aparecer los valores de dicho campo se ha escrito simplemente unsiacutembolo de interrogacioacuten Tambieacuten se ha antildeadido una instancia de la claseSQLiteStatement como parte de los atributos de la clase

private SQLiteStatement insertStatement

Realizamos los siguientes pasos

bull En el constructor de la clase MiAdaptadorBD llevamos a cabo la compilacioacuten de lasentencia

thisinsertStatement = thisdbcompileStatement(INSERT)

Persistencia

17Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Una vez hecho esto cada vez que deseemos insertar un nuevo usuario en la base dedatos deberemos dar valores concretos a la columna nombre y ejecutar la sentenciaPara ello debemos antildeadir el siguiente coacutedigo dentro del meacutetodo insert de la claseMiAdaptadorBD

thisinsertStatementbindString(1 nombreUsuario)return thisinsertStatementexecuteInsert()

bull Por uacuteltimo completamos el coacutedigo del meacutetodo deleteAll cuyo contenido es eliminara todos los usuarios de la base de datos Para que ademaacutes se devuelva el nuacutemero defilas afectadas podemos insertar la siguiente liacutenea en dicho meacutetodo

return dbdelete(NOMBRE_TABLA null null)

Una vez hechos todos estos cambios podremos utilizar la clase MiAdaptadorBD parahacer operaciones con la base de datos de usuarios de manera transparente

25 Base de datos probar nuestro adaptador (05 puntos)

Antildeade coacutedigo en la actividad Main para eliminar todos los usuarios de la base de datosantildeadir dos cualesquiera y listarlos por medio de la vista de tipo TextView que seencuentra en el layout de dicha actividad

Podemos comprobar mediante la liacutenea de comandos (comando adb shell) que la basede datos ha sido creada Para ello puede ser uacutetil hacer uso de los siguientes comandos

cd datadataesuajtechandroidbasedatosdatabasessqlite3 misusuariosdbsqlitegt schemasqlitegt tablessqlitegt select from usuarios

26 Base de datos cambios en la base de datos (05 puntos)

Ahora vamos a cambiar en la clase MiAdaptadorBD el nombre de la segunda columnaque en lugar de nombre se va a llamar nombres Ejecutamos la aplicacioacuten y comprobamosque sale con una excepcioacuten Comprueba por medio de LogCat cuaacutel ha sido el erroriquestCoacutemo lo podemos solucionar

NotaPista conforme hemos programado la clase MiAdaptadorBD y siguiendo el patroacuten de disentildeode SQLiteOpenHelper es posible arreglar el problema simplemente modificando el valor deun campo

Persistencia

18Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

3 Persistencia en Android proveedores de contenidos ySharedPreferences

31 Shared Preferences

Comenzamos esta sesioacuten hablando de una de las teacutecnicas maacutes simples de persistencia enAndroid junto al uso de ficheros Shared Preferences Se trata de un mecanismo ligeropara almacenar datos basado en pares clave-valor utilizado normalmente para guardarpreferencias u opciones de la aplicacioacuten Tambieacuten puede ser utilizado para almacenar elestado de la interfaz graacutefica para contemplar el caso de que nuestra actividad debafinalizar abruptamente su ejecucioacuten Otro posible uso es el de compartir informacioacutenentre componentes de una misma aplicacioacuten

NotaLos Shared Preferences nunca son compartidos entre diferentes aplicaciones

Este meacutetodo se basa en el uso de la clase SharedPreferences Es posible utilizarla paraguardar datos en muy diversos formatos booleanos cadenas flotantes y enteros

311 Guardar Shared Preferences

Para crear o modificar un Shared Preference llamamos al meacutetodogetSharedPreferences del contexto de la aplicacioacuten pasando como paraacutemetro elidentificador del conjunto de valores de preferencias con el que queremos trabajar Pararealizar la modificacioacuten del valor de un Shared Preference hacemos uso de la claseSharedPreferencesEditor Para obtener el objeto editor invocamos el meacutetodo edit

del objeto Shared Preferences que queremos modificar Los cambios se guardan medianteel meacutetodo commit Veamos un ejemplo

int modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences = getSharedPreferences(Mispreferencias modo)

Obtenemos un editor para modificar las preferenciasSharedPreferencesEditor editor = misSharedPreferencesedit()

Guardamos nuevos valores en el objeto SharedPreferenceseditorputBoolean(isTruetrue)editorputFloat(unFloat10)editorputInt(numeroEntero12)editorputLong(otroNumero2)editorputString(unaCadenavalor)

Guardamos los cambioseditorcommit()

Persistencia

19Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

312 Leer Shared Preferences

Para leer Shared Preferences utilizamos el meacutetodo getSharedPreferences tal como seha comentado anteriormente Pasamos como paraacutemetro el identificador del conjunto deShared Preferences al que deseamos acceder Una vez hecho esto ya podemos hacer usode meacutetodos de tipo get para acceder a preferencias individuales Hay diferentes meacutetodosget para diferentes tipos de datos Cada uno de ellos requiere como paraacutemetro la claveque identifica la preferencia cuyo valor deseamos obtener y un valor por defecto el cualse usaraacute en el caso concreto en el que no existiera la preferencia con la clave pasada comoprimer paraacutemetro Podemos ver un ejemplo a continuacioacuten

public static String MIS_PREFS = MIS_PREFS

public void cargarPreferences() Obtener las preferencias almacenadasint modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences =

getSharedPreferences(MIS_PREFS modo)

boolean isTrue = misSharedPreferencesgetBoolean(isTruefalse)float unFloat = misSharedPreferencesgetFloat(unFloat0)int numeroEntero = misSharedPreferencesgetInt(numeroEntero0)long otroNumero = misSharedPreferencesgetLong(otroNumero0)String unaCadena = misSharedPreferencesgetString(unaCadena)

313 Interfaces para Shared Preferences

Android proporciona una plataforma basada en XML para la creacioacuten de interfacesgraacuteficas para el manejo de preferencias Estas interfaces tendraacuten un aspecto similar al delas del resto de aplicaciones del sistema Al utilizarla nos estaremos asegurando de quenuestras actividades para el manejo de preferencias seraacuten consistentes con las del resto deaplicaciones del sistema De esta forma los usuarios estaraacuten familiarizados con el manejode esta parte de nuestra aplicacioacuten

La plataforma consiste en tres elementos

bull Layout de la actividad de preferencias se trata de un archivo XML que definiraacute lainterfaz graacutefica de la actividad que muestre los controles para modificar los valores delas preferencias Permite especificar las vistas a mostrar los posibles valores que sepueden introducir y a queacute clave de Shared Preferences se corresponde cada vista

bull Actividad de preferencias una subclase de PreferenceActivity que secorresponderaacute con la pantalla de preferencias de nuestra aplicacioacuten

bull Shared Preference Change Listener una implementacioacuten de la claseonSharedPreferenceChangeListener que actuaraacute en el caso en el que cambie elvalor de alguna Shared Preference

314 Definiendo una pantalla de preferencias con un layout en XML

Persistencia

20Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esta es sin duda la parte maacutes importante de una actividad de preferencias Se trata de unarchivo XML utilizado para definir varios aspectos de dicha actividad Al contrario queen el caso de los archivos de layout para interfaces graacuteficas que vimos en la sesioacutencorrespondiente del moacutedulo de Android estos archivos de layout se guardan en la carpetaresxml de los recursos de la aplicacioacuten

Aunque conceptualmente estos layouts son similares a los utilizados para definirinterfaces graacuteficas los layouts de actividades de preferencias utilizan un conjuntoespecializado de componentes especiacuteficos para este tipo de actividades Estaacuten pensadospara proporcionar interfaces con un aspecto similar al del resto de actividades depreferencias del sistema Estos controles se describen en maacutes detalle en la siguienteseccioacuten

Los layout de preferencias contendraacuten un elemento PreferenceScreen

ltxml version=10 encoding=utf-8gtltPreferenceScreen

xmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceScreengt

Se pueden antildeadir maacutes elementos de este tipo si se desea Al hacerlo eacutestos serepresentaraacuten como un elemento seleccionable en un listado que mostraraacute una nuevaventana de preferencias en caso de que sea seleccionado

Dentro de cada elemento PreferenceScreen podemos incluir tantos elementos de tipoPreferenceCategory y elementos especiacuteficos para antildeadir controles como se desee Loselementos PreferenceCategory son utilizados para dividir cada pantalla de preferenciasen subcategoriacuteas mediante una barra de tiacutetulo Su sintaxis es la siguiente

ltPreferenceCategoryandroidtitle=My Preference Categorygt

ltPreferenceCategorygt

Una vez dividida la pantalla en subcategoriacuteas tan soacutelo quedariacutea antildeadir los elementoscorrespondientes a los controles especiacuteficos que veremos en la siguiente seccioacuten Losatributos de los elementos XML para estos controles pueden variar aunque existe unconjunto de atributos comunes

bull androidkey la clave de Shared Preference que se usaraacute para guardar el valor delcontrol

bull androidtitle texto a mostrar para describir la preferenciabull androidsummary una descripcioacuten maacutes larga a mostrar debajo del texto definido con

el campo anteriorbull androiddefaultValue el valor por defecto que se mostraraacute inicialmente y tambieacuten

el que finalmente se guardaraacute si ninguacuten otro valor fue asignado a este campo

El siguiente listado muestra una pantalla de preferencias muy simple con una uacutenicacategoriacutea y un uacutenico control (un check box)

ltxml version=10 encoding=utf-8gt

Persistencia

21Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltPreferenceScreenxmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceCategory

androidtitle=Mi categoriacuteagtltCheckBoxPreference

androidkey=MI_CHECK_BOXandroidtitle=Preferencia Check Boxandroidsummary=Descripcioacuten de la preferencia

Check BoxandroiddefaultValue=true

gtltPreferenceCategorygt

ltPreferenceScreengt

Esta pantalla de preferencias se mostrariacutea de la siguiente forma

Ejemplo sencillo de ventana de preferencias

315 Controles nativos para preferencias

Android incluye diferentes controles que podemos antildeadir a nuestras ventanas depreferencias por medio de los elementos XML que se indican a continuacioacuten

bull CheckBoxPreference un check box estaacutendar que puede ser utilizado parapreferencias de tipo booleano

bull EditTextPreference permite al usuario introducir una cadena como valor para unapreferencia Al seleccionar el texto se mostraraacute un diaacutelogo con el que introducir elnuevo valor

bull ListPreference el equivalente a un Spinner Seleccionar este elemento de lainterfaz mostraraacute un diaacutelogo con las diferentes opciones que es posible seleccionar Seutilizan arrays para asociar valores y textos a las opciones de la lista

bull RingtonePreference un tipo especiacutefico de preferencia de tipo listado que permiteseleccionar entre diferentes tonos de teleacutefono Esta opcioacuten es particularmente uacutetil en

Persistencia

22Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

el caso en el que se esteacute creando una pantalla de preferencias para configurar lasopciones de notificacioacuten de una aplicacioacuten o componente de la misma

Tambieacuten es posible crear nuestros propios controles personalizados definiendo unasubclase de Preference (o cualquiera de sus subclases) Se puede encontrar maacutesinformacioacuten en la documentacioacuten de AndroidhttpdeveloperandroidcomreferenceandroidpreferencePreferencehtml

316 Actividades de preferencias

Para mostrar una pantalla de preferencias debemos definir una subclase dePreferenceActivity

public class MisPreferencias extends PreferenceActivity

La interfaz graacutefica de la interfaz se puede crear a partir del layout en el meacutetodo onCreatemediante una llamada a addPreferencesFromResource

Overridepublic void onCreate(Bundle savedInstaceState)

superonCreate(savedInstanceState)addPreferencesFromResource(Rxmlmilayout)

Como en el caso de cualquier otra actividad la actividad de preferencias que hayamosdefinido debe ser incluida en el Manifest de la aplicacioacuten

ltactivity androidname=MisPreferenciasandroidlabel=Mis preferenciasgt

ltactivitygt

Eso es todo lo que necesitamos para crear este tipo de actividades y mostrar una ventanacon las preferencias definidas en el layout Esta actividad puede ser iniciada comocualquier otra mediante un Intent ya sea a traveacutes de una llamada a startActivity obien a startActivityForResult

Intent i = new Intent(this MisPreferenciasclass)startActivityForResult(i MOSTRAR_PREFERENCIAS)

Los valores para las diferentes preferencias de la actividad son almacenadas en elcontexto de la aplicacioacuten Esto permite a cualquier componente de dicha aplicacioacutenincluyendo a cualquier actividad y servicio de la misma acceder a los valores tal como semuestra en el siguiente coacutedigo de ejemplo

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto) PENDIENTE hacer uso de meacutetodos get para obtener los valores

317 Shared Preference Change Listeners

Persistencia

23Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El uacuteltimo elemento que nos queda por examinar en esta plataforma de Shared Preferenceses la interfaz onSharedPreferenceChangeListener que es utilizada para invocar unevento cada vez que una preferencia es antildeadida eliminada o modificada Para registrarListeners usamos un coacutedigo como el que se muestra a continuacioacuten en el que la parte maacutesimportante es sin duda el meacutetodo onSharedPreferenceChanged dentro del cualdeberemos determinar queacute preferencia ha cambiado de estado a partir de sus paraacutemetrospara llevar a cabo las acciones oportunas

public class MiActividad extends Activity implementsOnSharedPreferenceChangeListener

Overridepublic void onCreate(Bundle SavedInstanceState)

Registramos este objetoOnSharedPreferenceChangeListener

Context contexto = getApplicationContext()SharedPreferences prefs =

PreferenceManagergetDefaultSharedPreferences(contexto)prefsregisterOnSharedPreferenceChangeListener(this)

public void onSharedPreferenceChanged(SharedPreferences prefsString clave)

PENDIENTE comprobar queacute clave concreta ha cambiado ysu nuevo valor

para modificar la interfaz de una actividad o elcomportamiento de

alguacuten componente

Otra forma maacutes sencilla de controlar los cambios en las preferencias es registrar unlistener en el constructor de la actividad principal o aquella que haga uso de los valores depreferencias de la forma

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto)

mListener = new OnSharedPreferenceChangeListener() public void onSharedPreferenceChanged(SharedPreferences

sharedPreferences String key)

if( keyequals(MICLAVE) )boolean valor =

sharedPreferencesgetBoolean(MICLAVE false)

prefsregisterOnSharedPreferenceChangeListener(mListener)

NotaEn este segundo ejemplo mListener es una variable miembro de la clase Para este caso esnecesario registrar el listener de esta forma porque sino el garbage collector lo eliminariacutea (debidoa particularidades en la programacioacuten de los listeners asociados a las SharedPreferences)

Persistencia

24Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

32 Proveedores de contenidos

Los proveedores de contenidos o ContentProvider proporcionan una interfaz parapublicar y consumir datos identificando la fuente de datos con una direccioacuten URI queempieza por content Son una forma maacutes estaacutendar que los adaptadores (como el quevimos en la sesioacuten anterior en la seccioacuten dedicada a SQLite) de desacoplar la capa deaplicacioacuten de la capa de datos

Podemos encontrar dos tipos principales de proveedores de contenidos en Android Losproveedores nativos son proveedores que el propio sistema nos proporciona para poderacceder a datos del dispostivo incluyendo audio viacutedeo imaacutegenes informacioacuten personaletc Por otra parte tambieacuten es posible que creemos nuestros propios proveedores con talde permitir a nuestra aplicacioacuten compartir datos con el resto del sistema En esta sesioacutenhablaremos de ambos tipos

321 Proveedores nativos

Android incluye una serie de proveedores de contenidos que nos pueden proporcionarinformacioacuten de diferentes tipos informacioacuten sobre nuestros contactos informacioacuten delcalendario e incluso ficheros multimedia Ejemplos de estos proveedores de contenidosnativos son el Browser CallLog ContactsContract MediaStore Settings y elUserDictionary Para poder hacer uso de estos proveedores de contenidos y acceder alos datos que nos proporcionan debemos antildeadir los permisos adecuados al ficheroAndroidManifestxml Por ejemplo para acceder al listiacuten telefoacutenico podemos antildeadir elpermiso correspondiente de la siguiente forma

ltuses-sdk androidminSdkVersion=8 gtltuses-permission androidname=androidpermissionREAD_CONTACTSgt

ltmanifestgt

Para obtener informacioacuten de un ContentProvider debemos hacer uso del meacutetodo query

de la clase ContentResolver El primero de los paraacutemetros de dicho meacutetodo seraacute la URIdel proveedor de contenidos al que deseamos acceder Este meacutetodo devuelve un cursorque nos permitiraacute iterar entre los resultados tal como se vio en la sesioacuten anterior en elapartado de cursores

ContentResolverquery(Uri uriString[] projectionString selectionString[] selectionArgsString sortOrder)

Por ejemplo la siguiente llamada nos proporcionaraacute un cursor que nos permitiraacute acceder atodos los contactos de nuestro teleacutefono moacutevil Para ello hemos hecho uso de la constante

Persistencia

25Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ContactsContractContactsCONTENT_URI que almacena la URI del proveedor decontenidos correspondiente Al resto de paraacutemetros se le ha asignado el valor null

ContentResolver cr = getContentResolver()Cursor cursor = crquery(ContactsContractContactsCONTENT_URI

null null null null)

NotaEn versiones anteriores a Android 20 las estructuras de datos de los contactos son diferentes y nose accede a esta URI

Una vez obtenido el cursor es posible mapear sus campos con alguacuten componente de lainterfaz graacutefica de la actividad De esta forma cualquier cambio que se produzca en elcursor se reflejaraacute automaacuteticamente en el componente graacutefico sin que sea necesario queprogramemos el coacutedigo que lo refresque Para esto tenemos que llamar al meacutetodosetNotificationUri del cursor con lo cual nos aseguraremos de que los listeners seraacutennotificados cuando se produzca alguacuten cambio en los datos referenciados por dicho cursor

cursorsetNotificationUri(crContactsContractContactsCONTENT_URI)

Para asignar un adaptador a una lista de la interfaz graacutefica de la actividad maacutesconcretamente una ListView utilizamos el meacutetodo setAdapter(Adapter)

ListView lv = (ListView)findViewById(RidListView01)SimpleCursorAdapter adapter = new SimpleCursorAdapter(

getApplicationContext()Rlayouttextviewlayoutcursornew String[]

ContactsContractContacts_IDContactsContractContactsDISPLAY_NAME

new int[]RidTextView1RidTextView2)

lvsetAdapter(adapter)

En este ejemplo los identificadores RidTextView1 y RidTextView2 se correspondencon views del layout que define cada fila de la ListView como se puede ver en elarchivo textviewlayoutxml que tendriacuteamos que crear

ltxml version=10 encoding=utf-8gt

ltLinearLayout xmlnsandroid=httpschemasandroidcomapkresandroidandroidorientation=horizontalandroidlayout_width=fill_parentandroidlayout_height=wrap_contentgt

ltTextView androidid=+idTextView1androidtextStyle=bold

Persistencia

26Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

androidems=2androidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltTextView androidid=+idTextView2androidtextStyle=boldandroidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltLinearLayoutgt

La lista en siacute (identificada por RidListView01 en el presente ejemplo) estariacutea en otroXML layout por ejemplo en mainxml

Se debe tener en cuenta que en el caso de este ejemplo si quisieacuteramos obtener losnuacutemeros de teleacutefono de cada uno de nuestros contactos tendriacuteamos que recorrer el cursorobtenido a partir del proveedor de contenidos y por cada persona en nuestra agenda crearun nuevo cursor que recorriera los teleacutefonos ya que una persona puede tener asignadosvarios teleacutefonos

Puedes acceder a un listado completo de los proveedores de contenidos nativos existentesen Android consultando su manual de desarrollo Concretamente leyendo la seccioacutendedicada al paquete androidprovider enhttpdeveloperandroidcomreferenceandroidproviderpackage-summaryhtml

322 Proveedores propios crear un nuevo proveedor de contenidos

Para acceder a nuestras fuentes de datos propias de manera estaacutendar nos interesaimplementar nuestros propios ContentProvider Gracias a que proporcionamos nuestrosdatos a partir de una URI quien use nuestro proveedor de contenidos no deberaacutepreocuparse de doacutende provienen los datos indicados por dicha URI podriacutean provenir deficheros locales en la tarjeta de memoria de un servidor en Internet o de una base dedatos SQLite Dada una URI nuestro proveedor de contenidos deberaacute proporcionar en suinterfaz los meacutetodos necesarios para realizar las operaciones baacutesicas en una base de datosinsercioacuten lectura actualizacioacuten y borrado

Para crear un nuevo proveedor de contenidos definimos una subclase deContentProvider Utilizaremos una sobrecarga del meacutetodo onCreate para crear einicializar la fuente de datos que queramos hacer puacuteblica a traveacutes de dicho proveedor Unesqueleto de subclase de ContentProvide podriacutea ser el siguiente

public class MiProveedor extends ContentProvider

Overridepublic boolean onCreate()

Inicializar la base de datosreturn true

Dentro de la clase deberemos crear un atributo puacuteblico y estaacutetico de nombreCONTENT_URI en el cual almacenaremos el URI completo del proveedor que estamos

Persistencia

27Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creando Este URI debe ser uacutenico por lo que una buena idea puede ser tomar como baseel nombre de paquete de nuestra aplicacioacuten La forma general de un URI es la siguiente

content[NOMBRE_PAQUETE]provider[NOMBRE_APLICACION][RUTA_DATOS]

Por ejemplo

contentesuajtechandroidprovidermiaplicacionelementos

En general (y por simplicidad) no es necesario antildeadir la cadena provider al nombre delpaquete por lo que podemos utilizar directamente el nombre del paquete seguido de laruta de datos de la forma

contentesuajtechandroidmiaplicacionelementos

Estos URIs se pueden presentar en dos formas La URI anterior representa una consultadirigida a obtener todos los valores de ese tipo (en este caso todos los elementos) Si a laURI anterior antildeadimos un sufijo [NUMERO_FILA] estamos representando una consultadestinada a obtener un uacutenico elemento (por ejemplo el quinto elemento en el siguienteejemplo)

contentesuajtechandroidprovidermiaplicacionelementos5

NotaSe considera una buena praacutectica de programacioacuten el proporcionar acceso a nuestro proveedor decontenidos utilizando cualquiera de estas dos formas

La forma maacutes simple de determinar cuaacutel de las dos formas anteriores de URI se utilizoacutepara hacer una peticioacuten a nuestro proveedor de contenidos es mediante un Uri MatcherCrearemos y configuramos un elemento Uri Matcher para analizar una URI y determinara cuaacutel de las dos formas se corresponde En el siguiente coacutedigo se muestra el coacutedigobaacutesico que deberemos emplear para implementar este patroacuten

public class MiProveedor extends ContentProvider

private static final String miURI =contentesuajtechandroidprovidermiaplicacionelementos

public static final Uri CONTENT_URI = Uriparse(miUri)

Creamos las constantes que nos van a permitir diferenciar entre los dos tipos distintos de peticiones a partir de la URIprivate static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

Inicializamos el objeto UriMatcher Una URI que termine en elementos se corresponderaacute con una consulta para todas las filas mientras que una URI con el sufijo elementos[FILA] representaraacute una uacutenica filastatic

uriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidprovidermiaplicacion

elementos TODAS_FILAS)

Persistencia

28Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

uriMatcheraddURI(esuajtechandroidprovidermiaplicacionelementos UNA_FILA)

Overridepublic boolean onCreate()

PENDIENTE inicializar la base de datosreturn true

Esta misma teacutecnica se puede emplear para proporcionar diferentes alternativas de URIpara diferentes subconjuntos de datos como por ejemplo diferentes tablas en una base dedatos a traveacutes del mismo proveedor de contenidos

323 Proveedores propios crear la interfaz de consultas

Para permitir realizar operaciones con los datos publicados a traveacutes de nuestro proveedorde contenidos deberemos implementar los meacutetodos delete insert update y query

para el borrado insercioacuten actualizacioacuten y realizacioacuten de consultas respectivamenteEstos meacutetodos son la interfaz estaacutendar utilizada con proveedores de contenidos de formaque para compartir datos entre aplicaciones no se tendraacuten interfaces distintos paradiferentes fuentes de datos

Lo maacutes habitual suele ser implementar un proveedor de contenidos como un mecanismopara permitir el acceso a una base de datos SQLite privada a una aplicacioacuten aunque atraveacutes de estos meacutetodos indicados se podriacutea en principio acceder a cualquier fuente dedatos

El siguiente coacutedigo extiende el ejemplo anterior (MiProveedor) y muestra el esqueleto deestos meacutetodos dentro de una subclase de ContentProvider Obseacutervese que se hace usodel objeto UriMatcher para determinar que tipo de consulta se estaacute realizando

Overridepublic Cursor query(Uri uri

String[] projectionString selectionString[] selectionArgsString sort)

Si se trata de una consulta para obtener una uacutenica filalimitamos

los resultados a obtener de la fuente de datos por medio de una claacuteusula whereswitch(UriMatchermatch(uri))

case UNA_FILA PENDIENTE modifica la sentencia SELECT

mediante una claacuteusula where obteniendo el identificador de fila

como numeroFila = urigetPathSegments()get(1))

return null

Override

Persistencia

29Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public Uri insert(Uri _uri ContentValues _valores) long idFila = [ Antildeadir un nuevo elemento ]

Devolvemos la URI del elemento recieacuten antildeadidoif (idFila gt 0)

return ContentUriswithAppendendId(CONTENT_URI idFila)

throw new SQLException(No se pudo antildeadir un nuevo elemento a +_uri)

Overridepublic int delete(Uri uri String where String[] whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

Overridepublic int update(Uri uri ContentValues valores String where String[]whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

324 Proveedores propios tipo MIME

El uacuteltimo paso para crear un proveedor de contenidos es definir el tipo MIME queidentifica el tipo de datos que devuelve el proveedor Debemos sobrecargar el meacutetodogetType para que devuelva una cadena que identifique nuestro tipo de datos de manerauacutenica Se deberiacutean definir dos tipos posibles uno para el caso de una uacutenica fila y otro parael caso de devolver todos los resultados Se debe utilizar una sintaxis similar a la delsiguiente ejemplo

bull Un uacutenico elementovndesuajtechandroidmiaplicacioncursoritemmiproveedorcontent

bull Todos los elementosvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

Como se puede observar la cadena comienza siempre por vnd A continuacioacutentendriacuteamos el nombre del paquete de nuestra aplicacioacuten Lo siguiente es cursor ya quenuestro proveedor de contenidos estaacute devolviendo datos de tipo Cursor Justo antes de labarra pondremos item en el caso del tipo MIME para un uacutenico elemento y dir para elcaso del tipo MIME para todos los elementos Finalmente tras la barra pondremos elnombre de nuestra clase (en minuacutesculas) seguido de content En el siguiente coacutedigo deejemplo se muestra coacutemo sobrecargar getType seguacuten la URI

Persistencia

30Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Overridepublic String getType(Uri _uri)

switch (uriMatchermatch(_uri)) case TODAS_FILAS

returnvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

case UNA_FILAreturn

vndesuajtechandroidmiaplicacioncursoritemmiproveedorcontentdefault

throw new IllegalArgumentException(URI nosoportada + _uri)

325 Proveedores propios registrar el proveedor

No debemos olvidar incorporar nuestro proveedor de contenidos al Manifest de laaplicacioacuten una vez que lo hemos completado Esto se haraacute por medio del elementoprovider cuyo atributo authorities podremos utilizar para especificar la URI base talcomo se puede ver en el siguiente ejemplo

ltprovider androidname=MiProveedorandroidauthorities=esuajtechandroidmiaplicaciongt

326 Content Resolvers

Aunque en la seccioacuten de proveedores nativos ya se ha dado alguacuten ejemplo de coacutemo haceruso de un Content Resolver para realizar una consulta ahora que hemos podido observaren detalle la estructura de un proveedor de contenidos propio es un buen momento paraver en detalle coacutemo realizar cada una de las operaciones que eacutestos permiten

El contexto de una aplicacioacuten incluye siempre una instancia de la claseContentResolver a la cual se puede acceder mediante el meacutetodo getContentResolver

ContentResolver cr = getContentResolver()

Esta clase incorpora diversos meacutetodos para realizar consultas a proveedores decontenidos Cada uno de estos meacutetodos acepta como paraacutemetro una URI que indica conqueacute proveedor de contenidos se desea interactuar

Las consultas realizadas sobre un proveedor de contenidos recuerdan a las consultasrealizadas sobre bases de datos Los resultados de las consultas se devuelven comoobjetos de la clase Cursor Se pueden extraer valores del cursor de la misma forma en laque se explicoacute en el apartado de bases de datos de la sesioacuten anterior El meacutetodo query dela clase ContentResolver acepta los siguientes paraacutemetros

bull La URI del proveedor del contenidos al que se desea realizar la consultabull Una proyeccioacuten que enumera las columnas que se quieren incluir en los resultados

obtenidosbull Una claacuteusula where que define las filas a obtener Se pueden utilizar comodines que

Persistencia

31Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

se reemplazaraacuten por valores incluidos en el siguiente paraacutemetrobull Una matriz de cadenas que se pueden utilizar para reemplazar los comodines en la

claacuteusula where anteriorbull Una cadena que describa la ordenacioacuten de los elementos devueltos

El siguiente coacutedigo muestra un ejemplo

ContentResolver cr = getContentResolver() Devolver todas las filasCursor todasFilas = crquery(MiProveedorCONTENT_URI null null nullnull) Devolver todas columnas de las filas para las que la columna 3 tiene un valor determinado ordenadas seguacuten el valor de la columna 5String where = COL3 + = + valorString orden = COL5Cursor algunasFilas = crquery(MiProveedorCONTENT_URI null where nullorden)

327 Otras operaciones con Content Resolvers

Para realizar otro tipo de operaciones con los datos de un proveedor de contenidosharemos uso de los meacutetodos delete update e insert de un objeto ContentResolver

Con respecto a la insercioacuten la clase ContentResolver proporciona en su interfaz dosmeacutetodos diferentes insert y bulkInsert Ambos aceptan como paraacutemetro la URI delproveedor de contenidos pero mientras que el primer meacutetodo tan solo toma comoparaacutemetro un objeto de la clase ContentValues (ya vistos en la seccioacuten de bases dedatos) el segundo toma como paraacutemetro una matriz de elementos de este tipo El meacutetodoinsert devolveraacute la URI del elemento recieacuten antildeadido mientras que bulkInsert

devolveraacute el nuacutemero de filas correctamente insertadas

Obtener el Content ResolverContentResolver cr = getContentResolver()

Crear una nueva fila de valores a insertarContentValues nuevosValores = new ContentValues()

Asignar valores a cada columnanuevosValoresput(NOMBRE_COLUMNA nuevoValor)[ Repetir para cada columna ]

Uri miFilaUri = crinsert(MiProveedorCONTENT_URI nuevosValores)

Insertamos ahora una matriz de nuevas filasContentValues[] arrayValores = new ContentValues[5] PENDIENTE rellenar el array con los valores correspondientesint count = crbulkInsert(MiProveedorCONTENT_URI arrayValores)

Para el caso del borrado hacemos uso del meacutetodo delete del Content Resolver pasandocomo paraacutemetro la URI de la fila que deseamos eliminar de la fuente de datos Otraposibilidad es incluir una claacuteusula where para eliminar muacuteltiples filas Ambas teacutecnicas semuestran en el siguiente ejemplo

ContentResolver cr = getContentResolver()

Persistencia

32Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Eliminar una fila especiacuteficacrdelete(miFilaUri null null) Eliminar las primeras cinco filasString where = _id lt 5crdelete(MiProveedorCONTENT_URI where null)

Una actualizacioacuten se lleva a cabo mediante el meacutetodo update del objeto ContentResolver Este meacutetodo toma como paraacutemetro la URI del proveedor de contenidosobjetivo un objeto de la clase ContentValues que empareja nombres de columna convalores actualizados y una claacuteusula where que indica queacute filas deben ser actualizadas Elresultado de la actualizacioacuten seraacute que en cada fila en la que se cumpla la condicioacuten de laclaacuteusula where se cambiaraacuten los valores de las columnas especificados por el objeto de laclase ContentValues tambieacuten se devolveraacute el nuacutemero de filas correctamenteactualizadas A continuacioacuten se muestra un ejemplo

ContentValues nuevosValores = new ContentValues()

Utilizamos el objeto ContentValues para especificar en queacute columnas queremos que se produzca un cambio y cuaacutel debe ser el nuevo valor de dichas columnasnuevosValuesput(NOMBRE_COLUMNA nuevoValor)

Aplicamos los cambios a las primeras cinco filasString where = _id lt 5

getContentResolver()update(MiProveedorCONTENT_URI nuevosValores wherenull)

Persistencia

33Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

4 Ejercicios - Persistencia en Android proveedores de contenidos ySharedPreferences

41 Compartir datos entre actividades con Shared Preferences (075 puntos)

Descarga de las plantillas el proyecto Dni Dicho proyecto estaacute compuesto de dosactividades Formulario y Resumen El objetivo del ejercicio es conseguir que al pulsar elbotoacuten Siguiente en la actividad Formulario se muestre en la actividad Resumen cuaacutelesfueron los datos introducidos

En este ejercicio vamos a hacer uso de SharedPreferences para pasar los datos de unaactividad a la siguiente Al pulsar en Siguiente en Formulario deberemos guardar el valorde todos los campos mediante esta plataforma Al mostrarse la actividad Resumendeberemos leer estos datos tambieacuten a partir de SharedPreferences para mostrarlos porpantalla

Por uacuteltimo crea un meacutetodo que valide el DNI (debe tratarse de una secuencia de ochodiacutegitos entre 0 y 9 y una letra al final) Si se pulsa Siguiente y el DNI no tiene el formatocorrecto no se deberaacute pasar a la actividad Resumen En lugar de esto se mostraraacute unToast en pantalla con un mensaje de error

42 Actividad de preferencias (075 puntos)

En este ejercicio seguimos trabajando con el proyecto del ejercicio anterior Vamos aantildeadir un menuacute de opciones que permita escoger en queacute momento se comprobaraacute lavalidez del DNI Para ello antildeade en primer lugar un menuacute a la actividad Formulario conuna uacutenica opcioacuten cuyo nombre seraacute Opciones

Al seleccionar esta opcioacuten se deberaacute mostrar una actividad de preferencias cuyo aspectoseraacute el siguiente

Persistencia

34Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Preferencias de la aplicacioacuten Dni

El significado de las opciones seraacute el siguiente

bull En el campo con esta opcioacuten seleccionada al pulsar sobre el campo de DNI en elformulario deberaacute mostrarse un Toast indicando si la sintaxis del DNI es correcta ono

bull Al pulsar si se desactiva esta opcioacuten (que deberaacute estar activada por defecto) no seharaacute la comprobacioacuten del DNI al pulsar el botoacuten Siguiente por lo que siempre seraacuteposible pasar de la actividad Formulario a la actividad Resumen sea cual sea elformato del DNI introducido

bull El uacuteltimo check box se encontraraacute siempre deshabilitado iquestQueacute atributo debemosutilizar para conseguir esto

Crea dos variables booleanas llamadas enElCampo y alPulsar Su valor dependeraacute delestado de los checkboxes anteriores y se actualizaraacute por medio del eventoonSharedPreferenceChange Utiliza el valor de estas variables en el coacutedigo de tuactividad para que esta tenga el comportamiento indicado

43 Proveedor de contenidos propio (075 puntos)

Vamos a implementar otra forma de acceder a la base de datos de usuarios de laaplicacioacuten BaseDatos desarrollada durante los ejercicios de la sesioacuten anterior siguiendoesta vez el patroacuten de disentildeo ContentProvider de Android Seguiremos trabajando puescon dicho proyecto

bull Creamos una nueva clase llamada UsuariosProvider que herede deContentProvider Esto nos obligaraacute a sobrecargar una serie de meacutetodos abstractosAntes de implementar la query vamos a configurar el provider

bull Antildeadimos algunos campos tiacutepicos de los content provider

Persistencia

35Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public static final Uri CONTENT_URI =Uriparse(contentesuajtechandroidbasedatosusuarios)

private static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

staticuriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

TODAS_FILAS)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

UNA_FILA)

bull Vamos a acceder a la misma base de datos de usuarios utilizada en los ejercicios de lasesioacuten anterior pero no vamos a hacerlo a traveacutes del adaptador que tuvimos queimplementar sino que vamos a copiar de eacutel el coacutedigo que nos haga falta Copia loscampos que definen el nombre de la base de datos de la tabla de las columnas laversioacuten asiacute como la referencia al contexto y a la base de datos La sentenciacompilada del insert ya no va a hacer falta Inicializa los valores necesarios en elconstructor Para inicializar la referencia a la base de datos vamos a utilizar una vezmaacutes MiOpenHelper Podemos copiarlo del adaptador que ya implementamos en losejercicios de la sesioacuten anterior

bull Implementa de forma apropiada el getType para devolver un tipo MIME diferenteseguacuten si se trata de una URI de una fila o de todas las filas Para ello ayuacutedate deluriMatcher

bull Implementa el meacutetodo query Simplemente se trata de devolver el cursor queobtenemos al hacer una consulta a la base de datos SQLite Algunos de los paraacutemetrosque le pasaremos los recibimos como paraacutemetros del meacutetodo del provider Los que notengamos iraacuten con valor null

bull Aunque no tenemos todos los meacutetodos del UsuariosProvider implementadospodemos probarlo Para ello debemos registarlo en el AndroidManifestxml

ltprovider androidname=UsuariosProvider

androidauthorities=esuajtechandroidbasedatosgtltapplicationgt

ltmanifestgt

bull En la clase Main se antildeadioacute codigo para insertar una serie de valores en la base dedatos y mostrarlos en el campo de texto Manteniendo este coacutedigo vamos a antildeadir alcampo de texto el resultado obtenido con la consulta del UsuariosProvider paracomprobar que tanto el adaptador de la base de datos como el proveedor nosdevuelven el mismo resultado

44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)

Porque es la forma estaacutendar que establece Android de acceder a contenidos Ademaacutes elproveedor de contenidos nos permitiraacute notificar al ContentResolver de los cambiosocurridos Asiacute componentes en la pantalla podraacuten refrescarse de forma automaacutetica

Persistencia

36Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Utiliza el proyecto ProveedorContenidos de las plantillas Implementa la insercioacuten enel proveedor de contenidos Prueacutebala insertando algunos usuarios de ejemplo en laclase Main Implementa tambieacuten el OnClickListener del botoacuten que inserta nuevosusuarios El nombre del nuevo usuario iraacute indicado en el EditText

bull Comprueba que la insercioacuten funciona y que gracias a la siguiente liacutenea y al estarusando un proveedor de contenidos la lista se actualiza automaacuteticamente cuandoocurre alguacuten cambio sin necesidad de pedir expliacutecitamente la actualizacioacuten al pulsarel botoacuten

cursorsetNotificationUri(cr UsuariosProviderCONTENT_URI)

NotaEsto no va a funcionar si se notifica que se ha producido un cambio al insertar o borrar unelemento Para ello usamosgetContext()getContentResolver()notifyChange(UsuariosProviderCONTENT_URI null)

bull Implementa el meacutetodo delete del proveedor de contenidos Prueacutebalo en la claseMain Termina de implementar el onCreateContextMenuListener que se ejecutaraacutecada vez que se haga una pulsacioacuten larga sobre alguna entrada de la lista Compruebaque funciona (eliminando el usuario correspondiente y no otro)

Persistencia

37Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

5 Persistencia de datos en iOS Ficheros y SQLite

51 Introduccioacuten

La persistencia de datos en iOS se realiza en una pequentildea memoria flash que tienenuestro dispositivo la cual es equivalente a un disco duro de tamantildeo limitado Los datosque ahiacute se almacenen se conservaraacuten aunque el dispositivo se apague Por motivos deseguridad el SO de iOS no permite que las aplicaciones accedan directamente a lamemoria interna pero a cambio existe una API completa de acceso a la porcioacuten dememoria que le corresponde a la app que desarrollemos

En una aplicacioacuten iOS encontraremos baacutesicamente 4 modos de almacenamiento deinformacioacuten de forma persistente

bull Ficheros de propiedades (plist)bull Bases de datos embebidas SQLitebull Datos de usuario (User Defaults)bull Core Data

En esta primera sesioacuten veremos coacutemo usar los ficheros de texto (Property Lists) y elalmacenamiento en base de datos de tipo SQLite

52 Ficheros plist (Property Lists)

El almacenamiento de datos en ficheros de propiedades es uno de los maacutes usados en eldesarrollo de las aplicaciones para iOS Se usa principalmente para guardar datos deconfiguracioacuten de la aplicacioacuten u otra informacioacuten poco compleja y acceder a ella deforma sencilla Si estamos desarrollando un juego podemos usarlos para la definicioacuten delos niveles almacenar las puntuaciones del jugador etc

521 Leyendo datos desde ficheros plist

Los ficheros plist (Property Lists) tienen un formato XML aunque XCode nosproporciona una interfaz que nos permite acceder a ellos de forma muy sencilla Paraentender el funcionamiento de este tipo de ficheros vamos a realizar un ejemplo en el queprimero crearemos un fichero plist lo completaremos con datos de ejemplo y despueacutes loleeremos para mostrarlo por pantalla

Un ejemplo de un fichero plist podriacutea ser el siguiente

Persistencia

38Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Fichero plist

Antes de nada creamos un proyecto nuevo en XCode de tipo Window-based

Application al que llamaremos sesion03-ejemplo1 Para crear un fichero plistdebemos hacer click derecho sobre el directorio Supporting Files de nuestro proyectoy seleccionamos iOS gt Resource gt Property List Lo guardaremos con el nombreconfigUsuario Seguidamente ya podemos acceder al fichero recieacuten creado haciendoclick sobre el en este momento XCode nos mostraraacute un pequentildeo editor totalmente vacioen el que iremos rellenando con datos Podemos verlo tambieacuten en formato XML sihacemos click derecho sobre el y seleccionamos Open As gt Preview

Para empezar a rellenar el fichero que acabamos de crear hacemos click derecho dentrode su contenido (ahora vacio) y seleccionamos Add row Entonces nos apareceraacute unafila vaciacutea Dentro del campo Key escribimos la clave un nombre descriptivo quedeberemos utilizar maacutes adelante para acceder a los datos En nuestro caso vamos aescribir config como clave Dentro de la columna Type seleccionamosDictionary Veremos que aparece una flecha en el lado izquierdo que indica quepueden haber subniveles dentro de nuestro diccionario

Ahora antildeadimos un nuevo item que cuelgue del diccionario que acabamos de crear paraello hacemos click sobre la flecha de la izquierda esta se giraraacute hacia abajo entonceshacemos click ahora sobre el siacutembolo + (maacutes) Nos apareceraacute una nueva liacutenea que cuelgadel diccionario

En esta nueva liacutenea dentro del campo key escribimos nombre el campo type lodejamos como de tipo String ya que va a ser una cadena de texto y en la columna deValue escribimos nuestro nombre por ejemplo Javi

Ahora vamos a antildeadir otro campo nuevo en nuestra configuracioacuten para ello hacemosclick sobre el siacutembolo + y nos apareceraacute una nueva liacutenea justo al mismo nivel que laanterior y colgando del diccionario Dentro del campo de Key escribimos ciudad yen Value escribimos Alicante El campo de Type lo dejamos como String

Persistencia

39Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora dentro de nuestro fichero plist vamos a indicar los dispositivos que dispone elusuario para ello creamos una nueva linea dentro del diccionario y le pondremos comoclave dispositivos Seleccionamos el tipo Array y seguimos los mismos pasosque al crear el diccionario para ello hacemos click sobre la flecha de la izquierda ydespueacutes sobre el siacutembolo + (maacutes) Ahora nos apareceraacute una nueva fila con clave Item 0Seleccionamos el tipo String y en el campo Value iPhone

Ahora repetimos el paso 3 veces indicando como values iPad Android y

Blackberry

Una vez seguidos todos estos pasos tendremos nuestro fichero de propiedades listo Debede quedar como se muestra a continuacioacuten

Fichero plist terminado

Este fichero se almacenaraacute dentro del directorio de bundle cuando compilemos laaplicacioacuten En el caso de que queramos escribir en el debermos almacenar el ficherodentro del directorio de Documents del binario

Ahora desde nuestro coacutedigo vamos a acceder a eacutel y a mostrarlo por pantalla para elloescribimos el siguiente coacutedigo dentro del meacutetodo didFinishLaunchingWithOptions dela clase delegada

Cargamos el fichero PLISTNSString mainBundlePath = [[NSBundle mainBundle] bundlePath]NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]NSDictionary diccionario = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionario delraiz

NSString nombre = [dicConfig objectForKeynombre]

Persistencia

40Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

[selfwindow makeKeyAndVisible]return YES

Si todo ha ido correctamente deberemos obtener la siguiente salida por la consola

Lista de dispositivos

522 Escribiendo datos en ficheros plist

Partiendo del ejemplo anterior vamos a escribir ahora en el fichero para ello primerodebemos comprobar que el fichero plist se encuentre dentro del directorio de Documents

del dispositivo en el caso de que no lo esteacute (por ejemplo si es la primera vez que arrancala aplicacioacuten) debemos copiarlo a ese directorio Una vez que tenemos el fichero dentrodel directorio de documentos ya podemos escribir en el

Para realizar todo el proceso de comprobacioacuten de la existencia del fichero dentro deldirectorio de documentos copiarlo en ese directorio y leer los datos debemos de sustituirel coacutedigo que hay dentro del meacutetodo didFinishLaunchingWithOptions de la clasedelegada por este otro

AtencioacutenPara que funcione la escritura en un fichero plist este debe de estar dentro del directorio deDocuments de nuestro dispositivo iOS no en el boundle

NSDictionary diccionarioRaiz

Ruta del directorio de documentos de nuestro dispositivoNSArray paths =

NSSearchPathForDirectoriesInDomains(NSDocumentDirectoryNSUserDomainMask YES)

if ([paths count] gt 0)

Persistencia

41Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

Primero comprobamos si existe el fichero en eldirectorio de documentos

BOOL fileExists = [[NSFileManager defaultManager]fileExistsAtPathdocumentsFilename]if (fileExists)

NSLog(Fichero encontrado en directorio de documentosOK

documentsFilename)

Si existe ya cargamos los datos desde aquidirectamente

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

else

NSLog(No se encuentra el fichero configUsuarioplisten

el directorio de documentos-gtLo creamos)

Si no existe primero cargamos el fichero PLISTdesde el Boundle

NSString mainBundlePath = [[NSBundle mainBundle]bundlePath]

NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Y despues escribimos los datos cargados (eldiccionario completo)

a un fichero nuevo de la carpeta de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

NSLog(plist de configUsuario creado)

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionariodel raiz

NSString nombre = [dicConfig objectForKeynombre]NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

Persistencia

42Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Por ultimo liberamos la memoria del diccionario raiz[diccionarioRaiz release]

[selfwindow makeKeyAndVisible]return YES

Para comprobar el funcionamiento arrancamos la aplicacioacuten y veremos que la primera vezse crearaacute el fichero dentro de la carpeta de Documents de nuestro dispositivo y en lassiguientes ya no se crearaacute y simplemente lo leeraacute desde la carpeta de Documents

Ahora que ya tenemos la gestioacuten de ficheros baacutesica completada vamos a escribir sobre elalguacuten dato Para ello simplemente escribimos el siguiente fragmento de coacutedigo justo antesde la liacutenea [selfwindow makeKeyAndVisible]

Cambiamos el nombre y lo guardamos en el fichero Ruta del directorio de documentos de nuestro dispositivo

if ([paths count] gt 0)

Cargamos el fichero desde el directorio dedocumentos del dispositivo

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cambiamos el valor del nombre en el diccionario[dicConfig setValueOtro nombre forKeynombre]

Escribimos todo el diccionario de nuevo al fichero del directorio de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

Por ultimo liberamos el diccionario de lamemoria

[diccionarioRaiz release]

Lo que hemos hecho en el coacutedigo anterior es cargar todo el diccionario de nuevo desde eldirectorio de documentos modificar los datos que queremos cambiar o incluso antildeadir ydespueacutes guardar el diccionario en el fichero

Volvemos a arrancar la aplicacioacuten y veremos que ha cambiado el campo de nombre

Nota

Persistencia

43Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

La lectura y escritura de datos en un fichero plist se debe de hacer de una soacutela vez nunca porpartes Siempre se debe de cargar o escribir un diccionario NSDictionary como elementoprincipal del fichero

53 SQLite

SQLite es una base de datos relacional ligera con la que se puede trabajar de formasencilla mediante sentencias SQL La base de datos se almacenaraacute en un fichero dentrode nuestra aplicacioacuten Este meacutetodo de persistencia puede ser el adecuado cuando los datosa almacenar esteacuten en un formato de tablas filas y columnas y se necesite que los datossean accesibles de forma raacutepida

531 Herramientas disponibles

Para explicar el funcionamiento de las librerias de SQLite de Cocoa Touch vamos a seguirun ejemplo completo Comenzamos creando la base de datos para ello podemos utilizaruno de los frontends que facilitan la gestioacuten del SQLite uno bastante sencillo yrecomendado es el SQLite Database Browser y otro es fmdb

Nosotros utilizaremos el primero (SQLite Database Browser) Abrimos la aplicacioacuten yhacemos click sobre el botoacuten de crear nueva base de datos que llamaremospersonasDBsqlite Seguidamente creamos las tablas para simplificar en nuestroejemplo crearemos una soacutela tabla llamada personas Esta tabla tendraacute las siguientescolumnas id (NUMERIC) nombre (TEXT) apellidos (TEXT) dni (TEXT) edad

(NUMERIC) y localidad (TEXT)

Persistencia

44Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Estructura de la Base de Datos

Una vez creada la tabla pasamos a insertar datos dentro de ella para ello abrimos lapestantildea de Browse Data y hacemos click en New Record Cuando tengamos al menos 3registros metidos dentro de la tabla ya podemos guardar la base de datos Hacemos clicksobre guardar le ponemos el nombre que queramos y elegimos cualquier directorio delMAC

Datos de la Base de Datos

532 Lectura de datos

Una vez que tenemos la base de datos SQLite creada ya podemos empezar a escribir elcoacutedigo para leer y mostrar los datos dentro de nuestra aplicacioacuten

Primero empezamos creando un proyecto nuevo en XCode usando la plantilla basada envistas View-based application y lo llamaremos sesion03-ejemplo2 Ahora antildeadimosel framework de sqlite a nuestro proyecto para ello hacemos click sobre el raiz delproyecto y seleccionamos la pestantildea Build Phases Desplegamos el listado de Link

Binary With Libraries y hacemos click sobre el botoacuten (+) El el listadoseleccionamos el framework libsqlite3dylib y hacemos click en Add

Persistencia

45Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Antildeadiendo el framework libsqlite3dylib

Ahora antildeadimos a nuestro proyecto la base de datos que hemos creado anteriormentepara ello arrastramos el fichero personasDBsqlite a nuestro directorio Supporting

Files Asegurate de seleccionar la opcioacuten de Copy items to destination groups

folder (if needed) para que se copie el fichero dentro de la carpeta del proyecto

Para acelerar el proceso de lectura de la base de datos y ahorrar memoria vamos a cargarprimero todos los datos en objetos para ello vamos a crear una clase que se encargue dealmacenar los datos Hacemos click derecho sobre el raiz del proyecto New file gtObjective-C class seleccionamos NSObject como clase principal y le damos a NextEscribimos como nombre de la clase Persona y hacemos click en save

Personah

imports iniciales

interface Persona NSObject int _uIdNSString _nombreNSString _apellidos

Persistencia

46Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSInteger _edadNSString _localidad

property (nonatomic assign) int uIdproperty (nonatomic copy) NSString nombreproperty (nonatomic copy) NSString apellidosproperty (nonatomic) NSInteger edadproperty (nonatomic copy) NSString localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidos edad(NSInteger)edadlocalidad(NSString )localidad

end

Personam

import Personah

implementation Persona

synthesize uId = _uIdsynthesize nombre = _nombresynthesize apellidos = _apellidossynthesize edad = _edadsynthesize localidad = _localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidosedad(NSInteger)edad localidad(NSString )localidad

if ((self = [super init])) selfuId = uIdselfnombre = nombreselfapellidos = apellidosselfedad = edadselflocalidad = localidad

return self

- (void) dealloc selfnombre = nilselfapellidos = nilselflocalidad = nil[super dealloc]

end

Una vez creada la clase Persona vamos a crear la clase encargada de manejar la basede datos sqlite3 Otra opcioacuten seriacutea cargar todos los datos directamente desde la clasedelegada al arrancar la aplicacioacuten pero no es recomendable ya que si en alguacuten momentotenemos que cambiar de motor de base de datos lo tendremos maacutes complicado

Persistencia

47Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creamos entonces una clase dentro de nuestro proyecto llamada BDPersistencia queherede de NSObject y escribimos el siguiente coacutedigo

BDPersistenciah

imports iniciales

interface BDPersistencia NSObject sqlite3 _database

+ (BDPersistencia)database- (NSArray )arrayPersonas

end

BDPersistenciam

import BDPersistenciahimport Personah

implementation BDPersistencia

static BDPersistencia _database

+ (BDPersistencia)database if (_database == nil)

_database = [[BDPersistencia alloc] init]return _database

- (id)init if ((self = [super init]))

NSString sqLiteDb = [[NSBundle mainBundle]pathForResourcepersonasDB

ofTypesqlite]

if (sqlite3_open([sqLiteDb UTF8String]amp_database) = SQLITE_OK)

NSLog(Fallo al abrir la BD)

return self

- (void)dealloc sqlite3_close(_database)[super dealloc]

- (NSArray )arrayPersonas

NSMutableArray arraySalida = [[[NSMutableArray alloc]init]

autorelease]

Persistencia

48Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString query = SELECT id nombre apellidoslocalidad edad

FROM personas ORDER BY apellidos DESC

sqlite3_stmt statementif (sqlite3_prepare_v2(_database [query UTF8String]

-1ampstatement nil)

== SQLITE_OK) while (sqlite3_step(statement) == SQLITE_ROW)

int uniqueId = sqlite3_column_int(statement0)

char nombreChar = (char )sqlite3_column_text(statement 1)

char apellidosChar = (char )sqlite3_column_text(statement 2)

char localidadChar = (char )sqlite3_column_text(statement 3)

int edad = sqlite3_column_int(statement 4)

NSString nombre = [[NSString alloc]initWithUTF8StringnombreChar]NSString apellidos = [[NSString alloc]initWithUTF8StringapellidosChar]NSString localidad = [[NSString alloc]initWithUTF8StringlocalidadChar]Persona persona = [[Persona alloc]initWithUiduniqueId nombrenombreapellidosapellidos edadedad

localidadlocalidad]

[arraySalida addObjectpersona][nombre release][apellidos release][localidad release][persona release]

sqlite3_finalize(statement)

return arraySalida

end

Con esto ya podemos leer de nuestra base de datos las personas Para ver que funcionatodo a la perfeccioacuten escribimos el siguiente coacutedigo dentro de la clase delegada en elmeacutetodo applicationDidFinishLaunching Antes debemos incluir en la parte superiorlas clases creadas

import BDPersistenciahimport Personah

NSArray personas = [BDPersistenciadatabase]arrayPersonas

for (Persona persona in personas) NSLog(d d personauId

personanombrepersonaapellidos personalocalidad personaedad)

Persistencia

49Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Si ejecutamos el proyecto obtendremos la siguiente salida por consola

Listado de personas

533 Mostrando los datos en una tabla

Una vez que tenemos el coacutedigo para obtener todas las filas de la tabla de personas de labase de datos queda lo maacutes faacutecil mostrarlas dentro de una vista de la aplicacioacuten En estecaso utilizaremos la vista de tabla UITableView

Comenzamos creando una nueva vista en nuestro proyecto para ello hacemos clickderecho sobre el raiz y seleccionamos New File gt UIVIewController SubclassAsegurate de marcar Subclass of UITableViewController y la opcoacuten de With

XIB for user interface Hacemos click en Next y guardamos el fichero comoPersonasViewController por ejemplo

Ahora abrimos el fichero PersonasViewControllerh y antildeadimos un NSArray queseraacute el que almacene las personas de la base de datos

imports iniciales

interface PersonasViewController UITableViewController

NSArray _listadoPersonas

property (nonatomic retain) NSArray listadoPersonas

end

Ahora abrimos el fichero PersonasViewControllerm antildeadimos el syntetizeimportamos los ficheros de gestioacuten de la base de datos la clase Persona y todo el coacutedigobaacutesico para la gestioacuten de la vista de la tabla

import PersonasViewControllerhimport Personahimport BDPersistenciah

implementation PersonasViewController

synthesize listadoPersonas = _listadoPersonas

Persistencia

50Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

- (id)initWithStyle(UITableViewStyle)style

self = [super initWithStylestyle]if (self)

Custom initializationreturn self

- (void)dealloc

[super dealloc]

selflistadoPersonas = nil

- (void)didReceiveMemoryWarning

Releases the view if it doesnt have a superview[super didReceiveMemoryWarning]

Release any cached data images etc that arent inuse

pragma mark - View lifecycle

- (void)viewDidLoad

[super viewDidLoad]

selftitle = Listado de personas

selflistadoPersonas = [BDPersistenciadatabase]arrayPersonas

- (void)viewDidUnload

[super viewDidUnload] Release any retained subviews of the main view eg selfmyOutlet = nil

- (void)viewWillAppear(BOOL)animated

[super viewWillAppearanimated]

- (void)viewDidAppear(BOOL)animated

[super viewDidAppearanimated]

- (void)viewWillDisappear(BOOL)animated

[super viewWillDisappearanimated]

- (void)viewDidDisappear(BOOL)animated

[super viewDidDisappearanimated]

- (BOOL)shouldAutorotateToInterfaceOrientation

Persistencia

51Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

(UIInterfaceOrientation)interfaceOrientation

Return YES for supported orientationsreturn (interfaceOrientation ==

UIInterfaceOrientationPortrait)

pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView(UITableView)tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection

(NSInteger)section

Return the number of rows in the sectionreturn [selflistadoPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath

(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]if (cell == nil)

cell = [[[UITableViewCell alloc]initWithStyleUITableViewCellStyleSubtitle

reuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona persona = (Persona )[selflistadoPersonas

objectAtIndexindexPathrow]celltextLabeltext = [NSString stringWithFormat

(d) personanombrepersonaapellidos personaedad]celldetailTextLabeltext = personalocalidad

return cell

pragma mark - Table view delegate

- (void)tableView(UITableView )tableViewdidSelectRowAtIndexPath

(NSIndexPath )indexPath

Navigation logic may go here Create and pushanother view controller

end

Persistencia

52Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora soacutelo nos queda incorporar la vista que acabamos de crear dentro de nuestraaplicacioacuten Para ello vamos a llamarla desde el Delegate y a meterla dentro de unNavigation Controller Primero debemos de antildeadir un Outlet en la clasedelegada hacia un Navigation Controller

imports iniciales

interface sesion03_ejemplo1AppDelegate NSObjectltUIApplicationDelegategt

UIWindow _windowUINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet

UINavigationController navController

end

Ahora hacemos click sobre la vista MainWindowxib y arrastramos un Navigation

Controller desde el listado de la columna de la derecha hacia la columna de laizquierda donde pone Objects Nos apareceraacute el Navigation Controller dibujadoen pantalla Ahora desplegamos el Navigation Controller y hacemos click sobre elView Controller que hay dentro Nos vamos a la pestantildea de Identity Inspector

de la columna de la derecha y escribimos dentro de Class el nombre de la vista dellistado de personas PersonasViewController

Finalmente hacemos click sobre el objeto Delegate vamos a la pestantildea de Show

Connections Inspector y arrastramos desde navController hasta el objetoNavigation Controller de la columna de la izquierda Ya tenemos los controladoresy las vistas conectadas

Una vez hecho esto nos queda indicar dentro de nuestro coacutedigo que la aplicacioacuten arranquecon el Navigation Controller para ello escribimos lo siguiente al final del meacutetododidFinishLaunchingWithOptions de la implementacioacuten de la clase delegada

selfwindowrootViewController = selfnavController[selfwindow makeKeyAndVisible]return YES

Ya estaacute listo todo y preparado para compilar Si ejecutamos el proyecto veremos que nosaparece el listado de personas en la vista de tabla

Persistencia

53Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios

61 Creando y leyendo un fichero plist (15 puntos)

En este primer ejercicio vamos a crear nuestra primera aplicacioacuten usando el meacutetodo depersistencia de ficheros La aplicacioacuten mostraraacute en una vista de tabla TableView unlistado de series en la que si seleccionamos una veremos su informacioacuten maacutes importanteen otra vista Comenzaremos creando un fichero de propiedades (plist) siguiendo unaestructura determinada y posteriormente mostraremos en una vista de tabla todos susdatos iexclComenzamos

1) Crear un proyecto usando la plantilla Master-Detail application Lo llamaremosejercicio-plist-ios como identificador de empresa escribiremos esuajtech y como prefijode clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremos UseAutomatic Reference Counting para olvidarnos de la gestioacuten de memoria El resto deopciones las dejaremos sin marcar

2) Crear un archivo de propiedades (plist) con el nombre de series

3) Editar el archivo plist creando la siguiente estructura de datos (5 series miacutenimo)

Plist series

4) Rellenar el archivo plist con datos de series reales

5) Escribir el coacutedigo necesario en UAMasterViewControllerm que recorra el archivoplist que acabamos de crear y muestre por consola los titulos de las series

6) Mostrar en la tabla que hay implementada los tiacutetulos de la serie Para ello deberemosde completar todos los meacutetodos del protocolo del TableView

7) Ejecutar la aplicacioacuten y comprobar que funciona correctamente

Persistencia

54Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

62 Implementar vista de detalle de la serie (05 puntos)

Dentro del proyecto anterior (no hace falta crear uno nuevo) vamos a mostrar toda lainformacioacuten adicional de cada una de las series en una vista de detalle Cuandoseleccionemos una fila de la tabla nos deberaacute aparecer una nueva vista con el resto deinformacioacuten (antildeo director principal sinopsis y duracioacuten)

63 Uso baacutesico de una base de datos SQLite (1 punto)

En este ejercicio vamos a crear la misma aplicacioacuten que en el ejercicio anterior perousando el meacutetodo de persistencia de SQLite Para completar el ejercicio deberemos seguirlos siguientes pasos

1) Crear un proyecto usando la plantilla Master-Detail application lo llamaremosejercicio-sqlite-ios como identificador de empresa escribiremos esuajtech y comoprefijo de clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremosUse Automatic Reference Counting para olvidarnos de la gestioacuten de memoria El restode opciones las dejaremos sin marcar

2) Nos descargamos e instalamos el programa SQLite Database Browser desde estadireccioacuten

3) Disentildear la estructura de la base de datos usando SQLite Database Browser tal y comose muestra en la imagen siguiente

Sqlite series

4) Insertar al menos 5 series en la base de datos usando el programa SQLite DatabaseBrowser

5) Cargar todos los datos de la base de datos desde el meacutetododidFinishLaunchingWithOptions de la clase UAAppDelegate Mostrar por consola losdatos cargados

6) Mostrar los datos cargados en el paso anterior en la vista de tabla UITableView quehay creada en la clase UAMasterViewController

Persistencia

55Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7) Arrancar la aplicacioacuten y comprobar que se muestran las series en la tabla de formacorrecta

Persistencia

56Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7 Persistencia de datos en iOS User Defaults y Core Data

71 Introduccioacuten

En esta sesioacuten veremos como utilizar dos nuevos meacutetodos de persistencia en iOS ambosmuy interesantes y usados muy frecuentemente

bull User Defaults Muy uacutetil para almacenar datos simples y de uso repetido dentro denuestras aplicaciones iOS

bull Core Data Meacutetodo avanzado de almacenamiento de objetos enteros dentro de lamemoria del dispositivo iOS Muy uacutetil para almacenar datos complejos conrelaciones etc

72 User Defaults

Este meacutetodo es muy uacutetil cuando queremos almacenar pequentildeas cantidades de datos comopuntuaciones informacioacuten de usuario el estado de nuestra aplicacioacuten configuracionesetc

Este meacutetodo es el maacutes raacutepido de usar ya que no requiere de una base de datos ni ficherosni ninguna otra capa intermedia Los datos se almacenan directamente en la memoria deldispositivo dentro de un objeto de la clase NSUserDefaults

Para aprender a usar este meacutetodo de almacenamiento de datos vamos a crear unaaplicacioacuten muy simple que lea un nombre y una edad de pantalla y las guarde en unobjeto NSUserDefaults Para ello seguimos los siguientes pasos

bull 1) Comenzamos creando un nuevo proyecto llamado sesion04-ejemplo1 dentrode XCode

bull 2) Abrimos la vista sesion04_ejemplo1ViewController y arrastramos dos TextField un botoacuten y dos labels quedando la vista de la siguiente manera

Persistencia

57Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista de la aplicacioacuten

bull 3) Abrimos el fichero sesion04_ejemplo1ViewControllerh y definimos el Outletdel evento onclick del botoacuten El fichero quedaraacute de la siguiente manera

imports iniciales

interface sesion04_ejemplo1ViewController UIViewController UITextField tfNombreUITextField tfEdad

property(nonatomic retain) IBOutlet UITextField tfNombreproperty(nonatomic retain) IBOutlet UITextField tfEdad

-(IBAction) guardaDatos

end

bull 4) Ahora implementamos el coacutedigo para el meacutetodo del botoacuten dentro desesion04_ejemplo1ViewControllerm

synthesize tfEdadsynthesize tfNombre

-(IBAction) guardaDatos NSLog(Guardamos los datos)

Persistencia

58Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creamos el objeto NSUserDefaultsNSUserDefaults datos = [NSUserDefaults standardUserDefaults]

guardamos el nombre (NSString)[datos setObjecttfNombretext forKeynombre]

guardamos la edad (Integer)[datos setInteger[tfEdadtext integerValue] forKeyedad]

almacenamos los datos en la memoria[datos synchronize]

bull 5) Tenemos que enlazar el botoacuten con el evento que hemos programado en el pasoanterior para ello abrimos la vista sesion04_ejemplo1ViewControllerxib y conla tecla ctlr apretada arrastramos desde el botoacuten hasta Files Owner (columna de laizquierda parte superior) y seleccionamos el meacutetodo que hemos creado(guardaDatos) De esta forma ya tenemos conectado el botoacuten

bull 6) Ahora hacemos lo mismo con los Text Field Tenemos que conectarlos alcontrolador para ello seleccionamos el objeto Files Owner y abrimos la pestantildeade conexiones (la que estaacute maacutes a la derecha en la columna de la derecha) Hacemosclick en tfEdad y arrastramos hasta el Text Field de edad Hacemos lo mismo para eltext field de nombre Ya tenemos todos los elementos de la vista conectados

Conexioacuten de Outlets

bull 7) Arrancamos la aplicacioacuten y la probamos

Persistencia

59Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Aplicacioacuten

bull 8) Ahora vamos a hacer que la aplicacioacuten cargue los datos que se han guardado alarrancar la vista para ello simplemente tenemos que escribir el siguiente coacutedigo en elmeacutetodo (void)viewDidLoad de la clase sesion04_ejemplo1ViewControllerm

- (void)viewDidLoad

[super viewDidLoad]

NSUserDefaults datos = [NSUserDefaults standardUserDefaults]

obtenemos un NSStringNSString nombre = [datos stringForKeynombre]

obtenemos un NSIntegerNSInteger edad = [datos integerForKeyedad]

if (nombre=nil ampamp edad=0)NSLog(Nombre cargado edad dnombreedad)

else

NSLog(Sin datos)

bull 9) Ya podemos arrancar de nuevo la aplicacioacuten y veremos que si escribimos cualquier

Persistencia

60Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

texto en los Text Fields estos se almacenaraacuten dentro del objeto NSUserDefaults Alapagar la aplicacioacuten y volver a arrancarla estos datos se cargaraacuten (apareceraacuten enconsola)

Consola

NotaLos datos permaneceraacuten en nuestro dispositivo hasta que se elimine la aplicacioacuten en ese casotodos los datos guardados en NSUserDefaults se borraraacuten

bull 10) Probar eliminar la aplicacioacuten del dispositivosimulador y volver arrancarComprobamos que los datos se han borrado

Consola

73 Core Data

De todas las formas de persistencia de datos que existen en iOS Core Data es la maacutesapropiada para usar en caso de manejar datos no triviales con relaciones algo maacutescomplejas entre ellos El uso de Core Data dentro de nuestra aplicacioacuten optimiza almaacuteximo el consumo de memoria mejora el tiempo de respuesta y reduce la cantidad decoacutedigo redundante a escribir

Core Data utiliza de fondo el meacutetodo de almacenamiento SQLite visto en la sesioacuten 3 deeste moacutedulo por tanto para explicar el funcionamiento de este meacutetodo de persistenciavamos a utilizar el mismo modelo de datos que usamos en esa sesioacuten Persona -gtDetallePersona

El el siguiente ejemplo haremos la misma aplicacioacuten que hicimos con SQLite perousando este nuevo meacutetodo de persistencia

731 Creando el proyecto

Para empezar tenemos que crearnos un nuevo proyecto en XCode Para ello abrimosXCode y seleccionamos crear una nueva aplicacioacuten basada en ventanas (Window-basedapplication) Hacemos click en Next escribimos como nombre del proyectosesion04-ejemplo2 y seleccionamos Use Core Data

Persistencia

61Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creando un proyecto nuevo

Con esto ya tenemos la estructura baacutesica para empezar a programar Antes de nadahechamos un vistazo raacutepido a toda la estructura de directorios creada y vemos que dentrodel directorio principal tenemos un archivo que se llamasesion04_ejemplo2xcdatamodeld Al hacer click sobre el archivo se nos abre uneditor especial que en principio estaacute vacio y que maacutes adelante utilizaremos para disentildearnuestro modelo de datos

Ahora hechamos un vistazo a la clase delegada sesion04_ejemplo2AppDelegatemvemos que hay unos cuantos meacutetodos nuevos que serviraacuten para configurar el Core DataVamos a explicar los maacutes importantes

bull - (NSManagedObjectModel )managedObjectModel NSManagedObjectModel esla clase que contiene la definicioacuten de cada uno de los objetos o entidades quealmacenamos en la base de datos Normalmente este meacutetodo no lo utilizaremos ya quenosotros vamos a utilizar el editor que hemos visto antes con el que podremos crearnuevas entidades crear sus atributos y relaciones

bull - (NSPersistentStoreCoordinator )persistentStoreCoordinator Aquiacute esdonde configuramos los nombres y las ubicaciones de las bases de datos que se usaraacutenpara almacenar los objetos Cuando un managed object necesite guardar algo pasaraacutepor este meacutetodo

bull - (NSManagedObjectContext )managedObjectContext Esta claseNSManagedObjectContext seraacute la maacutes usada con diferencia de las tres y por tanto la

Persistencia

62Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

maacutes importante La utilizaremos baacutesicamente para obtener objetos insertarlos oborrarlos

732 Definiendo el modelo de datos

En el ejemplo que hicimos en la sesioacuten anterior de SQLite creamos dos tablas una paralos datos baacutesicos de la persona y otra para el detalle Ahora haremos lo mismo perousando Core Data la diferencia principal es que mientras que con SQLite obteniamos soacutelolos datos que necesitaacutebamos de la base de datos aquiacute obtenemos el objeto completo

Para definir nuestro modelo de datos basado en dos objetos (Persona y DetallePersona)abrimos el editor visual haciendo click sobre el ficherosesion04_ejemplo2xcdatamodeld Empezaremos creando una nueva entidad para ellohacemos click sobre el botoacuten Add Entity (+) que se encuentra en la parte inferior dela pantalla A la nueva entidad le pondremos como nombre Persona

Dentro de la seccioacuten de Attributes hacemos click sobre (+) y creamos un nuevoatributo para nuestra entidad le llamamos nombre y le asignamos como tipo StringRealizamos este mismo paso 2 veces maacutes para antildeadir apellidos y edad este uacutetlimode tipo Integer 16

Atributos Persona

Ahora creamos la entidad de DetallePersona de la misma manera que la anterior perocreando los atributos localidad pais direccion cp y telefono Todasde tipo String

Atributos DetallePersona

Finalmente nos queda relacionar de alguacuten modo las dos entidades para ello antildeadimos una

Persistencia

63Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

relacioacuten dentro de la entidad de Persona hacia DetallePersona simplementehaciendo click sobre el botoacuten (+) de la seccioacuten de Relationships A la relacioacuten leponemos como nombre detalle seleccionamos como destino la entidadDetallePersona y seraacute No inverse Este tipo de relacioacuten es uno a uno Por dentroCore Data realizaraacute crearaacute una clave ajena en la tabla de persona que apunte aDetallePersona pero eso a nosotros no nos interesa

Vista general modelo de datos (1)

Apple recomienda que siempre que hagamos una relacioacuten de una entidad a otra hagamostambieacuten la relacioacuten inversa por lo tanto hacemos justo lo anterior pero al reveacutes (deDetallePersona a Persona) con la diferencia que en la columna de Inverse

seleccionamos datosPersona

Ya tenemos las entidades y las relaciones creadas Si hacemos click en el botoacuten deEditor Style en la parte de abajo a la derecha del editor podemos ver de una formamaacutes clara la estructura que hemos disentildeado

Persistencia

64Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista general modelo de datos (2)

733 Probando el modelo

Una vez que tenemos el modelo creado pasamos a probarlo para comprobar que estaacute todocorrecto para ello abrimos la clase delegada (sesion04_ejemplo2AppDelegatem) yantildeadimos el siguiente fragmento de coacutedigo arriba del meacutetodoapplicationDidFinishLaunching

NSManagedObjectContext context = [self managedObjectContext]NSManagedObject persona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

[persona setValueJuan forKeynombre][persona setValueMartinez forKeyapellidos][persona setValue[NSNumber numberWithInt28] forKeyedad]

NSManagedObject detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersonainManagedObjectContextcontext]

[detallePersona setValueCalle Falsa 123 forKeydireccion][detallePersona setValueAlicante forKeylocalidad][detallePersona setValueEspantildea forKeypais][detallePersona setValue965656565 forKeytelefono]

[detallePersona setValuepersona forKeydatosPersona][persona setValuedetallePersona forKeydetalle]

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Persistencia

65Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Con este coacutedigo hemos creado un objeto Persona enlazado con otro objetoDetallePersona iexclComo puedes ver no hemos necesitado nada de coacutedigo SQLPrimero creamos un objeto del tipo NSManagedObjectContext que almacena el conextode Core Data Despueacutes creamos el objeto Persona en siacute con NSManagedObject A esteobjeto le pondremos los valores para cada una de las propiedades definidas en el modeloHacemos lo mismo con DetallePersona Finalmente relacionamos el objetoPersona con el objeto DetallePersona

Compilamos el proyecto comprobamos que todo funciona bien y no salen errores

Para comprobar que los datos se han almacenado correctamente en nuestro dispositivo losmostramos en la consola de la siguiente manera

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

intValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(------------------)

[fetchRequest release]

El coacutedigo de arriba lo escribiremos dentro del meacutetododidFinishLaunchingWithOptions de la clase delegada Lo que hace este coacutedigo esrecorrer todos los objetos almacenados en memoria y mostrarlos por consola Siarrancamos la aplicacioacuten veremos que se muestran todos los datos de los objetosalmacenados Cada vez que arranquemos la aplicacioacuten se mostraraacuten maacutes objetos ya quetambieacuten los creamos

Para listar los objetos usamos la clase NSFetchRequest Esta clase es equivalente aejecutar una sentencia Select de SQL Al objeto NSFetchRequest le asignamos laentidad que queremos listar (SELECT FROM ) en nuestro caso seraacute PersonaEl listado de los objetos se obtiene mediante el meacutetodo executeFetchRequest de laclase NSManagedObjectContext que hemos definido en el bloque anterior de coacutedigoEste meacutetodo devolveraacute un NSArray con todos los objetos NSManagedObject

Persistencia

66Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Salida de consola

NotaSi queremos ver queacute sentencias SQL estaacute ejecutando Core Data por debajo tenemos que activarel flag de debug para ello seleccionamos la opcioacuten Product gt Edit Scheme Enla columna de la izquierda seleccionamos el esquema que estamos utilizando para Debug y en laparte de la derecha en el cuadro de Arguments Passed on Launch antildeadimos 2paraacutemetros -comappleCoreDataSQLDebug y 1

Ahora arrancamos de nuevo la aplicacioacuten y veremos que en la consola apareceraacuten lassentencias SQL utilizadas por Core Data

()

INSERT INTO ZDETALLEPERSONA(Z_PK Z_ENT Z_OPT ZDATOSPERSONA ZTELEFONOZLOCALIDAD ZDIRECCION ZPAIS)VALUES( )CoreData sql SELECT 0 t0Z_PK t0Z_OPT t0ZTELEFONO t0ZLOCALIDADt0ZDIRECCION t0ZPAIS t0ZDATOSPERSONAFROM ZDETALLEPERSONA t0 WHERE t0Z_PK =

()

734 Generando los modelos automaacuteticamente

Generar los modelos a mano directamente desde el editor no es lo maacutes recomendado yaque podemos cometer errores de escritura errores de tipo etc La mejor manera dehacerlo es creando un archivo para cada entidad Esto se puede hacer desde 0 peroXCode tiene un generador de clases que es muy faacutecil de usar y ahorremos bastantetiempo iexclVamos a usarlo

Hacemos click sobre el raiz de nuestro proyecto y seleccionamos New File En lacolumna de la izquierda seleccionamos iOS gt Core Data y en el cuadro de laderecha NSManagedObject subclass Click en next En la siguiente pantallaseleccionamos nuestro modelo de datos sesion04_ejemplo2 y click en next Ahoranos aseguramos de seleccionar las dos entidades que hemos creado anteriormentePersona y DetallePersona click en next Seleccionamos el raiz de nuestroproyecto para guardar los ficheros que se generen y aceptamos

Persistencia

67Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ventana de seleccioacuten de entidad

Ahora veremos que xsource ha creado automaacuteticamente 4 ficheros nuevos dentro denuestro proyecto Personamh y DetallePersonamh con todos los atributosespecificados Estas nuevas clases que heredan de NSManagedObject seraacuten las queutilicemos a partir de ahora

Cambiamos el coacutedigo de la clase delegada por el siguiente

NSManagedObjectContext context = [self managedObjectContext]Persona datosPersona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

datosPersonanombre = JuandatosPersonaapellidos = MartinezdatosPersonaedad = [NSNumber numberWithInt28]

DetallePersona detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersona

inManagedObjectContextcontext]detallePersonadireccion = Calle Falsa 123

Persistencia

68Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

detallePersonalocalidad = AlicantedetallePersonapais = EspantildeadetallePersonatelefono = 965656565

detallePersonadatosPersona = datosPersonadatosPersonadetalle = detallePersona

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Listamos los datosNSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

integerValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(-----------------)[fetchRequest release]

Ejecutamos el proyecto y comprobamos que en la consola aparece lo mismo que antespero ahora tenemos el coacutedigo bastante maacutes claro

735 Creando la vista de tabla

Ahora vamos a mostrar los objetos que antes hemos listado por consola en una vista detabla UITableView Para ello creamos un UITableViewController

Click en New file gt UIViewController subclass En la siguiente pantallanos aseguramos de seleccionar Subclass of UITableViewController de esta formanos apareceraacuten ya todos los meacutetodos de la tabla creados por defecto Guardamos elarchivo con el nombre que queramos por ejemplo PersonasViewController

Abrimos ahora PersonasViewControllerh y antildeadimos 2 atributos a la clase

bull Un NSArray que se seraacute el listado de personas (objetos de la clase Persona)bull Una referencia a NSManagedObjectContext

El fichero PersonasViewControllerh quedaraacute de la siguiente manera

Persistencia

69Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

imports iniciales

interface PersonasViewController UITableViewController NSArray _listaPersonasNSManagedObjectContext _context

property (nonatomic retain) NSArray listaPersonasproperty (nonatomic retain) NSManagedObjectContext context

end

Ahora en el m importamos en el encabezado las clases Personah yDetallePersonah y en el meacutetodo viewDidLoad creamos el array listaPersonas contodos los objetos Persona de forma similar a cuando lo hicimos en el apartado anterior

import PersonasViewControllerh

import Personahimport DetallePersonah

implementation PersonasViewController

synthesize listaPersonas = _listaPersonassynthesize context = _context

En viewDidLoad

- (void)viewDidLoad

[super viewDidLoad]

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContext_context]

[fetchRequest setEntityentity]NSError errorselflistaPersonas = [_context executeFetchRequestfetchRequest

erroramperror]selftitle = Listado Personas[fetchRequest release]

Ahora completamos el resto de meacutetodos heredados de UITableViewController de lamisma forma que hicimos en sesiones anteriores

- (NSInteger)numberOfSectionsInTableView(UITableView )tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection(NSInteger)section

Persistencia

70Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Return the number of rows in the sectionreturn [_listaPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]

if (cell == nil) cell = [[[UITableViewCell alloc]

initWithStyleUITableViewCellStyleSubtitlereuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona p = (Persona )[_listaPersonas objectAtIndexindexPathrow]celltextLabeltext = pnombrecelldetailTextLabeltext = [NSString stringWithFormatd antildeos

[pedad intValue]]

return cell

Ahora para poder mostrar la tabla dentro de nuestra aplicacioacuten debemos asignarla dentrode la clase delegada para ello abrimos la vista MainWindowxib y arrastramos unacontroladora de navegacioacuten Navigation Controller a la lista de objetos de laizquierda La desplegamos y dentro de View Controller en la pestantildea de la derechade Identity Inspector escribimos el nombre de nuestra clase que tiene la vista de latabla PersonasViewController

Pantalla del disentildeador

Ahora abrimos el fichero sesion04_ejemplo2AppDelegateh y creamos un

Persistencia

71Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

UINavigationController dentro de la declaracioacuten de variables dejando el ficherocomo sigue

imports iniciales

interface sesion04_ejemplo2AppDelegate NSObject ltUIApplicationDelegategt

UINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet UINavigationControllernavController

property (nonatomic retain readonly) NSManagedObjectContextmanagedObjectContextproperty (nonatomic retain readonly) NSManagedObjectModelmanagedObjectModelproperty (nonatomic retain readonly) NSPersistentStoreCoordinatorpersistentStoreCoordinator

- (void)saveContext- (NSURL )applicationDocumentsDirectory

end

Dentro del m importamos la clase PersonasViewControllerh y completamos elsynthetize

synthesize navController = _navController

Y por uacuteltimo justo antes de la llamada al meacutetodo makeKeyAndVisible escribimos elsiguiente coacutedigo para asignar el contexto de la clase delegada alPersonasViewController

PersonasViewController root = (PersonasViewController ) [_navControllertopViewController]

rootcontext = [self managedObjectContext][selfwindow addSubview_navControllerview]

Ahora compilamos y ejecutamos nuestro proyecto y si todo ha ido bien deberiacutea de salirla tabla con los datos de las personas

Persistencia

72Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Listado de personas

736 Migraciones de bases de datos con Core Data

Una vez que tenemos disentildeada nuestra base de datos inicial iquestqueacute pasariacutea si queremosmodificarla para antildeadirle un campo maacutes a una tabla iquestQueacute haraacute Core Data para realizar lamigracioacuten de la versioacuten anterior a la actual

Siguiendo con el ejemplo anterior si despueacutes de ejecutar la aplicacioacuten en nuestrodispositivo o simulador modificamos la estructura de la base de datos antildeadiendo unatributo nuevo en la entidad DetallePersona por ejemplo codigoPostal con el tipoInteger 16 veremos que al volver a arrancar la aplicacioacuten nos apareceraacute un error entiempo de ejecucioacuten similar al siguiente

Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 Theoperationcouldnrsquot be completed(Cocoa error 134100) UserInfo=0x6b6c680 metadata=ltCFBasicHash 0x6b68380[0x1337b38]gttype = immutable dict count = 7entries =gt

2 ltCFString 0x6b70320 [0x1337b38]gtcontents =NSStoreModelVersionIdentifiers

=ltCFArray 0x6b706e0 [0x1337b38]gttype = immutable count = 1

values = (0 ltCFString 0x1332cd8 [0x1337b38]gtcontents =

)4 ltCFString 0x6b70350 [0x1337b38]gtcontents =

NSPersistenceFrameworkVersion=

Persistencia

73Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltCFNumber 0x6b6f2c0 [0x1337b38]gtvalue = +386 type =kCFNumberSInt64Type

6 ltCFString 0x6b70380 [0x1337b38]gtcontents =NSStoreModelVersionHashes =

ltCFBasicHash 0x6b707e0 [0x1337b38]gttype = immutable dict count =2entries =gt

1 ltCFString 0x6b70700 [0x1337b38]gtcontents = Persona =ltCFData 0x6b70740

[0x1337b38]gtlength = 32 capacity = 32 bytes =0x1c50e5a379ff0ddc3cda7b82a3934c5e 75649ac3c919beea2 ltCFString 0x6b70720 [0x1337b38]gtcontents = DetallePersona

=ltCFData 0x6b70790[0x1337b38]gtlength = 32 capacity = 32 bytes =0xb7ba2eb498f9a1acdd6630d56f6cd12c e8963dd3e488ba95

7 ltCFString 0x10e3aa8 [0x1337b38]gtcontents = NSStoreUUID =ltCFString 0x6b70510[0x1337b38]gtcontents = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E8 ltCFString 0x10e3948 [0x1337b38]gtcontents = NSStoreType =ltCFString 0x10e3958[0x1337b38]gtcontents = SQLite9 ltCFString 0x6b703b0 [0x1337b38]gtcontents =

_NSAutoVacuumLevel = ltCFString0x6b70830 [0x1337b38]gtcontents = 210 ltFString 0x6b703d0 [0x1337b38]gtcontents =

NSStoreModelVersionHashesVersion=ltCFNumber 0x6b61950 [0x1337b38]gtvalue = +3 type =

kCFNumberSInt32Type reason=The model used to open the store is incompatible with the oneused tocreate the store

metadata = NSPersistenceFrameworkVersion = 386NSStoreModelVersionHashes =

DetallePersona = ltb7ba2eb4 98f9a1ac dd6630d5 6f6cd12c f890007746b16f00

e8963dd3 e488ba95gtPersona = lt1c50e5a3 79ff0ddc 3cda7b82 a3934c5e 5cb4ee4b

4ac98838 75649ac3c919beeagt

NSStoreModelVersionHashesVersion = 3NSStoreModelVersionIdentifiers = (

)NSStoreType = SQLiteNSStoreUUID = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E_NSAutoVacuumLevel = 2

reason = The model used to open the store is incompatible with the

one usedto create the store

Este error es muy comuacuten cuando estamos desarrollando aplicaciones con Core DataBaacutesicamente nos dice que el modelo de base de datos que estaacute intentando cargar dememoria es distinto al que se indica en la aplicacioacuten esto es porque no hemos indicadoninguacuten meacutetodo de migracioacuten Realmente existen dos formas de solucionar este error

Persistencia

74Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Por un lado y soacutelo cuando estemos desarrollando y no tengamos ninguna versioacutende nuestra aplicacioacuten lanzada en la App Store podremos eliminar la base de datosdel dispositivo simulador simplemente borrando la aplicacioacuten de el Tambieacutenpodemos ejecutar el siguiente coacutedigo una soacutela vez [[NSFileManagerdefaultManager] removeItemAtURLstoreURL errornil]

bull En el caso de que estemos realizando una migracioacuten sobre una versioacuten ya publicadaen la App Store el meacutetodo anterior no nos serviriacutea ya que obligariacuteamos al usuario aborrar su aplicacioacuten del dispositivo con lo que todos los datos que tuviera seperderiacutean Esto no es viable por lo que necesitaremos usar otro meacutetodo para realizarla migracioacuten y solucionar el error de una forma maacutes limpia este meacutetodo es el quevamos a comentar a continuacioacuten

XCode soporta Versioning para Core Data esto es que podemos crear versiones de labase de datos de una forma sencilla si seguimos una serie de pasos

1) Antildeadimos las siguientes opciones en forma de NSDictionary a nuestro coacutedigo delpersistentStoreCoordinator

NSDictionary options = [NSDictionary dictionaryWithObjectsAndKeys[NSNumber numberWithBoolYES]NSMigratePersistentStoresAutomaticallyOption[NSNumber numberWithBoolYES]NSInferMappingModelAutomaticallyOptionnil]

Y modificamos el meacutetodo addPersistentStoreWithType pasaacutendole como paraacutemetro eldiccionario options

if ([__persistentStoreCoordinatoraddPersistentStoreWithTypeNSSQLiteStoreTypeconfigurationnil URLstoreURL optionsoptions erroramperror])

Con el coacutedigo anterior indicamos al persistentStoreCoordinator que la migracioacuten enel caso de que se deba de realizar sea de forma automaacutetica

AtencioacutenDeberemos de tener en cuenta que este tipo de migracioacuten que estamos estudiando en este puntosoacutelo es vaacutelida para cambios simples en Core Data lo que Apple llama como lightweightmigration Este tipo de migracioacuten admite una serie de cambios que se indican dentro de ladocumentacioacuten de Apple para cambios maacutes complejos deberemos realizar migracionespersonalizadas y mucho maacutes complejas El documento en donde Apple explica el tipo demigraciones que existen para Core Data y su forma de implementarlas lo podemos consultardesde esta web

2) Ahora deberemos de crear la versioacuten del esquema de Core Data dentro de XCode para

Persistencia

75Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ello seleccionamos el fichero del esquema sesion04_ejemplo2xcdatamodeld yseleccionamos en el menuacute principal Editor gt Add Model Version A la nueva versioacuten lallamaremos sesion04_ejemplo_2 (versioacuten 2) Una vez que hayamos pulsado sobreFinish se nos habraacute creado la nueva versioacuten del esquema dentro de la principal y estaraacutemarcada con un tick la versioacuten anterior

3) Una vez que tenemos creada la nueva versioacuten deberemos modificarla antildeadiendole elnuevo atributo Seleccionamos la versioacuten recieacuten creadasesion04_ejemplo_2xcdatamodel y antildeadimos a la entidad DetallePersona elatributo codigoPostal Atencioacuten Si este atributo lo hemos antildeadido anteriormentedeberemos de borrarlo de la versioacuten anterior y antildeadirlo en la segunda versioacuten

4) Ahora nos queda indicar a Core Data que queremos usar la versioacuten 2 de la base dedatos para nuestra aplicacioacuten Para ello seleccionamos el esquema principal (el raiz)sesion04_ejemplo2xcdatamodeld y en la ventana de la derecha en la pestantildea de FileInspector seleccionamos como Versioacuten actual la versioacuten 2 esto lo hacemos dentro delapartado de Versioned Core Data Model Automaacuteticamente veremos que el siacutembolo detick cambia a la versioacuten 2

5) Ahora ya podemos volver a compilar y veremos que no nos aparece el error y funcionatodo seguacuten lo esperado

Magical RecordExisten varias librerias de coacutedigo libre que facilitan enormemente el uso de Core Data ennuestras aplicaciones iOS Una de ellas es Magical Record Mediante esta libreriacutea podremos usarCore Data sin tener que preocuparnos de todo el proceso de configuracioacuten en las controladorasde la configuracioacuten de migraciones etc Su instalacioacuten en un proyecto de XCode es muy faacutecil yaltamente recomendable ya que proporciona una API muy clara y sencilla de usar

Persistencia

76Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios

81 Usando User Defaults (1 punto)

En este ejercicio vamos a usar las variables de usuario para almacenar y mostrar los datosdel dispositivo que estamos usando y las veces que hemos arrancado la aplicacioacuten

Crear un proyecto nuevo usando la plantilla Single View Application con el nombreejercicio-userdefaults-ios Se debe de ejecutar en iPhone e iPad (Universal) Comoidentificador de empresa escribiremos esuajtech y como prefijo de clase UA Debe detener el ARC activado (desmarcar el resto de opciones)

Ayuda meacutetodos de UIDevicePara obtener los datos del dispositivo usaremos el singleton UIDevice estos son algunos de losmeacutetodos que podemos usar [[UIDevice currentDevice] name] [[UIDevicecurrentDevice] systemName] [[UIDevice currentDevice]systemVersion] [[UIDevice currentDevice] model] [[UIDevicecurrentDevice] localizedModel]

82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)

En este ejercicio disentildearemos un modelo de datos de dos tablas relacionadas entre ellasantildeadiremos datos por coacutedigo y los mostraremos en una vista de tabla La base de datosque vamos a implementar constaraacute de dos tablas por un lado una que contendraacute los tiacutetulosde series y otra que tendraacute los detalles A la primera la llamaremos Serie y la segundaDetalleSerie Para realizar correctamente el ejercicio deberemos seguir los siguientespasos

1) Crear un proyecto nuevo usando la plantilla Master-Detail Application con elnombre ejercicio-coredata-ios Se debe de ejecutar en iPhone como identificador deempresa escribiremos esuajtech y como prefijo de clase UA Debe de tener el ARCactivado y la opcioacuten de Use Core Data (desmarcar el resto de opciones)

2) Disentildear el siguiente modelo de datos

Persistencia

77Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esquema modelo de datos

3) Revisar el fichero UAAppDelegatem y comprobar que tenemos todos los meacutetodosnecesarios para que funcione Core Data en nuestra aplicacioacuten

4) Modificar el fichero UAMasterViewControllerm para que se muestren los tiacutetulos delas series en la tabla

5) Probar la aplicacioacuten antildeadiendo unos tiacutetulos de prueba

83 Migrando datos con Core Data (05 puntos)

Una vez que hemos disentildeado la Base de Datos con Core Data vamos a modificarlaantildeadiendo un atributo maacutes a la tabla de DetalleSerie valoracion

a) iquestQueacute pasa si cambiamos el modelo de datos y arrancamos la aplicacioacuten iquestQueacute errornos da y por queacute

b) iquestCoacutemo podemos evitar este tipo de errores cuando estemos desarrollando iquestY siestamos en produccioacuten con la aplicacioacuten ya a la venta

c) Crea una nueva versioacuten del modelo

d) Modifica el coacutedigo necesario dentro de la clase UAAppDelegate para evitar este tipo deerrores cuando estemos en un entorno de produccioacuten

e) Vuelve a arrancar la aplicacioacuten y comprueba que funciona bien

Persistencia

78Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Persistencia

79Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

  • 1 Persistencia en Android ficheros y SQLite
    • 11 Introduccioacuten
    • 12 Manejo de ficheros tradicionales en Android
      • 121 Apertura de ficheros
      • 122 Ficheros como recursos
      • 123 Operar con ficheros
      • 124 Almacenar datos en la tarjeta de memoria
        • 13 Base de datos SQLite
          • 131 Content Values
          • 132 Cursores
          • 133 Trabajar con bases de datos SQLite
          • 134 La clase SQLiteOpenHelper
          • 135 Crear una base de datos sin SQLiteHelper
          • 136 Realizar una consulta
          • 137 Extraer resultados de un cursor
          • 138 Antildeadir actualizar y borrar filas
              • 2 Ejercicios - Persistencia en Android ficheros y SQLite
                • 21 Uso de ficheros (05 puntos)
                • 22 Persistencia con ficheros (05 puntos)
                • 23 Base de datos SQLiteOpenHelper (05 puntos)
                • 24 Base de datos insercioacuten y borrado (05 puntos)
                • 25 Base de datos probar nuestro adaptador (05 puntos)
                • 26 Base de datos cambios en la base de datos (05 puntos)
                  • 3 Persistencia en Android proveedores de contenidos y SharedPreferences
                    • 31 Shared Preferences
                      • 311 Guardar Shared Preferences
                      • 312 Leer Shared Preferences
                      • 313 Interfaces para Shared Preferences
                      • 314 Definiendo una pantalla de preferencias con un layout en XML
                      • 315 Controles nativos para preferencias
                      • 316 Actividades de preferencias
                      • 317 Shared Preference Change Listeners
                        • 32 Proveedores de contenidos
                          • 321 Proveedores nativos
                          • 322 Proveedores propios crear un nuevo proveedor de contenidos
                          • 323 Proveedores propios crear la interfaz de consultas
                          • 324 Proveedores propios tipo MIME
                          • 325 Proveedores propios registrar el proveedor
                          • 326 Content Resolvers
                          • 327 Otras operaciones con Content Resolvers
                              • 4 Ejercicios - Persistencia en Android proveedores de contenidos y SharedPreferences
                                • 41 Compartir datos entre actividades con Shared Preferences (075 puntos)
                                • 42 Actividad de preferencias (075 puntos)
                                • 43 Proveedor de contenidos propio (075 puntos)
                                • 44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)
                                  • 5 Persistencia de datos en iOS Ficheros y SQLite
                                    • 51 Introduccioacuten
                                    • 52 Ficheros plist (Property Lists)
                                      • 521 Leyendo datos desde ficheros plist
                                      • 522 Escribiendo datos en ficheros plist
                                        • 53 SQLite
                                          • 531 Herramientas disponibles
                                          • 532 Lectura de datos
                                          • 533 Mostrando los datos en una tabla
                                              • 6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios
                                                • 61 Creando y leyendo un fichero plist (15 puntos)
                                                • 62 Implementar vista de detalle de la serie (05 puntos)
                                                • 63 Uso baacutesico de una base de datos SQLite (1 punto)
                                                  • 7 Persistencia de datos en iOS User Defaults y Core Data
                                                    • 71 Introduccioacuten
                                                    • 72 User Defaults
                                                    • 73 Core Data
                                                      • 731 Creando el proyecto
                                                      • 732 Definiendo el modelo de datos
                                                      • 733 Probando el modelo
                                                      • 734 Generando los modelos automaacuteticamente
                                                      • 735 Creando la vista de tabla
                                                      • 736 Migraciones de bases de datos con Core Data
                                                          • 8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios
                                                            • 81 Usando User Defaults (1 punto)
                                                            • 82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)
                                                            • 83 Migrando datos con Core Data (05 puntos)
Page 9: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de

Meacutetodo para eliminar un elemento de la tabla de la base dedatos

Devuelve un valor booleano indicando el eacutexito o no de la operacioacutenpublic boolean eliminar(long _indiceFila)

return dbdelete(TABLA_BASE_DATOS CP_ID + = +_indiceFila null) gt 0

Meacutetodo para obtener un Cursor que apunte a todas las filascontenidas

en la tabla de la base de datospublic Cursor obtenerTodos ()

return dbquery(TABLA_BASE_DATOS new String[] CP_IDCOLUMNA_NOMBRE

null null null nullnull)

Meacutetodo para obtener un elemento determinado de la base de datos a partir de su iacutendice de filapublic MiObjeto getObjeto(long _indiceFila)

PENDIENTE obtener un cursor a una fila de la base de datos y usar sus valores para inicializar una instancia de MiObjeto

return objectInstance

Meacutetodo para actualizar un elemento de la tabla de la base dedatos

public boolean actualizar(long _indiceFila MiObjeto _miObjeto) PENDIENTE crear nuevos ContentValues a partir del

objeto y usarlos para actualizar una fila en la tabla

correspondiente

return true

Clase privada auxiliar para ayudar en la creacioacuten yactualizacioacuten

de la base de datosprivate static class miHelperBD extends SQLiteOpenHelper

public miHelperBD(Context contexto String nombreCursorFactory factory int version)

super(contexto nombre factory version)

Meacutetodo llamado cuando la base de datos no existe en eldisco

y la clase Helper necesita crearla desde ceroOverridepublic void onCreate(SQLiteDatabase _db)

_dbexecSQL(CREAR_BASE_DATOS)

Meacutetodo llamado cuando la versioacuten de la base de datos en disco es diferente a la indicada en este caso la base de datos en disco necesita ser actualizadaOverridepublic void onUpgrade(SQLiteDatabase _db int

_versionAnterior

Persistencia

9Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

int _versionNueva) Mostramos en LogCat la operacioacutenLogw(AdaptadorBD Actualizando desde la

versioacuten +_versionAnterior + a la versioacuten +_versionNueva + lo cual borraraacute los

datospreviamente almacenados)

Actualizamos la base de datos para que seadapte a la nueva versioacuten

La forma maacutes sencilla de llevar a cabo dichaactualizacioacuten es

eliminar la tabla antigua y crear una nueva_dbexecSQL(DROP TABLE IF EXISTS +

TABLA_BASE_DATOS)onCreate(_db)

134 La clase SQLiteOpenHelper

La clase SQLiteOpenHelper es una clase abstracta utilizada como medio paraimplementar un patroacuten de creacioacuten apertura y actualizacioacuten de una determinada base dedatos Al implementar una subclase de SQLiteOpenHelper podemos abstraernos de laloacutegica subyacente a la decisioacuten de crear o actualizar una base de datos de manera previa aque eacutesta deba ser abierta

En el ejemplo de coacutedigo anterior se vio coacutemo crear una subclase de SQLiteOpenHelper

por medio de la sobrecarga de sus meacutetodos onCreate y onUpgrade los cuales permitencrear una nueva base de datos o actualizar a una nueva versioacuten respectivamente

NotaEn el ejemplo anterior el meacutetodo onUpgrade simplemente eliminaba la tabla existente y lareemplazaba con la nueva definicioacuten de la misma En la praacutectica la mejor solucioacuten seriacutea migrarlos datos ya existentes a la nueva tabla

Mediante los meacutetodos getReadableDatabase y getWritableDatabase podemos abrir yobtener una instancia de la base de datos de soacutelo lectura o de lectura y escriturarespectivamente La llamada a getWritableDatabase podriacutea fallar debido a falta deespacio en disco o cuestiones relativas a permisos por lo que es una buena praacutectica teneren cuenta esta posibilidad por medio del mecanismo de manejo de excepciones de JavaEn caso de fallo la solucioacuten podriacutea pasar por al menos proporcionar una copia de soacutelolectura tal como se muestra en el siguiente ejemplo

dbHelper = new miHelperBD(contexto NOMBRE_BASE_DATOS nullVERSION_BASE_DATOS)

SQLiteDatabase dbtry

db = dbHelpergetWritableDatabase() catch (SQLiteException ex)

db = dbHelpergetReadableDatabase()

Persistencia

10Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Al hacer una llamada a cualquiera de los dos meacutetodos anteriores se invocaraacuten losmanejadores de evento adecuados Asiacute pues si por ejemplo la base de datos no existieraen disco se ejecutariacutea el coacutedigo de onCreate Si por otra parte la versioacuten de la base dedatos se hubiera modificado se disparariacutea el manejador onUpgrade Lo que sucedeentonces es que al hacer una llamada a getWritableDatabase o getReadableDatabase

se devolveraacute una base de datos nueva actualizada o ya existente seguacuten el caso

135 Crear una base de datos sin SQLiteHelper

Tambieacuten es posible crear y abrir bases de datos sin necesidad de utilizar una subclase deSQLiteHelper Para ello haremos uso del meacutetodo openOrCreateDatabase En este casoseraacute necesario tambieacuten hacer uso del meacutetodo execSQL sobre la instancia de la base dedatos devuelta por el meacutetodo anterior para ejecutar los comandos SQL que permitiraacutencrear las tablas de la base de datos y establecer las relaciones entre ellas El siguientecoacutedigo muestra un ejemplo

private static final String NOMBRE_BASE_DATOS = mibasededatosdbprivate static final String TABLA_BASE_DATOS = tablaPrincipal

private static final String CREAR_BASE_DATOS =create table + TABLA_BASE_DATOS + _id integer primare key

autoincrement +columna_uno text not null)

SQLiteDatabase db

private void crearBaseDatos() db = openOrCreateDatabase(NOMBRE_BASE_DATOS ContextMODE_PRIVATE

null)dbexecSQL(CREAR_BASE_DATOS)

NotaAunque no es necesariamente obligatorio es recomendable que cada tabla incluya un campo detipo clave primaria con autoincremento a modo de iacutendice para cada fila En el caso en el que sedesee compartir la base de datos mediante un proveedor de contenidos un campo uacutenico de estetipo es obligatorio

136 Realizar una consulta

El resultado de cualquier consulta a una base de datos seraacute un objeto de la clase CursorEsto permite a Android manejar los recursos de manera maacutes eficiente de tal forma que enlugar de devolver todos los resultados eacutestos se van proporcionando conforme senecesitan

Para realizar una consulta a una base de datos haremos uso del meacutetodo query de lainstancia correspondiente de la clase SQLiteDatabase Los paraacutemetros que requiere este

Persistencia

11Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

meacutetodo son los siguientes

bull Un booleano opcional que permite indicar si el resultado deberiacutea contener tan soacutelovalores uacutenicos

bull El nombre de la tabla sobre la que realizar la consultabull Un array de cadenas que contenta un listado de las columnas que deben incluirse en el

resultadobull Una claacuteusula where que defina las filas que se deben obtener de la tabla Se puede

utilizar el comodiacuten el cual seraacute reemplazado por los valores que se pasen a traveacutesdel siguiente paraacutemetro

bull Un array de cadenas correspondientes a argumentos de seleccioacuten que reemplazaraacutenlos siacutembolos en la claacuteusula where

bull Una claacuteusula group by que define coacutemo se agruparaacuten las filas obtenidas comoresultado

bull Un filtro having que indique que grupos incluir en el resultado en el caso en el que sehaya hecho uso del paraacutemetro anterior

bull Una cadena que describa la ordenacioacuten que se llevaraacute a cabo sobre las filas obtenidascomo resultado

bull Una cadena opcional para definir un liacutemite en el nuacutemero de resultados a devolver

NotaComo se puede observar la mayoriacutea de estos paraacutemetros hacen referencia al lenguaje SQLTratar este tema queda fuera de los objetivos de este curso por lo que en nuestros ejemplos ledaremos en la mayoriacutea de las ocasiones un valor null a estos paraacutemetros

El siguiente coacutedigo muestra un ejemplo de consultas utilizando diversos paraacutemetros

Devolver los valores de las columnas uno y tres para todas las filas sin duplicadosString[] columnas = new String[] CLAVE_ID CLAVE_COL1 CLAVE_COL3

Cursor todasFilas = dbquery(true TABLA_BASE_DATOS columnas null nullnull null

null null)

Devolvemos los valores de todas las columnas para aquellas filas cuyovalor de la columna 3 sea igual al de una determinada variable Ordenamos las filasseguacuten el valor de la columna 5 Como queremos los valores de todas las columnas le damos al paraacutemetro correspondiente a las columnas a devolver el valor null En este caso no se ha hecho uso del primer paraacutemetro booleano que esoptativoString where = CLAVE_COL3 + = + valorString orden = CLAVE_COL5Cursor miResultado = dbquery(TABLA_BASE_DATOS null where null nullnull orden)

137 Extraer resultados de un cursor

Persistencia

12Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El primer paso para obtener resultados de un cursor seraacute desplazarnos a la posicioacuten apartir de la cual queremos obtener los datos usando cualquiera de los meacutetodosespecificados anteriormente (como por ejemplo moveToFirst o moveToPosition) Unavez hecho esto hacemos uso de meacutetodos get[TIPO] pasando como paraacutemetro el iacutendice dela columna Esto tendraacute como resultado la devolucioacuten del valor para dicha columna de lafila actualmente apuntada por el cursor

String valor = miResultadogetString(indiceColumna)

A continuacioacuten podemos ver un ejemplo maacutes completo en el que se va iterando a lo largode los resultados apuntados por un cursor extrayendo y sumando los valores de unacolumna de flotantes

int COLUMNA_VALORES_FLOTANTES = 2Cursor miResultado = dbquery(Tabla null null null null nullnull)float total = 0

Nos aseguramos de que se haya devuelto al menos una filaif (miResultadomoveToFirst())

Iteramos sobre el cursordo

float valor =miResultadogetFloat(COLUMNA_VALORES_FLOTANTES)

total += valor while (miResultadomoveToNext())

float media = total miResultadogetCount()

NotaDebido a que las columnas de las bases de datos en SQLite son debilmente tipadas podemosrealizar castings cuando sea necesario Por ejemplo los valores guardados como flotantes en labase de datos podriacutean leerse maacutes adelante como cadenas

138 Antildeadir actualizar y borrar filas

La clase SQLiteDatabase proporciona los meacutetodos insert delete y update queencapsulan las instrucciones SQL requeridas para llevar a cabo estas acciones Ademaacutesdisponemos de la posibilidad de utilizar el meacutetodo execSQL el cual nos permite ejecutarcualquier sentencia SQL vaacutelida en nuestras tablas de la base de datos en el caso en el quequeramos llevar a cabo estas (u otras) operaciones manualmente

NotaCada vez que modifiques los valores de la base de datos puedes actualizar un cursor que se puedaver afectado por medio del meacutetodo refreshQuery de la clase Cursor

Para insertar una nueva fila es necesario construir un objeto de la clase ContentValues

y usar su meacutetodo put para proporcionar valor a cada una de sus columnas Para insertar la

Persistencia

13Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

nueva fila pasaremos como paraacutemetro este objeto del tipo ContentValues al meacutetodoinsert de la instancia de la base de datos junto por supuesto con el nombre de la tablaA continuacioacuten se muestra un ejemplo

Crear la nueva filaContentValues nuevaFila = new ContentValues()

Asignamos valores a cada columnanuevaFilaput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas ]

Insertar la nueva fila en la tabladbinsert(TABLA_BASE_DATOS null nuevaFila)

NotaLos ficheros como imaacutegenes o ficheros de audio no se suelen almacenar dentro de tablas en unabase de datos En estos casos se suele optar por almacenar en la base de datos una cadena con laruta al fichero o una URI

La operacioacuten de actualizar una fila tambieacuten puede ser llevada a cabo por medio del usode la clase ContentValues En este caso deberemos llamar al meacutetodo update de lainstancia de la base de datos pasando como paraacutemetro el nombre de la tabla el objetoContentValues y una claacuteusula where que especifique la fila o filas a actualizar tal comose puede ver en el siguiente ejemplo

Creamos el objeto utilizado para definir el contenido a actualizarContentValues valoresActualizar = new ContentValues()

Asignamos valores a las columnas correspondientesvaloresActualizarput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas a actualizar ]

String where = CLAVE_ID + = + idFila

Actualizamos la fila con el iacutendice especificado en la instruccioacutenanteriordbupdate(TABLA_BASE_DATOS valoresActualizar where null)

Por uacuteltimo para borrar una fila simplemente llamaremos al meacutetodo delete de lainstancia de la base de datos especificando en este caso el nombre de la tabla que se veraacuteafectada por la operacioacuten y una claacuteusula where que utilizaremos para especificar las filasque queremos borrar Un ejemplo podriacutea ser el siguiente

dbdelete(TABLA_BASE_DATOS CLAVE_ID + = + idFila null)

Persistencia

14Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

2 Ejercicios - Persistencia en Android ficheros y SQLite

21 Uso de ficheros (05 puntos)

En este ejercicio vamos a crear una aplicacioacuten que muestre un listado de cadenas porpantalla Estas cadenas se almacenaraacuten en un fichero de texto privado para la aplicacioacutenPodremos antildeadir nuevas cadenas a partir de un cuadro de edicioacuten presente en la interfazPartiremos del proyecto Ficheros proporcionado en las plantillas de la aplicacioacuten Debesseguir los siguientes pasos

bull Antildeadir un manejador al botoacuten de la actividad principal para que cada vez que seapulsado se guarde en un fichero de texto El fichero se llamaraacute ficherotxt Al abrirel fichero para escritura se utilizaraacute el paraacutemetro ContextMODE_APPEND con lo quecada nueva cadena se antildeadiraacute al final del fichero en el caso en el que eacuteste ya existieraPara escribir en el fichero utilizaremos el meacutetodo writeBytes del objetoDataOutputStream correspondiente

bull Al pulsar el botoacuten tambieacuten se deberaacute borrar el contenido del elemento EditText (leasignamos a la vista una cadena vaciacutea con el meacutetodo setText)

bull Ejecutamos la aplicacioacuten e introducimos algunas liacuteneas en el fichero Vamos acomprobar ahora que todo ha funcionado correctamente Para ello accedemos alsistema de ficheros de nuestro dispositivo ejecutando el comando adb shell

dentro de la carpeta platform-tools de nuestra carpeta de instalacioacuten del SDK deAndroid

bull Comprueba que se ha creado el fichero ficherotxt en la carpetadatadataesuajtechandroidficherosfiles Examina su contenido ejecutando cat

ficherotxt Si algo no ha salido bien siempre podraacutes eliminar el fichero con rm

ficherotxtbull En la interfaz de la actividad se ha incluido una vista de tipo TextView debajo del

botoacuten Antildeade un manejador para dicha vista de tal forma que cada vez que se hagaclick sobre ella se muestre el contenido del fichero ficherotxt Para ello leeremos elfichero liacutenea a liacutenea antildeadiendo cada una al TextView por medio del meacutetodo append

Persistencia

15Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Interfaz de la aplicacioacuten Ficheros

22 Persistencia con ficheros (05 puntos)

Seguramente habraacutes observado que si abandonas la aplicacioacuten anterior (pulsando el botoacutenBACK del dispositivo) al volver a ejecutarla el contenido del TextView ha desaparecidoDebes volver a pulsar sobre la vista para que se vuelva a mostrar el contenido del ficheroHaz las modificaciones pertinentes para que esto no sea asiacute es decir para que cuandovuelva a mostrarse la actividad tras haber sido eliminada de la memoria se muestre elTextView tal cual se veiacutea antes de abandonar la aplicacioacuten

AvisoEn este ejercicio no se estaacute pidiendo que cargues el contenido del fichero en el TextView nadamaacutes arrancar la aplicacioacuten Puede darse el caso de que lo que esteacute mostrando el TextViewcuando la actividad sea eliminada de memoria no se corresponda con el contenido actualizado delfichero

NotaPara poder completar el ejercicio deberaacutes repasar los manejadores de evento de la primera sesioacutendel moacutedulo de Android relacionados con el ciclo de vida de ejecucioacuten de actividades

23 Base de datos SQLiteOpenHelper (05 puntos)

En las plantillas de la aplicacioacuten se incluye la aplicacioacuten BaseDatos En el proyecto de laaplicacioacuten se incluye el esqueleto de una clase MiAdaptadorBD que tendremos quecompletar Se trata de un patroacuten que nos va a permitir acceder a una base de datos de

Persistencia

16Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

usuarios dentro de la aplicacioacuten sin necesidad de hacer uso de coacutedigo SQL

La clase MiAdaptadorBD incluye a su vez otro patroacuten en este caso implementado comouna subclase de SQLiteOpenHelper Eacuteste nos obliga a definir queacute ocurre cuando la basede datos todaviacutea no existe y debe ser creada y queacute ocurre si ya existe pero debe seractualizada porque ha cambiado de versioacuten Asiacute el SQLiteOpenHelper queimplementemos en este caso MiOpenHelper nos devolveraacute siempre una base de datosseparaacutendonos de la loacutegica encargada de comprobar si la base de datos existe o no

En este primer ejercicio se pide hacer lo siguiente

bull Ejecutar la sentencia de creacioacuten de bases de datos (la tenemos declarada comoconstante de la clase) en el meacutetodo MiOpenHelperonCreate

bull Implementar tambieacuten el meacutetodo onUpgrade Idealmente eacuteste deberiacutea portar las tablasde la versioacuten antigua a la versioacuten nueva copiando todos los datos Nosotros vamos aeliminar directamente la tabla que tenemos con la sentencia SQL DROP TABLE IF

EXISTS + NOMBRE_TABLA y volveremos a crearlabull En el constructor de MiAdaptadorBD debemos obtener en el campo db la base de

datos utilizando MiOpenHelper

24 Base de datos insercioacuten y borrado (05 puntos)

Continuamos trabajando con el proyecto anterior En este ejercicio completaremos elcoacutedigo relacionado con las sentencias de insercioacuten y borrado Para realizar la insercioacutenvamos a hacer uso de un mecanismo que no hemos visto en la sesioacuten de teoriacutea y queconsiste en utilizar una sentencia de insercioacuten SQL precompilada que se completaraacute conlos valores concretos a insertar en la base de datos justo antes de realizar dicha insercioacutenLa ventaja de las sentencias compiladas es que evitan que se produzcan ataques deinyeccioacuten de coacutedigo

Como se puede observar se ha incluido un atributo a la clase que representa la insercioacutenSQL

private static final String INSERT = insert into + NOMBRE_TABLA +(+COLUMNAS[1]+) values ()

En esta sentencia no se indica ninguacuten valor concreto para la columna nombre En el lugaren el que deberiacutean aparecer los valores de dicho campo se ha escrito simplemente unsiacutembolo de interrogacioacuten Tambieacuten se ha antildeadido una instancia de la claseSQLiteStatement como parte de los atributos de la clase

private SQLiteStatement insertStatement

Realizamos los siguientes pasos

bull En el constructor de la clase MiAdaptadorBD llevamos a cabo la compilacioacuten de lasentencia

thisinsertStatement = thisdbcompileStatement(INSERT)

Persistencia

17Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Una vez hecho esto cada vez que deseemos insertar un nuevo usuario en la base dedatos deberemos dar valores concretos a la columna nombre y ejecutar la sentenciaPara ello debemos antildeadir el siguiente coacutedigo dentro del meacutetodo insert de la claseMiAdaptadorBD

thisinsertStatementbindString(1 nombreUsuario)return thisinsertStatementexecuteInsert()

bull Por uacuteltimo completamos el coacutedigo del meacutetodo deleteAll cuyo contenido es eliminara todos los usuarios de la base de datos Para que ademaacutes se devuelva el nuacutemero defilas afectadas podemos insertar la siguiente liacutenea en dicho meacutetodo

return dbdelete(NOMBRE_TABLA null null)

Una vez hechos todos estos cambios podremos utilizar la clase MiAdaptadorBD parahacer operaciones con la base de datos de usuarios de manera transparente

25 Base de datos probar nuestro adaptador (05 puntos)

Antildeade coacutedigo en la actividad Main para eliminar todos los usuarios de la base de datosantildeadir dos cualesquiera y listarlos por medio de la vista de tipo TextView que seencuentra en el layout de dicha actividad

Podemos comprobar mediante la liacutenea de comandos (comando adb shell) que la basede datos ha sido creada Para ello puede ser uacutetil hacer uso de los siguientes comandos

cd datadataesuajtechandroidbasedatosdatabasessqlite3 misusuariosdbsqlitegt schemasqlitegt tablessqlitegt select from usuarios

26 Base de datos cambios en la base de datos (05 puntos)

Ahora vamos a cambiar en la clase MiAdaptadorBD el nombre de la segunda columnaque en lugar de nombre se va a llamar nombres Ejecutamos la aplicacioacuten y comprobamosque sale con una excepcioacuten Comprueba por medio de LogCat cuaacutel ha sido el erroriquestCoacutemo lo podemos solucionar

NotaPista conforme hemos programado la clase MiAdaptadorBD y siguiendo el patroacuten de disentildeode SQLiteOpenHelper es posible arreglar el problema simplemente modificando el valor deun campo

Persistencia

18Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

3 Persistencia en Android proveedores de contenidos ySharedPreferences

31 Shared Preferences

Comenzamos esta sesioacuten hablando de una de las teacutecnicas maacutes simples de persistencia enAndroid junto al uso de ficheros Shared Preferences Se trata de un mecanismo ligeropara almacenar datos basado en pares clave-valor utilizado normalmente para guardarpreferencias u opciones de la aplicacioacuten Tambieacuten puede ser utilizado para almacenar elestado de la interfaz graacutefica para contemplar el caso de que nuestra actividad debafinalizar abruptamente su ejecucioacuten Otro posible uso es el de compartir informacioacutenentre componentes de una misma aplicacioacuten

NotaLos Shared Preferences nunca son compartidos entre diferentes aplicaciones

Este meacutetodo se basa en el uso de la clase SharedPreferences Es posible utilizarla paraguardar datos en muy diversos formatos booleanos cadenas flotantes y enteros

311 Guardar Shared Preferences

Para crear o modificar un Shared Preference llamamos al meacutetodogetSharedPreferences del contexto de la aplicacioacuten pasando como paraacutemetro elidentificador del conjunto de valores de preferencias con el que queremos trabajar Pararealizar la modificacioacuten del valor de un Shared Preference hacemos uso de la claseSharedPreferencesEditor Para obtener el objeto editor invocamos el meacutetodo edit

del objeto Shared Preferences que queremos modificar Los cambios se guardan medianteel meacutetodo commit Veamos un ejemplo

int modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences = getSharedPreferences(Mispreferencias modo)

Obtenemos un editor para modificar las preferenciasSharedPreferencesEditor editor = misSharedPreferencesedit()

Guardamos nuevos valores en el objeto SharedPreferenceseditorputBoolean(isTruetrue)editorputFloat(unFloat10)editorputInt(numeroEntero12)editorputLong(otroNumero2)editorputString(unaCadenavalor)

Guardamos los cambioseditorcommit()

Persistencia

19Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

312 Leer Shared Preferences

Para leer Shared Preferences utilizamos el meacutetodo getSharedPreferences tal como seha comentado anteriormente Pasamos como paraacutemetro el identificador del conjunto deShared Preferences al que deseamos acceder Una vez hecho esto ya podemos hacer usode meacutetodos de tipo get para acceder a preferencias individuales Hay diferentes meacutetodosget para diferentes tipos de datos Cada uno de ellos requiere como paraacutemetro la claveque identifica la preferencia cuyo valor deseamos obtener y un valor por defecto el cualse usaraacute en el caso concreto en el que no existiera la preferencia con la clave pasada comoprimer paraacutemetro Podemos ver un ejemplo a continuacioacuten

public static String MIS_PREFS = MIS_PREFS

public void cargarPreferences() Obtener las preferencias almacenadasint modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences =

getSharedPreferences(MIS_PREFS modo)

boolean isTrue = misSharedPreferencesgetBoolean(isTruefalse)float unFloat = misSharedPreferencesgetFloat(unFloat0)int numeroEntero = misSharedPreferencesgetInt(numeroEntero0)long otroNumero = misSharedPreferencesgetLong(otroNumero0)String unaCadena = misSharedPreferencesgetString(unaCadena)

313 Interfaces para Shared Preferences

Android proporciona una plataforma basada en XML para la creacioacuten de interfacesgraacuteficas para el manejo de preferencias Estas interfaces tendraacuten un aspecto similar al delas del resto de aplicaciones del sistema Al utilizarla nos estaremos asegurando de quenuestras actividades para el manejo de preferencias seraacuten consistentes con las del resto deaplicaciones del sistema De esta forma los usuarios estaraacuten familiarizados con el manejode esta parte de nuestra aplicacioacuten

La plataforma consiste en tres elementos

bull Layout de la actividad de preferencias se trata de un archivo XML que definiraacute lainterfaz graacutefica de la actividad que muestre los controles para modificar los valores delas preferencias Permite especificar las vistas a mostrar los posibles valores que sepueden introducir y a queacute clave de Shared Preferences se corresponde cada vista

bull Actividad de preferencias una subclase de PreferenceActivity que secorresponderaacute con la pantalla de preferencias de nuestra aplicacioacuten

bull Shared Preference Change Listener una implementacioacuten de la claseonSharedPreferenceChangeListener que actuaraacute en el caso en el que cambie elvalor de alguna Shared Preference

314 Definiendo una pantalla de preferencias con un layout en XML

Persistencia

20Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esta es sin duda la parte maacutes importante de una actividad de preferencias Se trata de unarchivo XML utilizado para definir varios aspectos de dicha actividad Al contrario queen el caso de los archivos de layout para interfaces graacuteficas que vimos en la sesioacutencorrespondiente del moacutedulo de Android estos archivos de layout se guardan en la carpetaresxml de los recursos de la aplicacioacuten

Aunque conceptualmente estos layouts son similares a los utilizados para definirinterfaces graacuteficas los layouts de actividades de preferencias utilizan un conjuntoespecializado de componentes especiacuteficos para este tipo de actividades Estaacuten pensadospara proporcionar interfaces con un aspecto similar al del resto de actividades depreferencias del sistema Estos controles se describen en maacutes detalle en la siguienteseccioacuten

Los layout de preferencias contendraacuten un elemento PreferenceScreen

ltxml version=10 encoding=utf-8gtltPreferenceScreen

xmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceScreengt

Se pueden antildeadir maacutes elementos de este tipo si se desea Al hacerlo eacutestos serepresentaraacuten como un elemento seleccionable en un listado que mostraraacute una nuevaventana de preferencias en caso de que sea seleccionado

Dentro de cada elemento PreferenceScreen podemos incluir tantos elementos de tipoPreferenceCategory y elementos especiacuteficos para antildeadir controles como se desee Loselementos PreferenceCategory son utilizados para dividir cada pantalla de preferenciasen subcategoriacuteas mediante una barra de tiacutetulo Su sintaxis es la siguiente

ltPreferenceCategoryandroidtitle=My Preference Categorygt

ltPreferenceCategorygt

Una vez dividida la pantalla en subcategoriacuteas tan soacutelo quedariacutea antildeadir los elementoscorrespondientes a los controles especiacuteficos que veremos en la siguiente seccioacuten Losatributos de los elementos XML para estos controles pueden variar aunque existe unconjunto de atributos comunes

bull androidkey la clave de Shared Preference que se usaraacute para guardar el valor delcontrol

bull androidtitle texto a mostrar para describir la preferenciabull androidsummary una descripcioacuten maacutes larga a mostrar debajo del texto definido con

el campo anteriorbull androiddefaultValue el valor por defecto que se mostraraacute inicialmente y tambieacuten

el que finalmente se guardaraacute si ninguacuten otro valor fue asignado a este campo

El siguiente listado muestra una pantalla de preferencias muy simple con una uacutenicacategoriacutea y un uacutenico control (un check box)

ltxml version=10 encoding=utf-8gt

Persistencia

21Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltPreferenceScreenxmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceCategory

androidtitle=Mi categoriacuteagtltCheckBoxPreference

androidkey=MI_CHECK_BOXandroidtitle=Preferencia Check Boxandroidsummary=Descripcioacuten de la preferencia

Check BoxandroiddefaultValue=true

gtltPreferenceCategorygt

ltPreferenceScreengt

Esta pantalla de preferencias se mostrariacutea de la siguiente forma

Ejemplo sencillo de ventana de preferencias

315 Controles nativos para preferencias

Android incluye diferentes controles que podemos antildeadir a nuestras ventanas depreferencias por medio de los elementos XML que se indican a continuacioacuten

bull CheckBoxPreference un check box estaacutendar que puede ser utilizado parapreferencias de tipo booleano

bull EditTextPreference permite al usuario introducir una cadena como valor para unapreferencia Al seleccionar el texto se mostraraacute un diaacutelogo con el que introducir elnuevo valor

bull ListPreference el equivalente a un Spinner Seleccionar este elemento de lainterfaz mostraraacute un diaacutelogo con las diferentes opciones que es posible seleccionar Seutilizan arrays para asociar valores y textos a las opciones de la lista

bull RingtonePreference un tipo especiacutefico de preferencia de tipo listado que permiteseleccionar entre diferentes tonos de teleacutefono Esta opcioacuten es particularmente uacutetil en

Persistencia

22Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

el caso en el que se esteacute creando una pantalla de preferencias para configurar lasopciones de notificacioacuten de una aplicacioacuten o componente de la misma

Tambieacuten es posible crear nuestros propios controles personalizados definiendo unasubclase de Preference (o cualquiera de sus subclases) Se puede encontrar maacutesinformacioacuten en la documentacioacuten de AndroidhttpdeveloperandroidcomreferenceandroidpreferencePreferencehtml

316 Actividades de preferencias

Para mostrar una pantalla de preferencias debemos definir una subclase dePreferenceActivity

public class MisPreferencias extends PreferenceActivity

La interfaz graacutefica de la interfaz se puede crear a partir del layout en el meacutetodo onCreatemediante una llamada a addPreferencesFromResource

Overridepublic void onCreate(Bundle savedInstaceState)

superonCreate(savedInstanceState)addPreferencesFromResource(Rxmlmilayout)

Como en el caso de cualquier otra actividad la actividad de preferencias que hayamosdefinido debe ser incluida en el Manifest de la aplicacioacuten

ltactivity androidname=MisPreferenciasandroidlabel=Mis preferenciasgt

ltactivitygt

Eso es todo lo que necesitamos para crear este tipo de actividades y mostrar una ventanacon las preferencias definidas en el layout Esta actividad puede ser iniciada comocualquier otra mediante un Intent ya sea a traveacutes de una llamada a startActivity obien a startActivityForResult

Intent i = new Intent(this MisPreferenciasclass)startActivityForResult(i MOSTRAR_PREFERENCIAS)

Los valores para las diferentes preferencias de la actividad son almacenadas en elcontexto de la aplicacioacuten Esto permite a cualquier componente de dicha aplicacioacutenincluyendo a cualquier actividad y servicio de la misma acceder a los valores tal como semuestra en el siguiente coacutedigo de ejemplo

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto) PENDIENTE hacer uso de meacutetodos get para obtener los valores

317 Shared Preference Change Listeners

Persistencia

23Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El uacuteltimo elemento que nos queda por examinar en esta plataforma de Shared Preferenceses la interfaz onSharedPreferenceChangeListener que es utilizada para invocar unevento cada vez que una preferencia es antildeadida eliminada o modificada Para registrarListeners usamos un coacutedigo como el que se muestra a continuacioacuten en el que la parte maacutesimportante es sin duda el meacutetodo onSharedPreferenceChanged dentro del cualdeberemos determinar queacute preferencia ha cambiado de estado a partir de sus paraacutemetrospara llevar a cabo las acciones oportunas

public class MiActividad extends Activity implementsOnSharedPreferenceChangeListener

Overridepublic void onCreate(Bundle SavedInstanceState)

Registramos este objetoOnSharedPreferenceChangeListener

Context contexto = getApplicationContext()SharedPreferences prefs =

PreferenceManagergetDefaultSharedPreferences(contexto)prefsregisterOnSharedPreferenceChangeListener(this)

public void onSharedPreferenceChanged(SharedPreferences prefsString clave)

PENDIENTE comprobar queacute clave concreta ha cambiado ysu nuevo valor

para modificar la interfaz de una actividad o elcomportamiento de

alguacuten componente

Otra forma maacutes sencilla de controlar los cambios en las preferencias es registrar unlistener en el constructor de la actividad principal o aquella que haga uso de los valores depreferencias de la forma

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto)

mListener = new OnSharedPreferenceChangeListener() public void onSharedPreferenceChanged(SharedPreferences

sharedPreferences String key)

if( keyequals(MICLAVE) )boolean valor =

sharedPreferencesgetBoolean(MICLAVE false)

prefsregisterOnSharedPreferenceChangeListener(mListener)

NotaEn este segundo ejemplo mListener es una variable miembro de la clase Para este caso esnecesario registrar el listener de esta forma porque sino el garbage collector lo eliminariacutea (debidoa particularidades en la programacioacuten de los listeners asociados a las SharedPreferences)

Persistencia

24Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

32 Proveedores de contenidos

Los proveedores de contenidos o ContentProvider proporcionan una interfaz parapublicar y consumir datos identificando la fuente de datos con una direccioacuten URI queempieza por content Son una forma maacutes estaacutendar que los adaptadores (como el quevimos en la sesioacuten anterior en la seccioacuten dedicada a SQLite) de desacoplar la capa deaplicacioacuten de la capa de datos

Podemos encontrar dos tipos principales de proveedores de contenidos en Android Losproveedores nativos son proveedores que el propio sistema nos proporciona para poderacceder a datos del dispostivo incluyendo audio viacutedeo imaacutegenes informacioacuten personaletc Por otra parte tambieacuten es posible que creemos nuestros propios proveedores con talde permitir a nuestra aplicacioacuten compartir datos con el resto del sistema En esta sesioacutenhablaremos de ambos tipos

321 Proveedores nativos

Android incluye una serie de proveedores de contenidos que nos pueden proporcionarinformacioacuten de diferentes tipos informacioacuten sobre nuestros contactos informacioacuten delcalendario e incluso ficheros multimedia Ejemplos de estos proveedores de contenidosnativos son el Browser CallLog ContactsContract MediaStore Settings y elUserDictionary Para poder hacer uso de estos proveedores de contenidos y acceder alos datos que nos proporcionan debemos antildeadir los permisos adecuados al ficheroAndroidManifestxml Por ejemplo para acceder al listiacuten telefoacutenico podemos antildeadir elpermiso correspondiente de la siguiente forma

ltuses-sdk androidminSdkVersion=8 gtltuses-permission androidname=androidpermissionREAD_CONTACTSgt

ltmanifestgt

Para obtener informacioacuten de un ContentProvider debemos hacer uso del meacutetodo query

de la clase ContentResolver El primero de los paraacutemetros de dicho meacutetodo seraacute la URIdel proveedor de contenidos al que deseamos acceder Este meacutetodo devuelve un cursorque nos permitiraacute iterar entre los resultados tal como se vio en la sesioacuten anterior en elapartado de cursores

ContentResolverquery(Uri uriString[] projectionString selectionString[] selectionArgsString sortOrder)

Por ejemplo la siguiente llamada nos proporcionaraacute un cursor que nos permitiraacute acceder atodos los contactos de nuestro teleacutefono moacutevil Para ello hemos hecho uso de la constante

Persistencia

25Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ContactsContractContactsCONTENT_URI que almacena la URI del proveedor decontenidos correspondiente Al resto de paraacutemetros se le ha asignado el valor null

ContentResolver cr = getContentResolver()Cursor cursor = crquery(ContactsContractContactsCONTENT_URI

null null null null)

NotaEn versiones anteriores a Android 20 las estructuras de datos de los contactos son diferentes y nose accede a esta URI

Una vez obtenido el cursor es posible mapear sus campos con alguacuten componente de lainterfaz graacutefica de la actividad De esta forma cualquier cambio que se produzca en elcursor se reflejaraacute automaacuteticamente en el componente graacutefico sin que sea necesario queprogramemos el coacutedigo que lo refresque Para esto tenemos que llamar al meacutetodosetNotificationUri del cursor con lo cual nos aseguraremos de que los listeners seraacutennotificados cuando se produzca alguacuten cambio en los datos referenciados por dicho cursor

cursorsetNotificationUri(crContactsContractContactsCONTENT_URI)

Para asignar un adaptador a una lista de la interfaz graacutefica de la actividad maacutesconcretamente una ListView utilizamos el meacutetodo setAdapter(Adapter)

ListView lv = (ListView)findViewById(RidListView01)SimpleCursorAdapter adapter = new SimpleCursorAdapter(

getApplicationContext()Rlayouttextviewlayoutcursornew String[]

ContactsContractContacts_IDContactsContractContactsDISPLAY_NAME

new int[]RidTextView1RidTextView2)

lvsetAdapter(adapter)

En este ejemplo los identificadores RidTextView1 y RidTextView2 se correspondencon views del layout que define cada fila de la ListView como se puede ver en elarchivo textviewlayoutxml que tendriacuteamos que crear

ltxml version=10 encoding=utf-8gt

ltLinearLayout xmlnsandroid=httpschemasandroidcomapkresandroidandroidorientation=horizontalandroidlayout_width=fill_parentandroidlayout_height=wrap_contentgt

ltTextView androidid=+idTextView1androidtextStyle=bold

Persistencia

26Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

androidems=2androidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltTextView androidid=+idTextView2androidtextStyle=boldandroidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltLinearLayoutgt

La lista en siacute (identificada por RidListView01 en el presente ejemplo) estariacutea en otroXML layout por ejemplo en mainxml

Se debe tener en cuenta que en el caso de este ejemplo si quisieacuteramos obtener losnuacutemeros de teleacutefono de cada uno de nuestros contactos tendriacuteamos que recorrer el cursorobtenido a partir del proveedor de contenidos y por cada persona en nuestra agenda crearun nuevo cursor que recorriera los teleacutefonos ya que una persona puede tener asignadosvarios teleacutefonos

Puedes acceder a un listado completo de los proveedores de contenidos nativos existentesen Android consultando su manual de desarrollo Concretamente leyendo la seccioacutendedicada al paquete androidprovider enhttpdeveloperandroidcomreferenceandroidproviderpackage-summaryhtml

322 Proveedores propios crear un nuevo proveedor de contenidos

Para acceder a nuestras fuentes de datos propias de manera estaacutendar nos interesaimplementar nuestros propios ContentProvider Gracias a que proporcionamos nuestrosdatos a partir de una URI quien use nuestro proveedor de contenidos no deberaacutepreocuparse de doacutende provienen los datos indicados por dicha URI podriacutean provenir deficheros locales en la tarjeta de memoria de un servidor en Internet o de una base dedatos SQLite Dada una URI nuestro proveedor de contenidos deberaacute proporcionar en suinterfaz los meacutetodos necesarios para realizar las operaciones baacutesicas en una base de datosinsercioacuten lectura actualizacioacuten y borrado

Para crear un nuevo proveedor de contenidos definimos una subclase deContentProvider Utilizaremos una sobrecarga del meacutetodo onCreate para crear einicializar la fuente de datos que queramos hacer puacuteblica a traveacutes de dicho proveedor Unesqueleto de subclase de ContentProvide podriacutea ser el siguiente

public class MiProveedor extends ContentProvider

Overridepublic boolean onCreate()

Inicializar la base de datosreturn true

Dentro de la clase deberemos crear un atributo puacuteblico y estaacutetico de nombreCONTENT_URI en el cual almacenaremos el URI completo del proveedor que estamos

Persistencia

27Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creando Este URI debe ser uacutenico por lo que una buena idea puede ser tomar como baseel nombre de paquete de nuestra aplicacioacuten La forma general de un URI es la siguiente

content[NOMBRE_PAQUETE]provider[NOMBRE_APLICACION][RUTA_DATOS]

Por ejemplo

contentesuajtechandroidprovidermiaplicacionelementos

En general (y por simplicidad) no es necesario antildeadir la cadena provider al nombre delpaquete por lo que podemos utilizar directamente el nombre del paquete seguido de laruta de datos de la forma

contentesuajtechandroidmiaplicacionelementos

Estos URIs se pueden presentar en dos formas La URI anterior representa una consultadirigida a obtener todos los valores de ese tipo (en este caso todos los elementos) Si a laURI anterior antildeadimos un sufijo [NUMERO_FILA] estamos representando una consultadestinada a obtener un uacutenico elemento (por ejemplo el quinto elemento en el siguienteejemplo)

contentesuajtechandroidprovidermiaplicacionelementos5

NotaSe considera una buena praacutectica de programacioacuten el proporcionar acceso a nuestro proveedor decontenidos utilizando cualquiera de estas dos formas

La forma maacutes simple de determinar cuaacutel de las dos formas anteriores de URI se utilizoacutepara hacer una peticioacuten a nuestro proveedor de contenidos es mediante un Uri MatcherCrearemos y configuramos un elemento Uri Matcher para analizar una URI y determinara cuaacutel de las dos formas se corresponde En el siguiente coacutedigo se muestra el coacutedigobaacutesico que deberemos emplear para implementar este patroacuten

public class MiProveedor extends ContentProvider

private static final String miURI =contentesuajtechandroidprovidermiaplicacionelementos

public static final Uri CONTENT_URI = Uriparse(miUri)

Creamos las constantes que nos van a permitir diferenciar entre los dos tipos distintos de peticiones a partir de la URIprivate static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

Inicializamos el objeto UriMatcher Una URI que termine en elementos se corresponderaacute con una consulta para todas las filas mientras que una URI con el sufijo elementos[FILA] representaraacute una uacutenica filastatic

uriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidprovidermiaplicacion

elementos TODAS_FILAS)

Persistencia

28Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

uriMatcheraddURI(esuajtechandroidprovidermiaplicacionelementos UNA_FILA)

Overridepublic boolean onCreate()

PENDIENTE inicializar la base de datosreturn true

Esta misma teacutecnica se puede emplear para proporcionar diferentes alternativas de URIpara diferentes subconjuntos de datos como por ejemplo diferentes tablas en una base dedatos a traveacutes del mismo proveedor de contenidos

323 Proveedores propios crear la interfaz de consultas

Para permitir realizar operaciones con los datos publicados a traveacutes de nuestro proveedorde contenidos deberemos implementar los meacutetodos delete insert update y query

para el borrado insercioacuten actualizacioacuten y realizacioacuten de consultas respectivamenteEstos meacutetodos son la interfaz estaacutendar utilizada con proveedores de contenidos de formaque para compartir datos entre aplicaciones no se tendraacuten interfaces distintos paradiferentes fuentes de datos

Lo maacutes habitual suele ser implementar un proveedor de contenidos como un mecanismopara permitir el acceso a una base de datos SQLite privada a una aplicacioacuten aunque atraveacutes de estos meacutetodos indicados se podriacutea en principio acceder a cualquier fuente dedatos

El siguiente coacutedigo extiende el ejemplo anterior (MiProveedor) y muestra el esqueleto deestos meacutetodos dentro de una subclase de ContentProvider Obseacutervese que se hace usodel objeto UriMatcher para determinar que tipo de consulta se estaacute realizando

Overridepublic Cursor query(Uri uri

String[] projectionString selectionString[] selectionArgsString sort)

Si se trata de una consulta para obtener una uacutenica filalimitamos

los resultados a obtener de la fuente de datos por medio de una claacuteusula whereswitch(UriMatchermatch(uri))

case UNA_FILA PENDIENTE modifica la sentencia SELECT

mediante una claacuteusula where obteniendo el identificador de fila

como numeroFila = urigetPathSegments()get(1))

return null

Override

Persistencia

29Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public Uri insert(Uri _uri ContentValues _valores) long idFila = [ Antildeadir un nuevo elemento ]

Devolvemos la URI del elemento recieacuten antildeadidoif (idFila gt 0)

return ContentUriswithAppendendId(CONTENT_URI idFila)

throw new SQLException(No se pudo antildeadir un nuevo elemento a +_uri)

Overridepublic int delete(Uri uri String where String[] whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

Overridepublic int update(Uri uri ContentValues valores String where String[]whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

324 Proveedores propios tipo MIME

El uacuteltimo paso para crear un proveedor de contenidos es definir el tipo MIME queidentifica el tipo de datos que devuelve el proveedor Debemos sobrecargar el meacutetodogetType para que devuelva una cadena que identifique nuestro tipo de datos de manerauacutenica Se deberiacutean definir dos tipos posibles uno para el caso de una uacutenica fila y otro parael caso de devolver todos los resultados Se debe utilizar una sintaxis similar a la delsiguiente ejemplo

bull Un uacutenico elementovndesuajtechandroidmiaplicacioncursoritemmiproveedorcontent

bull Todos los elementosvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

Como se puede observar la cadena comienza siempre por vnd A continuacioacutentendriacuteamos el nombre del paquete de nuestra aplicacioacuten Lo siguiente es cursor ya quenuestro proveedor de contenidos estaacute devolviendo datos de tipo Cursor Justo antes de labarra pondremos item en el caso del tipo MIME para un uacutenico elemento y dir para elcaso del tipo MIME para todos los elementos Finalmente tras la barra pondremos elnombre de nuestra clase (en minuacutesculas) seguido de content En el siguiente coacutedigo deejemplo se muestra coacutemo sobrecargar getType seguacuten la URI

Persistencia

30Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Overridepublic String getType(Uri _uri)

switch (uriMatchermatch(_uri)) case TODAS_FILAS

returnvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

case UNA_FILAreturn

vndesuajtechandroidmiaplicacioncursoritemmiproveedorcontentdefault

throw new IllegalArgumentException(URI nosoportada + _uri)

325 Proveedores propios registrar el proveedor

No debemos olvidar incorporar nuestro proveedor de contenidos al Manifest de laaplicacioacuten una vez que lo hemos completado Esto se haraacute por medio del elementoprovider cuyo atributo authorities podremos utilizar para especificar la URI base talcomo se puede ver en el siguiente ejemplo

ltprovider androidname=MiProveedorandroidauthorities=esuajtechandroidmiaplicaciongt

326 Content Resolvers

Aunque en la seccioacuten de proveedores nativos ya se ha dado alguacuten ejemplo de coacutemo haceruso de un Content Resolver para realizar una consulta ahora que hemos podido observaren detalle la estructura de un proveedor de contenidos propio es un buen momento paraver en detalle coacutemo realizar cada una de las operaciones que eacutestos permiten

El contexto de una aplicacioacuten incluye siempre una instancia de la claseContentResolver a la cual se puede acceder mediante el meacutetodo getContentResolver

ContentResolver cr = getContentResolver()

Esta clase incorpora diversos meacutetodos para realizar consultas a proveedores decontenidos Cada uno de estos meacutetodos acepta como paraacutemetro una URI que indica conqueacute proveedor de contenidos se desea interactuar

Las consultas realizadas sobre un proveedor de contenidos recuerdan a las consultasrealizadas sobre bases de datos Los resultados de las consultas se devuelven comoobjetos de la clase Cursor Se pueden extraer valores del cursor de la misma forma en laque se explicoacute en el apartado de bases de datos de la sesioacuten anterior El meacutetodo query dela clase ContentResolver acepta los siguientes paraacutemetros

bull La URI del proveedor del contenidos al que se desea realizar la consultabull Una proyeccioacuten que enumera las columnas que se quieren incluir en los resultados

obtenidosbull Una claacuteusula where que define las filas a obtener Se pueden utilizar comodines que

Persistencia

31Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

se reemplazaraacuten por valores incluidos en el siguiente paraacutemetrobull Una matriz de cadenas que se pueden utilizar para reemplazar los comodines en la

claacuteusula where anteriorbull Una cadena que describa la ordenacioacuten de los elementos devueltos

El siguiente coacutedigo muestra un ejemplo

ContentResolver cr = getContentResolver() Devolver todas las filasCursor todasFilas = crquery(MiProveedorCONTENT_URI null null nullnull) Devolver todas columnas de las filas para las que la columna 3 tiene un valor determinado ordenadas seguacuten el valor de la columna 5String where = COL3 + = + valorString orden = COL5Cursor algunasFilas = crquery(MiProveedorCONTENT_URI null where nullorden)

327 Otras operaciones con Content Resolvers

Para realizar otro tipo de operaciones con los datos de un proveedor de contenidosharemos uso de los meacutetodos delete update e insert de un objeto ContentResolver

Con respecto a la insercioacuten la clase ContentResolver proporciona en su interfaz dosmeacutetodos diferentes insert y bulkInsert Ambos aceptan como paraacutemetro la URI delproveedor de contenidos pero mientras que el primer meacutetodo tan solo toma comoparaacutemetro un objeto de la clase ContentValues (ya vistos en la seccioacuten de bases dedatos) el segundo toma como paraacutemetro una matriz de elementos de este tipo El meacutetodoinsert devolveraacute la URI del elemento recieacuten antildeadido mientras que bulkInsert

devolveraacute el nuacutemero de filas correctamente insertadas

Obtener el Content ResolverContentResolver cr = getContentResolver()

Crear una nueva fila de valores a insertarContentValues nuevosValores = new ContentValues()

Asignar valores a cada columnanuevosValoresput(NOMBRE_COLUMNA nuevoValor)[ Repetir para cada columna ]

Uri miFilaUri = crinsert(MiProveedorCONTENT_URI nuevosValores)

Insertamos ahora una matriz de nuevas filasContentValues[] arrayValores = new ContentValues[5] PENDIENTE rellenar el array con los valores correspondientesint count = crbulkInsert(MiProveedorCONTENT_URI arrayValores)

Para el caso del borrado hacemos uso del meacutetodo delete del Content Resolver pasandocomo paraacutemetro la URI de la fila que deseamos eliminar de la fuente de datos Otraposibilidad es incluir una claacuteusula where para eliminar muacuteltiples filas Ambas teacutecnicas semuestran en el siguiente ejemplo

ContentResolver cr = getContentResolver()

Persistencia

32Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Eliminar una fila especiacuteficacrdelete(miFilaUri null null) Eliminar las primeras cinco filasString where = _id lt 5crdelete(MiProveedorCONTENT_URI where null)

Una actualizacioacuten se lleva a cabo mediante el meacutetodo update del objeto ContentResolver Este meacutetodo toma como paraacutemetro la URI del proveedor de contenidosobjetivo un objeto de la clase ContentValues que empareja nombres de columna convalores actualizados y una claacuteusula where que indica queacute filas deben ser actualizadas Elresultado de la actualizacioacuten seraacute que en cada fila en la que se cumpla la condicioacuten de laclaacuteusula where se cambiaraacuten los valores de las columnas especificados por el objeto de laclase ContentValues tambieacuten se devolveraacute el nuacutemero de filas correctamenteactualizadas A continuacioacuten se muestra un ejemplo

ContentValues nuevosValores = new ContentValues()

Utilizamos el objeto ContentValues para especificar en queacute columnas queremos que se produzca un cambio y cuaacutel debe ser el nuevo valor de dichas columnasnuevosValuesput(NOMBRE_COLUMNA nuevoValor)

Aplicamos los cambios a las primeras cinco filasString where = _id lt 5

getContentResolver()update(MiProveedorCONTENT_URI nuevosValores wherenull)

Persistencia

33Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

4 Ejercicios - Persistencia en Android proveedores de contenidos ySharedPreferences

41 Compartir datos entre actividades con Shared Preferences (075 puntos)

Descarga de las plantillas el proyecto Dni Dicho proyecto estaacute compuesto de dosactividades Formulario y Resumen El objetivo del ejercicio es conseguir que al pulsar elbotoacuten Siguiente en la actividad Formulario se muestre en la actividad Resumen cuaacutelesfueron los datos introducidos

En este ejercicio vamos a hacer uso de SharedPreferences para pasar los datos de unaactividad a la siguiente Al pulsar en Siguiente en Formulario deberemos guardar el valorde todos los campos mediante esta plataforma Al mostrarse la actividad Resumendeberemos leer estos datos tambieacuten a partir de SharedPreferences para mostrarlos porpantalla

Por uacuteltimo crea un meacutetodo que valide el DNI (debe tratarse de una secuencia de ochodiacutegitos entre 0 y 9 y una letra al final) Si se pulsa Siguiente y el DNI no tiene el formatocorrecto no se deberaacute pasar a la actividad Resumen En lugar de esto se mostraraacute unToast en pantalla con un mensaje de error

42 Actividad de preferencias (075 puntos)

En este ejercicio seguimos trabajando con el proyecto del ejercicio anterior Vamos aantildeadir un menuacute de opciones que permita escoger en queacute momento se comprobaraacute lavalidez del DNI Para ello antildeade en primer lugar un menuacute a la actividad Formulario conuna uacutenica opcioacuten cuyo nombre seraacute Opciones

Al seleccionar esta opcioacuten se deberaacute mostrar una actividad de preferencias cuyo aspectoseraacute el siguiente

Persistencia

34Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Preferencias de la aplicacioacuten Dni

El significado de las opciones seraacute el siguiente

bull En el campo con esta opcioacuten seleccionada al pulsar sobre el campo de DNI en elformulario deberaacute mostrarse un Toast indicando si la sintaxis del DNI es correcta ono

bull Al pulsar si se desactiva esta opcioacuten (que deberaacute estar activada por defecto) no seharaacute la comprobacioacuten del DNI al pulsar el botoacuten Siguiente por lo que siempre seraacuteposible pasar de la actividad Formulario a la actividad Resumen sea cual sea elformato del DNI introducido

bull El uacuteltimo check box se encontraraacute siempre deshabilitado iquestQueacute atributo debemosutilizar para conseguir esto

Crea dos variables booleanas llamadas enElCampo y alPulsar Su valor dependeraacute delestado de los checkboxes anteriores y se actualizaraacute por medio del eventoonSharedPreferenceChange Utiliza el valor de estas variables en el coacutedigo de tuactividad para que esta tenga el comportamiento indicado

43 Proveedor de contenidos propio (075 puntos)

Vamos a implementar otra forma de acceder a la base de datos de usuarios de laaplicacioacuten BaseDatos desarrollada durante los ejercicios de la sesioacuten anterior siguiendoesta vez el patroacuten de disentildeo ContentProvider de Android Seguiremos trabajando puescon dicho proyecto

bull Creamos una nueva clase llamada UsuariosProvider que herede deContentProvider Esto nos obligaraacute a sobrecargar una serie de meacutetodos abstractosAntes de implementar la query vamos a configurar el provider

bull Antildeadimos algunos campos tiacutepicos de los content provider

Persistencia

35Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public static final Uri CONTENT_URI =Uriparse(contentesuajtechandroidbasedatosusuarios)

private static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

staticuriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

TODAS_FILAS)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

UNA_FILA)

bull Vamos a acceder a la misma base de datos de usuarios utilizada en los ejercicios de lasesioacuten anterior pero no vamos a hacerlo a traveacutes del adaptador que tuvimos queimplementar sino que vamos a copiar de eacutel el coacutedigo que nos haga falta Copia loscampos que definen el nombre de la base de datos de la tabla de las columnas laversioacuten asiacute como la referencia al contexto y a la base de datos La sentenciacompilada del insert ya no va a hacer falta Inicializa los valores necesarios en elconstructor Para inicializar la referencia a la base de datos vamos a utilizar una vezmaacutes MiOpenHelper Podemos copiarlo del adaptador que ya implementamos en losejercicios de la sesioacuten anterior

bull Implementa de forma apropiada el getType para devolver un tipo MIME diferenteseguacuten si se trata de una URI de una fila o de todas las filas Para ello ayuacutedate deluriMatcher

bull Implementa el meacutetodo query Simplemente se trata de devolver el cursor queobtenemos al hacer una consulta a la base de datos SQLite Algunos de los paraacutemetrosque le pasaremos los recibimos como paraacutemetros del meacutetodo del provider Los que notengamos iraacuten con valor null

bull Aunque no tenemos todos los meacutetodos del UsuariosProvider implementadospodemos probarlo Para ello debemos registarlo en el AndroidManifestxml

ltprovider androidname=UsuariosProvider

androidauthorities=esuajtechandroidbasedatosgtltapplicationgt

ltmanifestgt

bull En la clase Main se antildeadioacute codigo para insertar una serie de valores en la base dedatos y mostrarlos en el campo de texto Manteniendo este coacutedigo vamos a antildeadir alcampo de texto el resultado obtenido con la consulta del UsuariosProvider paracomprobar que tanto el adaptador de la base de datos como el proveedor nosdevuelven el mismo resultado

44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)

Porque es la forma estaacutendar que establece Android de acceder a contenidos Ademaacutes elproveedor de contenidos nos permitiraacute notificar al ContentResolver de los cambiosocurridos Asiacute componentes en la pantalla podraacuten refrescarse de forma automaacutetica

Persistencia

36Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Utiliza el proyecto ProveedorContenidos de las plantillas Implementa la insercioacuten enel proveedor de contenidos Prueacutebala insertando algunos usuarios de ejemplo en laclase Main Implementa tambieacuten el OnClickListener del botoacuten que inserta nuevosusuarios El nombre del nuevo usuario iraacute indicado en el EditText

bull Comprueba que la insercioacuten funciona y que gracias a la siguiente liacutenea y al estarusando un proveedor de contenidos la lista se actualiza automaacuteticamente cuandoocurre alguacuten cambio sin necesidad de pedir expliacutecitamente la actualizacioacuten al pulsarel botoacuten

cursorsetNotificationUri(cr UsuariosProviderCONTENT_URI)

NotaEsto no va a funcionar si se notifica que se ha producido un cambio al insertar o borrar unelemento Para ello usamosgetContext()getContentResolver()notifyChange(UsuariosProviderCONTENT_URI null)

bull Implementa el meacutetodo delete del proveedor de contenidos Prueacutebalo en la claseMain Termina de implementar el onCreateContextMenuListener que se ejecutaraacutecada vez que se haga una pulsacioacuten larga sobre alguna entrada de la lista Compruebaque funciona (eliminando el usuario correspondiente y no otro)

Persistencia

37Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

5 Persistencia de datos en iOS Ficheros y SQLite

51 Introduccioacuten

La persistencia de datos en iOS se realiza en una pequentildea memoria flash que tienenuestro dispositivo la cual es equivalente a un disco duro de tamantildeo limitado Los datosque ahiacute se almacenen se conservaraacuten aunque el dispositivo se apague Por motivos deseguridad el SO de iOS no permite que las aplicaciones accedan directamente a lamemoria interna pero a cambio existe una API completa de acceso a la porcioacuten dememoria que le corresponde a la app que desarrollemos

En una aplicacioacuten iOS encontraremos baacutesicamente 4 modos de almacenamiento deinformacioacuten de forma persistente

bull Ficheros de propiedades (plist)bull Bases de datos embebidas SQLitebull Datos de usuario (User Defaults)bull Core Data

En esta primera sesioacuten veremos coacutemo usar los ficheros de texto (Property Lists) y elalmacenamiento en base de datos de tipo SQLite

52 Ficheros plist (Property Lists)

El almacenamiento de datos en ficheros de propiedades es uno de los maacutes usados en eldesarrollo de las aplicaciones para iOS Se usa principalmente para guardar datos deconfiguracioacuten de la aplicacioacuten u otra informacioacuten poco compleja y acceder a ella deforma sencilla Si estamos desarrollando un juego podemos usarlos para la definicioacuten delos niveles almacenar las puntuaciones del jugador etc

521 Leyendo datos desde ficheros plist

Los ficheros plist (Property Lists) tienen un formato XML aunque XCode nosproporciona una interfaz que nos permite acceder a ellos de forma muy sencilla Paraentender el funcionamiento de este tipo de ficheros vamos a realizar un ejemplo en el queprimero crearemos un fichero plist lo completaremos con datos de ejemplo y despueacutes loleeremos para mostrarlo por pantalla

Un ejemplo de un fichero plist podriacutea ser el siguiente

Persistencia

38Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Fichero plist

Antes de nada creamos un proyecto nuevo en XCode de tipo Window-based

Application al que llamaremos sesion03-ejemplo1 Para crear un fichero plistdebemos hacer click derecho sobre el directorio Supporting Files de nuestro proyectoy seleccionamos iOS gt Resource gt Property List Lo guardaremos con el nombreconfigUsuario Seguidamente ya podemos acceder al fichero recieacuten creado haciendoclick sobre el en este momento XCode nos mostraraacute un pequentildeo editor totalmente vacioen el que iremos rellenando con datos Podemos verlo tambieacuten en formato XML sihacemos click derecho sobre el y seleccionamos Open As gt Preview

Para empezar a rellenar el fichero que acabamos de crear hacemos click derecho dentrode su contenido (ahora vacio) y seleccionamos Add row Entonces nos apareceraacute unafila vaciacutea Dentro del campo Key escribimos la clave un nombre descriptivo quedeberemos utilizar maacutes adelante para acceder a los datos En nuestro caso vamos aescribir config como clave Dentro de la columna Type seleccionamosDictionary Veremos que aparece una flecha en el lado izquierdo que indica quepueden haber subniveles dentro de nuestro diccionario

Ahora antildeadimos un nuevo item que cuelgue del diccionario que acabamos de crear paraello hacemos click sobre la flecha de la izquierda esta se giraraacute hacia abajo entonceshacemos click ahora sobre el siacutembolo + (maacutes) Nos apareceraacute una nueva liacutenea que cuelgadel diccionario

En esta nueva liacutenea dentro del campo key escribimos nombre el campo type lodejamos como de tipo String ya que va a ser una cadena de texto y en la columna deValue escribimos nuestro nombre por ejemplo Javi

Ahora vamos a antildeadir otro campo nuevo en nuestra configuracioacuten para ello hacemosclick sobre el siacutembolo + y nos apareceraacute una nueva liacutenea justo al mismo nivel que laanterior y colgando del diccionario Dentro del campo de Key escribimos ciudad yen Value escribimos Alicante El campo de Type lo dejamos como String

Persistencia

39Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora dentro de nuestro fichero plist vamos a indicar los dispositivos que dispone elusuario para ello creamos una nueva linea dentro del diccionario y le pondremos comoclave dispositivos Seleccionamos el tipo Array y seguimos los mismos pasosque al crear el diccionario para ello hacemos click sobre la flecha de la izquierda ydespueacutes sobre el siacutembolo + (maacutes) Ahora nos apareceraacute una nueva fila con clave Item 0Seleccionamos el tipo String y en el campo Value iPhone

Ahora repetimos el paso 3 veces indicando como values iPad Android y

Blackberry

Una vez seguidos todos estos pasos tendremos nuestro fichero de propiedades listo Debede quedar como se muestra a continuacioacuten

Fichero plist terminado

Este fichero se almacenaraacute dentro del directorio de bundle cuando compilemos laaplicacioacuten En el caso de que queramos escribir en el debermos almacenar el ficherodentro del directorio de Documents del binario

Ahora desde nuestro coacutedigo vamos a acceder a eacutel y a mostrarlo por pantalla para elloescribimos el siguiente coacutedigo dentro del meacutetodo didFinishLaunchingWithOptions dela clase delegada

Cargamos el fichero PLISTNSString mainBundlePath = [[NSBundle mainBundle] bundlePath]NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]NSDictionary diccionario = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionario delraiz

NSString nombre = [dicConfig objectForKeynombre]

Persistencia

40Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

[selfwindow makeKeyAndVisible]return YES

Si todo ha ido correctamente deberemos obtener la siguiente salida por la consola

Lista de dispositivos

522 Escribiendo datos en ficheros plist

Partiendo del ejemplo anterior vamos a escribir ahora en el fichero para ello primerodebemos comprobar que el fichero plist se encuentre dentro del directorio de Documents

del dispositivo en el caso de que no lo esteacute (por ejemplo si es la primera vez que arrancala aplicacioacuten) debemos copiarlo a ese directorio Una vez que tenemos el fichero dentrodel directorio de documentos ya podemos escribir en el

Para realizar todo el proceso de comprobacioacuten de la existencia del fichero dentro deldirectorio de documentos copiarlo en ese directorio y leer los datos debemos de sustituirel coacutedigo que hay dentro del meacutetodo didFinishLaunchingWithOptions de la clasedelegada por este otro

AtencioacutenPara que funcione la escritura en un fichero plist este debe de estar dentro del directorio deDocuments de nuestro dispositivo iOS no en el boundle

NSDictionary diccionarioRaiz

Ruta del directorio de documentos de nuestro dispositivoNSArray paths =

NSSearchPathForDirectoriesInDomains(NSDocumentDirectoryNSUserDomainMask YES)

if ([paths count] gt 0)

Persistencia

41Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

Primero comprobamos si existe el fichero en eldirectorio de documentos

BOOL fileExists = [[NSFileManager defaultManager]fileExistsAtPathdocumentsFilename]if (fileExists)

NSLog(Fichero encontrado en directorio de documentosOK

documentsFilename)

Si existe ya cargamos los datos desde aquidirectamente

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

else

NSLog(No se encuentra el fichero configUsuarioplisten

el directorio de documentos-gtLo creamos)

Si no existe primero cargamos el fichero PLISTdesde el Boundle

NSString mainBundlePath = [[NSBundle mainBundle]bundlePath]

NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Y despues escribimos los datos cargados (eldiccionario completo)

a un fichero nuevo de la carpeta de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

NSLog(plist de configUsuario creado)

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionariodel raiz

NSString nombre = [dicConfig objectForKeynombre]NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

Persistencia

42Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Por ultimo liberamos la memoria del diccionario raiz[diccionarioRaiz release]

[selfwindow makeKeyAndVisible]return YES

Para comprobar el funcionamiento arrancamos la aplicacioacuten y veremos que la primera vezse crearaacute el fichero dentro de la carpeta de Documents de nuestro dispositivo y en lassiguientes ya no se crearaacute y simplemente lo leeraacute desde la carpeta de Documents

Ahora que ya tenemos la gestioacuten de ficheros baacutesica completada vamos a escribir sobre elalguacuten dato Para ello simplemente escribimos el siguiente fragmento de coacutedigo justo antesde la liacutenea [selfwindow makeKeyAndVisible]

Cambiamos el nombre y lo guardamos en el fichero Ruta del directorio de documentos de nuestro dispositivo

if ([paths count] gt 0)

Cargamos el fichero desde el directorio dedocumentos del dispositivo

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cambiamos el valor del nombre en el diccionario[dicConfig setValueOtro nombre forKeynombre]

Escribimos todo el diccionario de nuevo al fichero del directorio de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

Por ultimo liberamos el diccionario de lamemoria

[diccionarioRaiz release]

Lo que hemos hecho en el coacutedigo anterior es cargar todo el diccionario de nuevo desde eldirectorio de documentos modificar los datos que queremos cambiar o incluso antildeadir ydespueacutes guardar el diccionario en el fichero

Volvemos a arrancar la aplicacioacuten y veremos que ha cambiado el campo de nombre

Nota

Persistencia

43Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

La lectura y escritura de datos en un fichero plist se debe de hacer de una soacutela vez nunca porpartes Siempre se debe de cargar o escribir un diccionario NSDictionary como elementoprincipal del fichero

53 SQLite

SQLite es una base de datos relacional ligera con la que se puede trabajar de formasencilla mediante sentencias SQL La base de datos se almacenaraacute en un fichero dentrode nuestra aplicacioacuten Este meacutetodo de persistencia puede ser el adecuado cuando los datosa almacenar esteacuten en un formato de tablas filas y columnas y se necesite que los datossean accesibles de forma raacutepida

531 Herramientas disponibles

Para explicar el funcionamiento de las librerias de SQLite de Cocoa Touch vamos a seguirun ejemplo completo Comenzamos creando la base de datos para ello podemos utilizaruno de los frontends que facilitan la gestioacuten del SQLite uno bastante sencillo yrecomendado es el SQLite Database Browser y otro es fmdb

Nosotros utilizaremos el primero (SQLite Database Browser) Abrimos la aplicacioacuten yhacemos click sobre el botoacuten de crear nueva base de datos que llamaremospersonasDBsqlite Seguidamente creamos las tablas para simplificar en nuestroejemplo crearemos una soacutela tabla llamada personas Esta tabla tendraacute las siguientescolumnas id (NUMERIC) nombre (TEXT) apellidos (TEXT) dni (TEXT) edad

(NUMERIC) y localidad (TEXT)

Persistencia

44Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Estructura de la Base de Datos

Una vez creada la tabla pasamos a insertar datos dentro de ella para ello abrimos lapestantildea de Browse Data y hacemos click en New Record Cuando tengamos al menos 3registros metidos dentro de la tabla ya podemos guardar la base de datos Hacemos clicksobre guardar le ponemos el nombre que queramos y elegimos cualquier directorio delMAC

Datos de la Base de Datos

532 Lectura de datos

Una vez que tenemos la base de datos SQLite creada ya podemos empezar a escribir elcoacutedigo para leer y mostrar los datos dentro de nuestra aplicacioacuten

Primero empezamos creando un proyecto nuevo en XCode usando la plantilla basada envistas View-based application y lo llamaremos sesion03-ejemplo2 Ahora antildeadimosel framework de sqlite a nuestro proyecto para ello hacemos click sobre el raiz delproyecto y seleccionamos la pestantildea Build Phases Desplegamos el listado de Link

Binary With Libraries y hacemos click sobre el botoacuten (+) El el listadoseleccionamos el framework libsqlite3dylib y hacemos click en Add

Persistencia

45Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Antildeadiendo el framework libsqlite3dylib

Ahora antildeadimos a nuestro proyecto la base de datos que hemos creado anteriormentepara ello arrastramos el fichero personasDBsqlite a nuestro directorio Supporting

Files Asegurate de seleccionar la opcioacuten de Copy items to destination groups

folder (if needed) para que se copie el fichero dentro de la carpeta del proyecto

Para acelerar el proceso de lectura de la base de datos y ahorrar memoria vamos a cargarprimero todos los datos en objetos para ello vamos a crear una clase que se encargue dealmacenar los datos Hacemos click derecho sobre el raiz del proyecto New file gtObjective-C class seleccionamos NSObject como clase principal y le damos a NextEscribimos como nombre de la clase Persona y hacemos click en save

Personah

imports iniciales

interface Persona NSObject int _uIdNSString _nombreNSString _apellidos

Persistencia

46Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSInteger _edadNSString _localidad

property (nonatomic assign) int uIdproperty (nonatomic copy) NSString nombreproperty (nonatomic copy) NSString apellidosproperty (nonatomic) NSInteger edadproperty (nonatomic copy) NSString localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidos edad(NSInteger)edadlocalidad(NSString )localidad

end

Personam

import Personah

implementation Persona

synthesize uId = _uIdsynthesize nombre = _nombresynthesize apellidos = _apellidossynthesize edad = _edadsynthesize localidad = _localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidosedad(NSInteger)edad localidad(NSString )localidad

if ((self = [super init])) selfuId = uIdselfnombre = nombreselfapellidos = apellidosselfedad = edadselflocalidad = localidad

return self

- (void) dealloc selfnombre = nilselfapellidos = nilselflocalidad = nil[super dealloc]

end

Una vez creada la clase Persona vamos a crear la clase encargada de manejar la basede datos sqlite3 Otra opcioacuten seriacutea cargar todos los datos directamente desde la clasedelegada al arrancar la aplicacioacuten pero no es recomendable ya que si en alguacuten momentotenemos que cambiar de motor de base de datos lo tendremos maacutes complicado

Persistencia

47Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creamos entonces una clase dentro de nuestro proyecto llamada BDPersistencia queherede de NSObject y escribimos el siguiente coacutedigo

BDPersistenciah

imports iniciales

interface BDPersistencia NSObject sqlite3 _database

+ (BDPersistencia)database- (NSArray )arrayPersonas

end

BDPersistenciam

import BDPersistenciahimport Personah

implementation BDPersistencia

static BDPersistencia _database

+ (BDPersistencia)database if (_database == nil)

_database = [[BDPersistencia alloc] init]return _database

- (id)init if ((self = [super init]))

NSString sqLiteDb = [[NSBundle mainBundle]pathForResourcepersonasDB

ofTypesqlite]

if (sqlite3_open([sqLiteDb UTF8String]amp_database) = SQLITE_OK)

NSLog(Fallo al abrir la BD)

return self

- (void)dealloc sqlite3_close(_database)[super dealloc]

- (NSArray )arrayPersonas

NSMutableArray arraySalida = [[[NSMutableArray alloc]init]

autorelease]

Persistencia

48Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString query = SELECT id nombre apellidoslocalidad edad

FROM personas ORDER BY apellidos DESC

sqlite3_stmt statementif (sqlite3_prepare_v2(_database [query UTF8String]

-1ampstatement nil)

== SQLITE_OK) while (sqlite3_step(statement) == SQLITE_ROW)

int uniqueId = sqlite3_column_int(statement0)

char nombreChar = (char )sqlite3_column_text(statement 1)

char apellidosChar = (char )sqlite3_column_text(statement 2)

char localidadChar = (char )sqlite3_column_text(statement 3)

int edad = sqlite3_column_int(statement 4)

NSString nombre = [[NSString alloc]initWithUTF8StringnombreChar]NSString apellidos = [[NSString alloc]initWithUTF8StringapellidosChar]NSString localidad = [[NSString alloc]initWithUTF8StringlocalidadChar]Persona persona = [[Persona alloc]initWithUiduniqueId nombrenombreapellidosapellidos edadedad

localidadlocalidad]

[arraySalida addObjectpersona][nombre release][apellidos release][localidad release][persona release]

sqlite3_finalize(statement)

return arraySalida

end

Con esto ya podemos leer de nuestra base de datos las personas Para ver que funcionatodo a la perfeccioacuten escribimos el siguiente coacutedigo dentro de la clase delegada en elmeacutetodo applicationDidFinishLaunching Antes debemos incluir en la parte superiorlas clases creadas

import BDPersistenciahimport Personah

NSArray personas = [BDPersistenciadatabase]arrayPersonas

for (Persona persona in personas) NSLog(d d personauId

personanombrepersonaapellidos personalocalidad personaedad)

Persistencia

49Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Si ejecutamos el proyecto obtendremos la siguiente salida por consola

Listado de personas

533 Mostrando los datos en una tabla

Una vez que tenemos el coacutedigo para obtener todas las filas de la tabla de personas de labase de datos queda lo maacutes faacutecil mostrarlas dentro de una vista de la aplicacioacuten En estecaso utilizaremos la vista de tabla UITableView

Comenzamos creando una nueva vista en nuestro proyecto para ello hacemos clickderecho sobre el raiz y seleccionamos New File gt UIVIewController SubclassAsegurate de marcar Subclass of UITableViewController y la opcoacuten de With

XIB for user interface Hacemos click en Next y guardamos el fichero comoPersonasViewController por ejemplo

Ahora abrimos el fichero PersonasViewControllerh y antildeadimos un NSArray queseraacute el que almacene las personas de la base de datos

imports iniciales

interface PersonasViewController UITableViewController

NSArray _listadoPersonas

property (nonatomic retain) NSArray listadoPersonas

end

Ahora abrimos el fichero PersonasViewControllerm antildeadimos el syntetizeimportamos los ficheros de gestioacuten de la base de datos la clase Persona y todo el coacutedigobaacutesico para la gestioacuten de la vista de la tabla

import PersonasViewControllerhimport Personahimport BDPersistenciah

implementation PersonasViewController

synthesize listadoPersonas = _listadoPersonas

Persistencia

50Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

- (id)initWithStyle(UITableViewStyle)style

self = [super initWithStylestyle]if (self)

Custom initializationreturn self

- (void)dealloc

[super dealloc]

selflistadoPersonas = nil

- (void)didReceiveMemoryWarning

Releases the view if it doesnt have a superview[super didReceiveMemoryWarning]

Release any cached data images etc that arent inuse

pragma mark - View lifecycle

- (void)viewDidLoad

[super viewDidLoad]

selftitle = Listado de personas

selflistadoPersonas = [BDPersistenciadatabase]arrayPersonas

- (void)viewDidUnload

[super viewDidUnload] Release any retained subviews of the main view eg selfmyOutlet = nil

- (void)viewWillAppear(BOOL)animated

[super viewWillAppearanimated]

- (void)viewDidAppear(BOOL)animated

[super viewDidAppearanimated]

- (void)viewWillDisappear(BOOL)animated

[super viewWillDisappearanimated]

- (void)viewDidDisappear(BOOL)animated

[super viewDidDisappearanimated]

- (BOOL)shouldAutorotateToInterfaceOrientation

Persistencia

51Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

(UIInterfaceOrientation)interfaceOrientation

Return YES for supported orientationsreturn (interfaceOrientation ==

UIInterfaceOrientationPortrait)

pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView(UITableView)tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection

(NSInteger)section

Return the number of rows in the sectionreturn [selflistadoPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath

(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]if (cell == nil)

cell = [[[UITableViewCell alloc]initWithStyleUITableViewCellStyleSubtitle

reuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona persona = (Persona )[selflistadoPersonas

objectAtIndexindexPathrow]celltextLabeltext = [NSString stringWithFormat

(d) personanombrepersonaapellidos personaedad]celldetailTextLabeltext = personalocalidad

return cell

pragma mark - Table view delegate

- (void)tableView(UITableView )tableViewdidSelectRowAtIndexPath

(NSIndexPath )indexPath

Navigation logic may go here Create and pushanother view controller

end

Persistencia

52Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora soacutelo nos queda incorporar la vista que acabamos de crear dentro de nuestraaplicacioacuten Para ello vamos a llamarla desde el Delegate y a meterla dentro de unNavigation Controller Primero debemos de antildeadir un Outlet en la clasedelegada hacia un Navigation Controller

imports iniciales

interface sesion03_ejemplo1AppDelegate NSObjectltUIApplicationDelegategt

UIWindow _windowUINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet

UINavigationController navController

end

Ahora hacemos click sobre la vista MainWindowxib y arrastramos un Navigation

Controller desde el listado de la columna de la derecha hacia la columna de laizquierda donde pone Objects Nos apareceraacute el Navigation Controller dibujadoen pantalla Ahora desplegamos el Navigation Controller y hacemos click sobre elView Controller que hay dentro Nos vamos a la pestantildea de Identity Inspector

de la columna de la derecha y escribimos dentro de Class el nombre de la vista dellistado de personas PersonasViewController

Finalmente hacemos click sobre el objeto Delegate vamos a la pestantildea de Show

Connections Inspector y arrastramos desde navController hasta el objetoNavigation Controller de la columna de la izquierda Ya tenemos los controladoresy las vistas conectadas

Una vez hecho esto nos queda indicar dentro de nuestro coacutedigo que la aplicacioacuten arranquecon el Navigation Controller para ello escribimos lo siguiente al final del meacutetododidFinishLaunchingWithOptions de la implementacioacuten de la clase delegada

selfwindowrootViewController = selfnavController[selfwindow makeKeyAndVisible]return YES

Ya estaacute listo todo y preparado para compilar Si ejecutamos el proyecto veremos que nosaparece el listado de personas en la vista de tabla

Persistencia

53Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios

61 Creando y leyendo un fichero plist (15 puntos)

En este primer ejercicio vamos a crear nuestra primera aplicacioacuten usando el meacutetodo depersistencia de ficheros La aplicacioacuten mostraraacute en una vista de tabla TableView unlistado de series en la que si seleccionamos una veremos su informacioacuten maacutes importanteen otra vista Comenzaremos creando un fichero de propiedades (plist) siguiendo unaestructura determinada y posteriormente mostraremos en una vista de tabla todos susdatos iexclComenzamos

1) Crear un proyecto usando la plantilla Master-Detail application Lo llamaremosejercicio-plist-ios como identificador de empresa escribiremos esuajtech y como prefijode clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremos UseAutomatic Reference Counting para olvidarnos de la gestioacuten de memoria El resto deopciones las dejaremos sin marcar

2) Crear un archivo de propiedades (plist) con el nombre de series

3) Editar el archivo plist creando la siguiente estructura de datos (5 series miacutenimo)

Plist series

4) Rellenar el archivo plist con datos de series reales

5) Escribir el coacutedigo necesario en UAMasterViewControllerm que recorra el archivoplist que acabamos de crear y muestre por consola los titulos de las series

6) Mostrar en la tabla que hay implementada los tiacutetulos de la serie Para ello deberemosde completar todos los meacutetodos del protocolo del TableView

7) Ejecutar la aplicacioacuten y comprobar que funciona correctamente

Persistencia

54Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

62 Implementar vista de detalle de la serie (05 puntos)

Dentro del proyecto anterior (no hace falta crear uno nuevo) vamos a mostrar toda lainformacioacuten adicional de cada una de las series en una vista de detalle Cuandoseleccionemos una fila de la tabla nos deberaacute aparecer una nueva vista con el resto deinformacioacuten (antildeo director principal sinopsis y duracioacuten)

63 Uso baacutesico de una base de datos SQLite (1 punto)

En este ejercicio vamos a crear la misma aplicacioacuten que en el ejercicio anterior perousando el meacutetodo de persistencia de SQLite Para completar el ejercicio deberemos seguirlos siguientes pasos

1) Crear un proyecto usando la plantilla Master-Detail application lo llamaremosejercicio-sqlite-ios como identificador de empresa escribiremos esuajtech y comoprefijo de clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremosUse Automatic Reference Counting para olvidarnos de la gestioacuten de memoria El restode opciones las dejaremos sin marcar

2) Nos descargamos e instalamos el programa SQLite Database Browser desde estadireccioacuten

3) Disentildear la estructura de la base de datos usando SQLite Database Browser tal y comose muestra en la imagen siguiente

Sqlite series

4) Insertar al menos 5 series en la base de datos usando el programa SQLite DatabaseBrowser

5) Cargar todos los datos de la base de datos desde el meacutetododidFinishLaunchingWithOptions de la clase UAAppDelegate Mostrar por consola losdatos cargados

6) Mostrar los datos cargados en el paso anterior en la vista de tabla UITableView quehay creada en la clase UAMasterViewController

Persistencia

55Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7) Arrancar la aplicacioacuten y comprobar que se muestran las series en la tabla de formacorrecta

Persistencia

56Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7 Persistencia de datos en iOS User Defaults y Core Data

71 Introduccioacuten

En esta sesioacuten veremos como utilizar dos nuevos meacutetodos de persistencia en iOS ambosmuy interesantes y usados muy frecuentemente

bull User Defaults Muy uacutetil para almacenar datos simples y de uso repetido dentro denuestras aplicaciones iOS

bull Core Data Meacutetodo avanzado de almacenamiento de objetos enteros dentro de lamemoria del dispositivo iOS Muy uacutetil para almacenar datos complejos conrelaciones etc

72 User Defaults

Este meacutetodo es muy uacutetil cuando queremos almacenar pequentildeas cantidades de datos comopuntuaciones informacioacuten de usuario el estado de nuestra aplicacioacuten configuracionesetc

Este meacutetodo es el maacutes raacutepido de usar ya que no requiere de una base de datos ni ficherosni ninguna otra capa intermedia Los datos se almacenan directamente en la memoria deldispositivo dentro de un objeto de la clase NSUserDefaults

Para aprender a usar este meacutetodo de almacenamiento de datos vamos a crear unaaplicacioacuten muy simple que lea un nombre y una edad de pantalla y las guarde en unobjeto NSUserDefaults Para ello seguimos los siguientes pasos

bull 1) Comenzamos creando un nuevo proyecto llamado sesion04-ejemplo1 dentrode XCode

bull 2) Abrimos la vista sesion04_ejemplo1ViewController y arrastramos dos TextField un botoacuten y dos labels quedando la vista de la siguiente manera

Persistencia

57Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista de la aplicacioacuten

bull 3) Abrimos el fichero sesion04_ejemplo1ViewControllerh y definimos el Outletdel evento onclick del botoacuten El fichero quedaraacute de la siguiente manera

imports iniciales

interface sesion04_ejemplo1ViewController UIViewController UITextField tfNombreUITextField tfEdad

property(nonatomic retain) IBOutlet UITextField tfNombreproperty(nonatomic retain) IBOutlet UITextField tfEdad

-(IBAction) guardaDatos

end

bull 4) Ahora implementamos el coacutedigo para el meacutetodo del botoacuten dentro desesion04_ejemplo1ViewControllerm

synthesize tfEdadsynthesize tfNombre

-(IBAction) guardaDatos NSLog(Guardamos los datos)

Persistencia

58Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creamos el objeto NSUserDefaultsNSUserDefaults datos = [NSUserDefaults standardUserDefaults]

guardamos el nombre (NSString)[datos setObjecttfNombretext forKeynombre]

guardamos la edad (Integer)[datos setInteger[tfEdadtext integerValue] forKeyedad]

almacenamos los datos en la memoria[datos synchronize]

bull 5) Tenemos que enlazar el botoacuten con el evento que hemos programado en el pasoanterior para ello abrimos la vista sesion04_ejemplo1ViewControllerxib y conla tecla ctlr apretada arrastramos desde el botoacuten hasta Files Owner (columna de laizquierda parte superior) y seleccionamos el meacutetodo que hemos creado(guardaDatos) De esta forma ya tenemos conectado el botoacuten

bull 6) Ahora hacemos lo mismo con los Text Field Tenemos que conectarlos alcontrolador para ello seleccionamos el objeto Files Owner y abrimos la pestantildeade conexiones (la que estaacute maacutes a la derecha en la columna de la derecha) Hacemosclick en tfEdad y arrastramos hasta el Text Field de edad Hacemos lo mismo para eltext field de nombre Ya tenemos todos los elementos de la vista conectados

Conexioacuten de Outlets

bull 7) Arrancamos la aplicacioacuten y la probamos

Persistencia

59Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Aplicacioacuten

bull 8) Ahora vamos a hacer que la aplicacioacuten cargue los datos que se han guardado alarrancar la vista para ello simplemente tenemos que escribir el siguiente coacutedigo en elmeacutetodo (void)viewDidLoad de la clase sesion04_ejemplo1ViewControllerm

- (void)viewDidLoad

[super viewDidLoad]

NSUserDefaults datos = [NSUserDefaults standardUserDefaults]

obtenemos un NSStringNSString nombre = [datos stringForKeynombre]

obtenemos un NSIntegerNSInteger edad = [datos integerForKeyedad]

if (nombre=nil ampamp edad=0)NSLog(Nombre cargado edad dnombreedad)

else

NSLog(Sin datos)

bull 9) Ya podemos arrancar de nuevo la aplicacioacuten y veremos que si escribimos cualquier

Persistencia

60Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

texto en los Text Fields estos se almacenaraacuten dentro del objeto NSUserDefaults Alapagar la aplicacioacuten y volver a arrancarla estos datos se cargaraacuten (apareceraacuten enconsola)

Consola

NotaLos datos permaneceraacuten en nuestro dispositivo hasta que se elimine la aplicacioacuten en ese casotodos los datos guardados en NSUserDefaults se borraraacuten

bull 10) Probar eliminar la aplicacioacuten del dispositivosimulador y volver arrancarComprobamos que los datos se han borrado

Consola

73 Core Data

De todas las formas de persistencia de datos que existen en iOS Core Data es la maacutesapropiada para usar en caso de manejar datos no triviales con relaciones algo maacutescomplejas entre ellos El uso de Core Data dentro de nuestra aplicacioacuten optimiza almaacuteximo el consumo de memoria mejora el tiempo de respuesta y reduce la cantidad decoacutedigo redundante a escribir

Core Data utiliza de fondo el meacutetodo de almacenamiento SQLite visto en la sesioacuten 3 deeste moacutedulo por tanto para explicar el funcionamiento de este meacutetodo de persistenciavamos a utilizar el mismo modelo de datos que usamos en esa sesioacuten Persona -gtDetallePersona

El el siguiente ejemplo haremos la misma aplicacioacuten que hicimos con SQLite perousando este nuevo meacutetodo de persistencia

731 Creando el proyecto

Para empezar tenemos que crearnos un nuevo proyecto en XCode Para ello abrimosXCode y seleccionamos crear una nueva aplicacioacuten basada en ventanas (Window-basedapplication) Hacemos click en Next escribimos como nombre del proyectosesion04-ejemplo2 y seleccionamos Use Core Data

Persistencia

61Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creando un proyecto nuevo

Con esto ya tenemos la estructura baacutesica para empezar a programar Antes de nadahechamos un vistazo raacutepido a toda la estructura de directorios creada y vemos que dentrodel directorio principal tenemos un archivo que se llamasesion04_ejemplo2xcdatamodeld Al hacer click sobre el archivo se nos abre uneditor especial que en principio estaacute vacio y que maacutes adelante utilizaremos para disentildearnuestro modelo de datos

Ahora hechamos un vistazo a la clase delegada sesion04_ejemplo2AppDelegatemvemos que hay unos cuantos meacutetodos nuevos que serviraacuten para configurar el Core DataVamos a explicar los maacutes importantes

bull - (NSManagedObjectModel )managedObjectModel NSManagedObjectModel esla clase que contiene la definicioacuten de cada uno de los objetos o entidades quealmacenamos en la base de datos Normalmente este meacutetodo no lo utilizaremos ya quenosotros vamos a utilizar el editor que hemos visto antes con el que podremos crearnuevas entidades crear sus atributos y relaciones

bull - (NSPersistentStoreCoordinator )persistentStoreCoordinator Aquiacute esdonde configuramos los nombres y las ubicaciones de las bases de datos que se usaraacutenpara almacenar los objetos Cuando un managed object necesite guardar algo pasaraacutepor este meacutetodo

bull - (NSManagedObjectContext )managedObjectContext Esta claseNSManagedObjectContext seraacute la maacutes usada con diferencia de las tres y por tanto la

Persistencia

62Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

maacutes importante La utilizaremos baacutesicamente para obtener objetos insertarlos oborrarlos

732 Definiendo el modelo de datos

En el ejemplo que hicimos en la sesioacuten anterior de SQLite creamos dos tablas una paralos datos baacutesicos de la persona y otra para el detalle Ahora haremos lo mismo perousando Core Data la diferencia principal es que mientras que con SQLite obteniamos soacutelolos datos que necesitaacutebamos de la base de datos aquiacute obtenemos el objeto completo

Para definir nuestro modelo de datos basado en dos objetos (Persona y DetallePersona)abrimos el editor visual haciendo click sobre el ficherosesion04_ejemplo2xcdatamodeld Empezaremos creando una nueva entidad para ellohacemos click sobre el botoacuten Add Entity (+) que se encuentra en la parte inferior dela pantalla A la nueva entidad le pondremos como nombre Persona

Dentro de la seccioacuten de Attributes hacemos click sobre (+) y creamos un nuevoatributo para nuestra entidad le llamamos nombre y le asignamos como tipo StringRealizamos este mismo paso 2 veces maacutes para antildeadir apellidos y edad este uacutetlimode tipo Integer 16

Atributos Persona

Ahora creamos la entidad de DetallePersona de la misma manera que la anterior perocreando los atributos localidad pais direccion cp y telefono Todasde tipo String

Atributos DetallePersona

Finalmente nos queda relacionar de alguacuten modo las dos entidades para ello antildeadimos una

Persistencia

63Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

relacioacuten dentro de la entidad de Persona hacia DetallePersona simplementehaciendo click sobre el botoacuten (+) de la seccioacuten de Relationships A la relacioacuten leponemos como nombre detalle seleccionamos como destino la entidadDetallePersona y seraacute No inverse Este tipo de relacioacuten es uno a uno Por dentroCore Data realizaraacute crearaacute una clave ajena en la tabla de persona que apunte aDetallePersona pero eso a nosotros no nos interesa

Vista general modelo de datos (1)

Apple recomienda que siempre que hagamos una relacioacuten de una entidad a otra hagamostambieacuten la relacioacuten inversa por lo tanto hacemos justo lo anterior pero al reveacutes (deDetallePersona a Persona) con la diferencia que en la columna de Inverse

seleccionamos datosPersona

Ya tenemos las entidades y las relaciones creadas Si hacemos click en el botoacuten deEditor Style en la parte de abajo a la derecha del editor podemos ver de una formamaacutes clara la estructura que hemos disentildeado

Persistencia

64Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista general modelo de datos (2)

733 Probando el modelo

Una vez que tenemos el modelo creado pasamos a probarlo para comprobar que estaacute todocorrecto para ello abrimos la clase delegada (sesion04_ejemplo2AppDelegatem) yantildeadimos el siguiente fragmento de coacutedigo arriba del meacutetodoapplicationDidFinishLaunching

NSManagedObjectContext context = [self managedObjectContext]NSManagedObject persona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

[persona setValueJuan forKeynombre][persona setValueMartinez forKeyapellidos][persona setValue[NSNumber numberWithInt28] forKeyedad]

NSManagedObject detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersonainManagedObjectContextcontext]

[detallePersona setValueCalle Falsa 123 forKeydireccion][detallePersona setValueAlicante forKeylocalidad][detallePersona setValueEspantildea forKeypais][detallePersona setValue965656565 forKeytelefono]

[detallePersona setValuepersona forKeydatosPersona][persona setValuedetallePersona forKeydetalle]

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Persistencia

65Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Con este coacutedigo hemos creado un objeto Persona enlazado con otro objetoDetallePersona iexclComo puedes ver no hemos necesitado nada de coacutedigo SQLPrimero creamos un objeto del tipo NSManagedObjectContext que almacena el conextode Core Data Despueacutes creamos el objeto Persona en siacute con NSManagedObject A esteobjeto le pondremos los valores para cada una de las propiedades definidas en el modeloHacemos lo mismo con DetallePersona Finalmente relacionamos el objetoPersona con el objeto DetallePersona

Compilamos el proyecto comprobamos que todo funciona bien y no salen errores

Para comprobar que los datos se han almacenado correctamente en nuestro dispositivo losmostramos en la consola de la siguiente manera

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

intValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(------------------)

[fetchRequest release]

El coacutedigo de arriba lo escribiremos dentro del meacutetododidFinishLaunchingWithOptions de la clase delegada Lo que hace este coacutedigo esrecorrer todos los objetos almacenados en memoria y mostrarlos por consola Siarrancamos la aplicacioacuten veremos que se muestran todos los datos de los objetosalmacenados Cada vez que arranquemos la aplicacioacuten se mostraraacuten maacutes objetos ya quetambieacuten los creamos

Para listar los objetos usamos la clase NSFetchRequest Esta clase es equivalente aejecutar una sentencia Select de SQL Al objeto NSFetchRequest le asignamos laentidad que queremos listar (SELECT FROM ) en nuestro caso seraacute PersonaEl listado de los objetos se obtiene mediante el meacutetodo executeFetchRequest de laclase NSManagedObjectContext que hemos definido en el bloque anterior de coacutedigoEste meacutetodo devolveraacute un NSArray con todos los objetos NSManagedObject

Persistencia

66Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Salida de consola

NotaSi queremos ver queacute sentencias SQL estaacute ejecutando Core Data por debajo tenemos que activarel flag de debug para ello seleccionamos la opcioacuten Product gt Edit Scheme Enla columna de la izquierda seleccionamos el esquema que estamos utilizando para Debug y en laparte de la derecha en el cuadro de Arguments Passed on Launch antildeadimos 2paraacutemetros -comappleCoreDataSQLDebug y 1

Ahora arrancamos de nuevo la aplicacioacuten y veremos que en la consola apareceraacuten lassentencias SQL utilizadas por Core Data

()

INSERT INTO ZDETALLEPERSONA(Z_PK Z_ENT Z_OPT ZDATOSPERSONA ZTELEFONOZLOCALIDAD ZDIRECCION ZPAIS)VALUES( )CoreData sql SELECT 0 t0Z_PK t0Z_OPT t0ZTELEFONO t0ZLOCALIDADt0ZDIRECCION t0ZPAIS t0ZDATOSPERSONAFROM ZDETALLEPERSONA t0 WHERE t0Z_PK =

()

734 Generando los modelos automaacuteticamente

Generar los modelos a mano directamente desde el editor no es lo maacutes recomendado yaque podemos cometer errores de escritura errores de tipo etc La mejor manera dehacerlo es creando un archivo para cada entidad Esto se puede hacer desde 0 peroXCode tiene un generador de clases que es muy faacutecil de usar y ahorremos bastantetiempo iexclVamos a usarlo

Hacemos click sobre el raiz de nuestro proyecto y seleccionamos New File En lacolumna de la izquierda seleccionamos iOS gt Core Data y en el cuadro de laderecha NSManagedObject subclass Click en next En la siguiente pantallaseleccionamos nuestro modelo de datos sesion04_ejemplo2 y click en next Ahoranos aseguramos de seleccionar las dos entidades que hemos creado anteriormentePersona y DetallePersona click en next Seleccionamos el raiz de nuestroproyecto para guardar los ficheros que se generen y aceptamos

Persistencia

67Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ventana de seleccioacuten de entidad

Ahora veremos que xsource ha creado automaacuteticamente 4 ficheros nuevos dentro denuestro proyecto Personamh y DetallePersonamh con todos los atributosespecificados Estas nuevas clases que heredan de NSManagedObject seraacuten las queutilicemos a partir de ahora

Cambiamos el coacutedigo de la clase delegada por el siguiente

NSManagedObjectContext context = [self managedObjectContext]Persona datosPersona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

datosPersonanombre = JuandatosPersonaapellidos = MartinezdatosPersonaedad = [NSNumber numberWithInt28]

DetallePersona detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersona

inManagedObjectContextcontext]detallePersonadireccion = Calle Falsa 123

Persistencia

68Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

detallePersonalocalidad = AlicantedetallePersonapais = EspantildeadetallePersonatelefono = 965656565

detallePersonadatosPersona = datosPersonadatosPersonadetalle = detallePersona

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Listamos los datosNSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

integerValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(-----------------)[fetchRequest release]

Ejecutamos el proyecto y comprobamos que en la consola aparece lo mismo que antespero ahora tenemos el coacutedigo bastante maacutes claro

735 Creando la vista de tabla

Ahora vamos a mostrar los objetos que antes hemos listado por consola en una vista detabla UITableView Para ello creamos un UITableViewController

Click en New file gt UIViewController subclass En la siguiente pantallanos aseguramos de seleccionar Subclass of UITableViewController de esta formanos apareceraacuten ya todos los meacutetodos de la tabla creados por defecto Guardamos elarchivo con el nombre que queramos por ejemplo PersonasViewController

Abrimos ahora PersonasViewControllerh y antildeadimos 2 atributos a la clase

bull Un NSArray que se seraacute el listado de personas (objetos de la clase Persona)bull Una referencia a NSManagedObjectContext

El fichero PersonasViewControllerh quedaraacute de la siguiente manera

Persistencia

69Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

imports iniciales

interface PersonasViewController UITableViewController NSArray _listaPersonasNSManagedObjectContext _context

property (nonatomic retain) NSArray listaPersonasproperty (nonatomic retain) NSManagedObjectContext context

end

Ahora en el m importamos en el encabezado las clases Personah yDetallePersonah y en el meacutetodo viewDidLoad creamos el array listaPersonas contodos los objetos Persona de forma similar a cuando lo hicimos en el apartado anterior

import PersonasViewControllerh

import Personahimport DetallePersonah

implementation PersonasViewController

synthesize listaPersonas = _listaPersonassynthesize context = _context

En viewDidLoad

- (void)viewDidLoad

[super viewDidLoad]

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContext_context]

[fetchRequest setEntityentity]NSError errorselflistaPersonas = [_context executeFetchRequestfetchRequest

erroramperror]selftitle = Listado Personas[fetchRequest release]

Ahora completamos el resto de meacutetodos heredados de UITableViewController de lamisma forma que hicimos en sesiones anteriores

- (NSInteger)numberOfSectionsInTableView(UITableView )tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection(NSInteger)section

Persistencia

70Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Return the number of rows in the sectionreturn [_listaPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]

if (cell == nil) cell = [[[UITableViewCell alloc]

initWithStyleUITableViewCellStyleSubtitlereuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona p = (Persona )[_listaPersonas objectAtIndexindexPathrow]celltextLabeltext = pnombrecelldetailTextLabeltext = [NSString stringWithFormatd antildeos

[pedad intValue]]

return cell

Ahora para poder mostrar la tabla dentro de nuestra aplicacioacuten debemos asignarla dentrode la clase delegada para ello abrimos la vista MainWindowxib y arrastramos unacontroladora de navegacioacuten Navigation Controller a la lista de objetos de laizquierda La desplegamos y dentro de View Controller en la pestantildea de la derechade Identity Inspector escribimos el nombre de nuestra clase que tiene la vista de latabla PersonasViewController

Pantalla del disentildeador

Ahora abrimos el fichero sesion04_ejemplo2AppDelegateh y creamos un

Persistencia

71Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

UINavigationController dentro de la declaracioacuten de variables dejando el ficherocomo sigue

imports iniciales

interface sesion04_ejemplo2AppDelegate NSObject ltUIApplicationDelegategt

UINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet UINavigationControllernavController

property (nonatomic retain readonly) NSManagedObjectContextmanagedObjectContextproperty (nonatomic retain readonly) NSManagedObjectModelmanagedObjectModelproperty (nonatomic retain readonly) NSPersistentStoreCoordinatorpersistentStoreCoordinator

- (void)saveContext- (NSURL )applicationDocumentsDirectory

end

Dentro del m importamos la clase PersonasViewControllerh y completamos elsynthetize

synthesize navController = _navController

Y por uacuteltimo justo antes de la llamada al meacutetodo makeKeyAndVisible escribimos elsiguiente coacutedigo para asignar el contexto de la clase delegada alPersonasViewController

PersonasViewController root = (PersonasViewController ) [_navControllertopViewController]

rootcontext = [self managedObjectContext][selfwindow addSubview_navControllerview]

Ahora compilamos y ejecutamos nuestro proyecto y si todo ha ido bien deberiacutea de salirla tabla con los datos de las personas

Persistencia

72Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Listado de personas

736 Migraciones de bases de datos con Core Data

Una vez que tenemos disentildeada nuestra base de datos inicial iquestqueacute pasariacutea si queremosmodificarla para antildeadirle un campo maacutes a una tabla iquestQueacute haraacute Core Data para realizar lamigracioacuten de la versioacuten anterior a la actual

Siguiendo con el ejemplo anterior si despueacutes de ejecutar la aplicacioacuten en nuestrodispositivo o simulador modificamos la estructura de la base de datos antildeadiendo unatributo nuevo en la entidad DetallePersona por ejemplo codigoPostal con el tipoInteger 16 veremos que al volver a arrancar la aplicacioacuten nos apareceraacute un error entiempo de ejecucioacuten similar al siguiente

Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 Theoperationcouldnrsquot be completed(Cocoa error 134100) UserInfo=0x6b6c680 metadata=ltCFBasicHash 0x6b68380[0x1337b38]gttype = immutable dict count = 7entries =gt

2 ltCFString 0x6b70320 [0x1337b38]gtcontents =NSStoreModelVersionIdentifiers

=ltCFArray 0x6b706e0 [0x1337b38]gttype = immutable count = 1

values = (0 ltCFString 0x1332cd8 [0x1337b38]gtcontents =

)4 ltCFString 0x6b70350 [0x1337b38]gtcontents =

NSPersistenceFrameworkVersion=

Persistencia

73Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltCFNumber 0x6b6f2c0 [0x1337b38]gtvalue = +386 type =kCFNumberSInt64Type

6 ltCFString 0x6b70380 [0x1337b38]gtcontents =NSStoreModelVersionHashes =

ltCFBasicHash 0x6b707e0 [0x1337b38]gttype = immutable dict count =2entries =gt

1 ltCFString 0x6b70700 [0x1337b38]gtcontents = Persona =ltCFData 0x6b70740

[0x1337b38]gtlength = 32 capacity = 32 bytes =0x1c50e5a379ff0ddc3cda7b82a3934c5e 75649ac3c919beea2 ltCFString 0x6b70720 [0x1337b38]gtcontents = DetallePersona

=ltCFData 0x6b70790[0x1337b38]gtlength = 32 capacity = 32 bytes =0xb7ba2eb498f9a1acdd6630d56f6cd12c e8963dd3e488ba95

7 ltCFString 0x10e3aa8 [0x1337b38]gtcontents = NSStoreUUID =ltCFString 0x6b70510[0x1337b38]gtcontents = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E8 ltCFString 0x10e3948 [0x1337b38]gtcontents = NSStoreType =ltCFString 0x10e3958[0x1337b38]gtcontents = SQLite9 ltCFString 0x6b703b0 [0x1337b38]gtcontents =

_NSAutoVacuumLevel = ltCFString0x6b70830 [0x1337b38]gtcontents = 210 ltFString 0x6b703d0 [0x1337b38]gtcontents =

NSStoreModelVersionHashesVersion=ltCFNumber 0x6b61950 [0x1337b38]gtvalue = +3 type =

kCFNumberSInt32Type reason=The model used to open the store is incompatible with the oneused tocreate the store

metadata = NSPersistenceFrameworkVersion = 386NSStoreModelVersionHashes =

DetallePersona = ltb7ba2eb4 98f9a1ac dd6630d5 6f6cd12c f890007746b16f00

e8963dd3 e488ba95gtPersona = lt1c50e5a3 79ff0ddc 3cda7b82 a3934c5e 5cb4ee4b

4ac98838 75649ac3c919beeagt

NSStoreModelVersionHashesVersion = 3NSStoreModelVersionIdentifiers = (

)NSStoreType = SQLiteNSStoreUUID = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E_NSAutoVacuumLevel = 2

reason = The model used to open the store is incompatible with the

one usedto create the store

Este error es muy comuacuten cuando estamos desarrollando aplicaciones con Core DataBaacutesicamente nos dice que el modelo de base de datos que estaacute intentando cargar dememoria es distinto al que se indica en la aplicacioacuten esto es porque no hemos indicadoninguacuten meacutetodo de migracioacuten Realmente existen dos formas de solucionar este error

Persistencia

74Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Por un lado y soacutelo cuando estemos desarrollando y no tengamos ninguna versioacutende nuestra aplicacioacuten lanzada en la App Store podremos eliminar la base de datosdel dispositivo simulador simplemente borrando la aplicacioacuten de el Tambieacutenpodemos ejecutar el siguiente coacutedigo una soacutela vez [[NSFileManagerdefaultManager] removeItemAtURLstoreURL errornil]

bull En el caso de que estemos realizando una migracioacuten sobre una versioacuten ya publicadaen la App Store el meacutetodo anterior no nos serviriacutea ya que obligariacuteamos al usuario aborrar su aplicacioacuten del dispositivo con lo que todos los datos que tuviera seperderiacutean Esto no es viable por lo que necesitaremos usar otro meacutetodo para realizarla migracioacuten y solucionar el error de una forma maacutes limpia este meacutetodo es el quevamos a comentar a continuacioacuten

XCode soporta Versioning para Core Data esto es que podemos crear versiones de labase de datos de una forma sencilla si seguimos una serie de pasos

1) Antildeadimos las siguientes opciones en forma de NSDictionary a nuestro coacutedigo delpersistentStoreCoordinator

NSDictionary options = [NSDictionary dictionaryWithObjectsAndKeys[NSNumber numberWithBoolYES]NSMigratePersistentStoresAutomaticallyOption[NSNumber numberWithBoolYES]NSInferMappingModelAutomaticallyOptionnil]

Y modificamos el meacutetodo addPersistentStoreWithType pasaacutendole como paraacutemetro eldiccionario options

if ([__persistentStoreCoordinatoraddPersistentStoreWithTypeNSSQLiteStoreTypeconfigurationnil URLstoreURL optionsoptions erroramperror])

Con el coacutedigo anterior indicamos al persistentStoreCoordinator que la migracioacuten enel caso de que se deba de realizar sea de forma automaacutetica

AtencioacutenDeberemos de tener en cuenta que este tipo de migracioacuten que estamos estudiando en este puntosoacutelo es vaacutelida para cambios simples en Core Data lo que Apple llama como lightweightmigration Este tipo de migracioacuten admite una serie de cambios que se indican dentro de ladocumentacioacuten de Apple para cambios maacutes complejos deberemos realizar migracionespersonalizadas y mucho maacutes complejas El documento en donde Apple explica el tipo demigraciones que existen para Core Data y su forma de implementarlas lo podemos consultardesde esta web

2) Ahora deberemos de crear la versioacuten del esquema de Core Data dentro de XCode para

Persistencia

75Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ello seleccionamos el fichero del esquema sesion04_ejemplo2xcdatamodeld yseleccionamos en el menuacute principal Editor gt Add Model Version A la nueva versioacuten lallamaremos sesion04_ejemplo_2 (versioacuten 2) Una vez que hayamos pulsado sobreFinish se nos habraacute creado la nueva versioacuten del esquema dentro de la principal y estaraacutemarcada con un tick la versioacuten anterior

3) Una vez que tenemos creada la nueva versioacuten deberemos modificarla antildeadiendole elnuevo atributo Seleccionamos la versioacuten recieacuten creadasesion04_ejemplo_2xcdatamodel y antildeadimos a la entidad DetallePersona elatributo codigoPostal Atencioacuten Si este atributo lo hemos antildeadido anteriormentedeberemos de borrarlo de la versioacuten anterior y antildeadirlo en la segunda versioacuten

4) Ahora nos queda indicar a Core Data que queremos usar la versioacuten 2 de la base dedatos para nuestra aplicacioacuten Para ello seleccionamos el esquema principal (el raiz)sesion04_ejemplo2xcdatamodeld y en la ventana de la derecha en la pestantildea de FileInspector seleccionamos como Versioacuten actual la versioacuten 2 esto lo hacemos dentro delapartado de Versioned Core Data Model Automaacuteticamente veremos que el siacutembolo detick cambia a la versioacuten 2

5) Ahora ya podemos volver a compilar y veremos que no nos aparece el error y funcionatodo seguacuten lo esperado

Magical RecordExisten varias librerias de coacutedigo libre que facilitan enormemente el uso de Core Data ennuestras aplicaciones iOS Una de ellas es Magical Record Mediante esta libreriacutea podremos usarCore Data sin tener que preocuparnos de todo el proceso de configuracioacuten en las controladorasde la configuracioacuten de migraciones etc Su instalacioacuten en un proyecto de XCode es muy faacutecil yaltamente recomendable ya que proporciona una API muy clara y sencilla de usar

Persistencia

76Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios

81 Usando User Defaults (1 punto)

En este ejercicio vamos a usar las variables de usuario para almacenar y mostrar los datosdel dispositivo que estamos usando y las veces que hemos arrancado la aplicacioacuten

Crear un proyecto nuevo usando la plantilla Single View Application con el nombreejercicio-userdefaults-ios Se debe de ejecutar en iPhone e iPad (Universal) Comoidentificador de empresa escribiremos esuajtech y como prefijo de clase UA Debe detener el ARC activado (desmarcar el resto de opciones)

Ayuda meacutetodos de UIDevicePara obtener los datos del dispositivo usaremos el singleton UIDevice estos son algunos de losmeacutetodos que podemos usar [[UIDevice currentDevice] name] [[UIDevicecurrentDevice] systemName] [[UIDevice currentDevice]systemVersion] [[UIDevice currentDevice] model] [[UIDevicecurrentDevice] localizedModel]

82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)

En este ejercicio disentildearemos un modelo de datos de dos tablas relacionadas entre ellasantildeadiremos datos por coacutedigo y los mostraremos en una vista de tabla La base de datosque vamos a implementar constaraacute de dos tablas por un lado una que contendraacute los tiacutetulosde series y otra que tendraacute los detalles A la primera la llamaremos Serie y la segundaDetalleSerie Para realizar correctamente el ejercicio deberemos seguir los siguientespasos

1) Crear un proyecto nuevo usando la plantilla Master-Detail Application con elnombre ejercicio-coredata-ios Se debe de ejecutar en iPhone como identificador deempresa escribiremos esuajtech y como prefijo de clase UA Debe de tener el ARCactivado y la opcioacuten de Use Core Data (desmarcar el resto de opciones)

2) Disentildear el siguiente modelo de datos

Persistencia

77Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esquema modelo de datos

3) Revisar el fichero UAAppDelegatem y comprobar que tenemos todos los meacutetodosnecesarios para que funcione Core Data en nuestra aplicacioacuten

4) Modificar el fichero UAMasterViewControllerm para que se muestren los tiacutetulos delas series en la tabla

5) Probar la aplicacioacuten antildeadiendo unos tiacutetulos de prueba

83 Migrando datos con Core Data (05 puntos)

Una vez que hemos disentildeado la Base de Datos con Core Data vamos a modificarlaantildeadiendo un atributo maacutes a la tabla de DetalleSerie valoracion

a) iquestQueacute pasa si cambiamos el modelo de datos y arrancamos la aplicacioacuten iquestQueacute errornos da y por queacute

b) iquestCoacutemo podemos evitar este tipo de errores cuando estemos desarrollando iquestY siestamos en produccioacuten con la aplicacioacuten ya a la venta

c) Crea una nueva versioacuten del modelo

d) Modifica el coacutedigo necesario dentro de la clase UAAppDelegate para evitar este tipo deerrores cuando estemos en un entorno de produccioacuten

e) Vuelve a arrancar la aplicacioacuten y comprueba que funciona bien

Persistencia

78Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Persistencia

79Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

  • 1 Persistencia en Android ficheros y SQLite
    • 11 Introduccioacuten
    • 12 Manejo de ficheros tradicionales en Android
      • 121 Apertura de ficheros
      • 122 Ficheros como recursos
      • 123 Operar con ficheros
      • 124 Almacenar datos en la tarjeta de memoria
        • 13 Base de datos SQLite
          • 131 Content Values
          • 132 Cursores
          • 133 Trabajar con bases de datos SQLite
          • 134 La clase SQLiteOpenHelper
          • 135 Crear una base de datos sin SQLiteHelper
          • 136 Realizar una consulta
          • 137 Extraer resultados de un cursor
          • 138 Antildeadir actualizar y borrar filas
              • 2 Ejercicios - Persistencia en Android ficheros y SQLite
                • 21 Uso de ficheros (05 puntos)
                • 22 Persistencia con ficheros (05 puntos)
                • 23 Base de datos SQLiteOpenHelper (05 puntos)
                • 24 Base de datos insercioacuten y borrado (05 puntos)
                • 25 Base de datos probar nuestro adaptador (05 puntos)
                • 26 Base de datos cambios en la base de datos (05 puntos)
                  • 3 Persistencia en Android proveedores de contenidos y SharedPreferences
                    • 31 Shared Preferences
                      • 311 Guardar Shared Preferences
                      • 312 Leer Shared Preferences
                      • 313 Interfaces para Shared Preferences
                      • 314 Definiendo una pantalla de preferencias con un layout en XML
                      • 315 Controles nativos para preferencias
                      • 316 Actividades de preferencias
                      • 317 Shared Preference Change Listeners
                        • 32 Proveedores de contenidos
                          • 321 Proveedores nativos
                          • 322 Proveedores propios crear un nuevo proveedor de contenidos
                          • 323 Proveedores propios crear la interfaz de consultas
                          • 324 Proveedores propios tipo MIME
                          • 325 Proveedores propios registrar el proveedor
                          • 326 Content Resolvers
                          • 327 Otras operaciones con Content Resolvers
                              • 4 Ejercicios - Persistencia en Android proveedores de contenidos y SharedPreferences
                                • 41 Compartir datos entre actividades con Shared Preferences (075 puntos)
                                • 42 Actividad de preferencias (075 puntos)
                                • 43 Proveedor de contenidos propio (075 puntos)
                                • 44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)
                                  • 5 Persistencia de datos en iOS Ficheros y SQLite
                                    • 51 Introduccioacuten
                                    • 52 Ficheros plist (Property Lists)
                                      • 521 Leyendo datos desde ficheros plist
                                      • 522 Escribiendo datos en ficheros plist
                                        • 53 SQLite
                                          • 531 Herramientas disponibles
                                          • 532 Lectura de datos
                                          • 533 Mostrando los datos en una tabla
                                              • 6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios
                                                • 61 Creando y leyendo un fichero plist (15 puntos)
                                                • 62 Implementar vista de detalle de la serie (05 puntos)
                                                • 63 Uso baacutesico de una base de datos SQLite (1 punto)
                                                  • 7 Persistencia de datos en iOS User Defaults y Core Data
                                                    • 71 Introduccioacuten
                                                    • 72 User Defaults
                                                    • 73 Core Data
                                                      • 731 Creando el proyecto
                                                      • 732 Definiendo el modelo de datos
                                                      • 733 Probando el modelo
                                                      • 734 Generando los modelos automaacuteticamente
                                                      • 735 Creando la vista de tabla
                                                      • 736 Migraciones de bases de datos con Core Data
                                                          • 8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios
                                                            • 81 Usando User Defaults (1 punto)
                                                            • 82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)
                                                            • 83 Migrando datos con Core Data (05 puntos)
Page 10: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de

int _versionNueva) Mostramos en LogCat la operacioacutenLogw(AdaptadorBD Actualizando desde la

versioacuten +_versionAnterior + a la versioacuten +_versionNueva + lo cual borraraacute los

datospreviamente almacenados)

Actualizamos la base de datos para que seadapte a la nueva versioacuten

La forma maacutes sencilla de llevar a cabo dichaactualizacioacuten es

eliminar la tabla antigua y crear una nueva_dbexecSQL(DROP TABLE IF EXISTS +

TABLA_BASE_DATOS)onCreate(_db)

134 La clase SQLiteOpenHelper

La clase SQLiteOpenHelper es una clase abstracta utilizada como medio paraimplementar un patroacuten de creacioacuten apertura y actualizacioacuten de una determinada base dedatos Al implementar una subclase de SQLiteOpenHelper podemos abstraernos de laloacutegica subyacente a la decisioacuten de crear o actualizar una base de datos de manera previa aque eacutesta deba ser abierta

En el ejemplo de coacutedigo anterior se vio coacutemo crear una subclase de SQLiteOpenHelper

por medio de la sobrecarga de sus meacutetodos onCreate y onUpgrade los cuales permitencrear una nueva base de datos o actualizar a una nueva versioacuten respectivamente

NotaEn el ejemplo anterior el meacutetodo onUpgrade simplemente eliminaba la tabla existente y lareemplazaba con la nueva definicioacuten de la misma En la praacutectica la mejor solucioacuten seriacutea migrarlos datos ya existentes a la nueva tabla

Mediante los meacutetodos getReadableDatabase y getWritableDatabase podemos abrir yobtener una instancia de la base de datos de soacutelo lectura o de lectura y escriturarespectivamente La llamada a getWritableDatabase podriacutea fallar debido a falta deespacio en disco o cuestiones relativas a permisos por lo que es una buena praacutectica teneren cuenta esta posibilidad por medio del mecanismo de manejo de excepciones de JavaEn caso de fallo la solucioacuten podriacutea pasar por al menos proporcionar una copia de soacutelolectura tal como se muestra en el siguiente ejemplo

dbHelper = new miHelperBD(contexto NOMBRE_BASE_DATOS nullVERSION_BASE_DATOS)

SQLiteDatabase dbtry

db = dbHelpergetWritableDatabase() catch (SQLiteException ex)

db = dbHelpergetReadableDatabase()

Persistencia

10Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Al hacer una llamada a cualquiera de los dos meacutetodos anteriores se invocaraacuten losmanejadores de evento adecuados Asiacute pues si por ejemplo la base de datos no existieraen disco se ejecutariacutea el coacutedigo de onCreate Si por otra parte la versioacuten de la base dedatos se hubiera modificado se disparariacutea el manejador onUpgrade Lo que sucedeentonces es que al hacer una llamada a getWritableDatabase o getReadableDatabase

se devolveraacute una base de datos nueva actualizada o ya existente seguacuten el caso

135 Crear una base de datos sin SQLiteHelper

Tambieacuten es posible crear y abrir bases de datos sin necesidad de utilizar una subclase deSQLiteHelper Para ello haremos uso del meacutetodo openOrCreateDatabase En este casoseraacute necesario tambieacuten hacer uso del meacutetodo execSQL sobre la instancia de la base dedatos devuelta por el meacutetodo anterior para ejecutar los comandos SQL que permitiraacutencrear las tablas de la base de datos y establecer las relaciones entre ellas El siguientecoacutedigo muestra un ejemplo

private static final String NOMBRE_BASE_DATOS = mibasededatosdbprivate static final String TABLA_BASE_DATOS = tablaPrincipal

private static final String CREAR_BASE_DATOS =create table + TABLA_BASE_DATOS + _id integer primare key

autoincrement +columna_uno text not null)

SQLiteDatabase db

private void crearBaseDatos() db = openOrCreateDatabase(NOMBRE_BASE_DATOS ContextMODE_PRIVATE

null)dbexecSQL(CREAR_BASE_DATOS)

NotaAunque no es necesariamente obligatorio es recomendable que cada tabla incluya un campo detipo clave primaria con autoincremento a modo de iacutendice para cada fila En el caso en el que sedesee compartir la base de datos mediante un proveedor de contenidos un campo uacutenico de estetipo es obligatorio

136 Realizar una consulta

El resultado de cualquier consulta a una base de datos seraacute un objeto de la clase CursorEsto permite a Android manejar los recursos de manera maacutes eficiente de tal forma que enlugar de devolver todos los resultados eacutestos se van proporcionando conforme senecesitan

Para realizar una consulta a una base de datos haremos uso del meacutetodo query de lainstancia correspondiente de la clase SQLiteDatabase Los paraacutemetros que requiere este

Persistencia

11Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

meacutetodo son los siguientes

bull Un booleano opcional que permite indicar si el resultado deberiacutea contener tan soacutelovalores uacutenicos

bull El nombre de la tabla sobre la que realizar la consultabull Un array de cadenas que contenta un listado de las columnas que deben incluirse en el

resultadobull Una claacuteusula where que defina las filas que se deben obtener de la tabla Se puede

utilizar el comodiacuten el cual seraacute reemplazado por los valores que se pasen a traveacutesdel siguiente paraacutemetro

bull Un array de cadenas correspondientes a argumentos de seleccioacuten que reemplazaraacutenlos siacutembolos en la claacuteusula where

bull Una claacuteusula group by que define coacutemo se agruparaacuten las filas obtenidas comoresultado

bull Un filtro having que indique que grupos incluir en el resultado en el caso en el que sehaya hecho uso del paraacutemetro anterior

bull Una cadena que describa la ordenacioacuten que se llevaraacute a cabo sobre las filas obtenidascomo resultado

bull Una cadena opcional para definir un liacutemite en el nuacutemero de resultados a devolver

NotaComo se puede observar la mayoriacutea de estos paraacutemetros hacen referencia al lenguaje SQLTratar este tema queda fuera de los objetivos de este curso por lo que en nuestros ejemplos ledaremos en la mayoriacutea de las ocasiones un valor null a estos paraacutemetros

El siguiente coacutedigo muestra un ejemplo de consultas utilizando diversos paraacutemetros

Devolver los valores de las columnas uno y tres para todas las filas sin duplicadosString[] columnas = new String[] CLAVE_ID CLAVE_COL1 CLAVE_COL3

Cursor todasFilas = dbquery(true TABLA_BASE_DATOS columnas null nullnull null

null null)

Devolvemos los valores de todas las columnas para aquellas filas cuyovalor de la columna 3 sea igual al de una determinada variable Ordenamos las filasseguacuten el valor de la columna 5 Como queremos los valores de todas las columnas le damos al paraacutemetro correspondiente a las columnas a devolver el valor null En este caso no se ha hecho uso del primer paraacutemetro booleano que esoptativoString where = CLAVE_COL3 + = + valorString orden = CLAVE_COL5Cursor miResultado = dbquery(TABLA_BASE_DATOS null where null nullnull orden)

137 Extraer resultados de un cursor

Persistencia

12Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El primer paso para obtener resultados de un cursor seraacute desplazarnos a la posicioacuten apartir de la cual queremos obtener los datos usando cualquiera de los meacutetodosespecificados anteriormente (como por ejemplo moveToFirst o moveToPosition) Unavez hecho esto hacemos uso de meacutetodos get[TIPO] pasando como paraacutemetro el iacutendice dela columna Esto tendraacute como resultado la devolucioacuten del valor para dicha columna de lafila actualmente apuntada por el cursor

String valor = miResultadogetString(indiceColumna)

A continuacioacuten podemos ver un ejemplo maacutes completo en el que se va iterando a lo largode los resultados apuntados por un cursor extrayendo y sumando los valores de unacolumna de flotantes

int COLUMNA_VALORES_FLOTANTES = 2Cursor miResultado = dbquery(Tabla null null null null nullnull)float total = 0

Nos aseguramos de que se haya devuelto al menos una filaif (miResultadomoveToFirst())

Iteramos sobre el cursordo

float valor =miResultadogetFloat(COLUMNA_VALORES_FLOTANTES)

total += valor while (miResultadomoveToNext())

float media = total miResultadogetCount()

NotaDebido a que las columnas de las bases de datos en SQLite son debilmente tipadas podemosrealizar castings cuando sea necesario Por ejemplo los valores guardados como flotantes en labase de datos podriacutean leerse maacutes adelante como cadenas

138 Antildeadir actualizar y borrar filas

La clase SQLiteDatabase proporciona los meacutetodos insert delete y update queencapsulan las instrucciones SQL requeridas para llevar a cabo estas acciones Ademaacutesdisponemos de la posibilidad de utilizar el meacutetodo execSQL el cual nos permite ejecutarcualquier sentencia SQL vaacutelida en nuestras tablas de la base de datos en el caso en el quequeramos llevar a cabo estas (u otras) operaciones manualmente

NotaCada vez que modifiques los valores de la base de datos puedes actualizar un cursor que se puedaver afectado por medio del meacutetodo refreshQuery de la clase Cursor

Para insertar una nueva fila es necesario construir un objeto de la clase ContentValues

y usar su meacutetodo put para proporcionar valor a cada una de sus columnas Para insertar la

Persistencia

13Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

nueva fila pasaremos como paraacutemetro este objeto del tipo ContentValues al meacutetodoinsert de la instancia de la base de datos junto por supuesto con el nombre de la tablaA continuacioacuten se muestra un ejemplo

Crear la nueva filaContentValues nuevaFila = new ContentValues()

Asignamos valores a cada columnanuevaFilaput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas ]

Insertar la nueva fila en la tabladbinsert(TABLA_BASE_DATOS null nuevaFila)

NotaLos ficheros como imaacutegenes o ficheros de audio no se suelen almacenar dentro de tablas en unabase de datos En estos casos se suele optar por almacenar en la base de datos una cadena con laruta al fichero o una URI

La operacioacuten de actualizar una fila tambieacuten puede ser llevada a cabo por medio del usode la clase ContentValues En este caso deberemos llamar al meacutetodo update de lainstancia de la base de datos pasando como paraacutemetro el nombre de la tabla el objetoContentValues y una claacuteusula where que especifique la fila o filas a actualizar tal comose puede ver en el siguiente ejemplo

Creamos el objeto utilizado para definir el contenido a actualizarContentValues valoresActualizar = new ContentValues()

Asignamos valores a las columnas correspondientesvaloresActualizarput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas a actualizar ]

String where = CLAVE_ID + = + idFila

Actualizamos la fila con el iacutendice especificado en la instruccioacutenanteriordbupdate(TABLA_BASE_DATOS valoresActualizar where null)

Por uacuteltimo para borrar una fila simplemente llamaremos al meacutetodo delete de lainstancia de la base de datos especificando en este caso el nombre de la tabla que se veraacuteafectada por la operacioacuten y una claacuteusula where que utilizaremos para especificar las filasque queremos borrar Un ejemplo podriacutea ser el siguiente

dbdelete(TABLA_BASE_DATOS CLAVE_ID + = + idFila null)

Persistencia

14Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

2 Ejercicios - Persistencia en Android ficheros y SQLite

21 Uso de ficheros (05 puntos)

En este ejercicio vamos a crear una aplicacioacuten que muestre un listado de cadenas porpantalla Estas cadenas se almacenaraacuten en un fichero de texto privado para la aplicacioacutenPodremos antildeadir nuevas cadenas a partir de un cuadro de edicioacuten presente en la interfazPartiremos del proyecto Ficheros proporcionado en las plantillas de la aplicacioacuten Debesseguir los siguientes pasos

bull Antildeadir un manejador al botoacuten de la actividad principal para que cada vez que seapulsado se guarde en un fichero de texto El fichero se llamaraacute ficherotxt Al abrirel fichero para escritura se utilizaraacute el paraacutemetro ContextMODE_APPEND con lo quecada nueva cadena se antildeadiraacute al final del fichero en el caso en el que eacuteste ya existieraPara escribir en el fichero utilizaremos el meacutetodo writeBytes del objetoDataOutputStream correspondiente

bull Al pulsar el botoacuten tambieacuten se deberaacute borrar el contenido del elemento EditText (leasignamos a la vista una cadena vaciacutea con el meacutetodo setText)

bull Ejecutamos la aplicacioacuten e introducimos algunas liacuteneas en el fichero Vamos acomprobar ahora que todo ha funcionado correctamente Para ello accedemos alsistema de ficheros de nuestro dispositivo ejecutando el comando adb shell

dentro de la carpeta platform-tools de nuestra carpeta de instalacioacuten del SDK deAndroid

bull Comprueba que se ha creado el fichero ficherotxt en la carpetadatadataesuajtechandroidficherosfiles Examina su contenido ejecutando cat

ficherotxt Si algo no ha salido bien siempre podraacutes eliminar el fichero con rm

ficherotxtbull En la interfaz de la actividad se ha incluido una vista de tipo TextView debajo del

botoacuten Antildeade un manejador para dicha vista de tal forma que cada vez que se hagaclick sobre ella se muestre el contenido del fichero ficherotxt Para ello leeremos elfichero liacutenea a liacutenea antildeadiendo cada una al TextView por medio del meacutetodo append

Persistencia

15Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Interfaz de la aplicacioacuten Ficheros

22 Persistencia con ficheros (05 puntos)

Seguramente habraacutes observado que si abandonas la aplicacioacuten anterior (pulsando el botoacutenBACK del dispositivo) al volver a ejecutarla el contenido del TextView ha desaparecidoDebes volver a pulsar sobre la vista para que se vuelva a mostrar el contenido del ficheroHaz las modificaciones pertinentes para que esto no sea asiacute es decir para que cuandovuelva a mostrarse la actividad tras haber sido eliminada de la memoria se muestre elTextView tal cual se veiacutea antes de abandonar la aplicacioacuten

AvisoEn este ejercicio no se estaacute pidiendo que cargues el contenido del fichero en el TextView nadamaacutes arrancar la aplicacioacuten Puede darse el caso de que lo que esteacute mostrando el TextViewcuando la actividad sea eliminada de memoria no se corresponda con el contenido actualizado delfichero

NotaPara poder completar el ejercicio deberaacutes repasar los manejadores de evento de la primera sesioacutendel moacutedulo de Android relacionados con el ciclo de vida de ejecucioacuten de actividades

23 Base de datos SQLiteOpenHelper (05 puntos)

En las plantillas de la aplicacioacuten se incluye la aplicacioacuten BaseDatos En el proyecto de laaplicacioacuten se incluye el esqueleto de una clase MiAdaptadorBD que tendremos quecompletar Se trata de un patroacuten que nos va a permitir acceder a una base de datos de

Persistencia

16Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

usuarios dentro de la aplicacioacuten sin necesidad de hacer uso de coacutedigo SQL

La clase MiAdaptadorBD incluye a su vez otro patroacuten en este caso implementado comouna subclase de SQLiteOpenHelper Eacuteste nos obliga a definir queacute ocurre cuando la basede datos todaviacutea no existe y debe ser creada y queacute ocurre si ya existe pero debe seractualizada porque ha cambiado de versioacuten Asiacute el SQLiteOpenHelper queimplementemos en este caso MiOpenHelper nos devolveraacute siempre una base de datosseparaacutendonos de la loacutegica encargada de comprobar si la base de datos existe o no

En este primer ejercicio se pide hacer lo siguiente

bull Ejecutar la sentencia de creacioacuten de bases de datos (la tenemos declarada comoconstante de la clase) en el meacutetodo MiOpenHelperonCreate

bull Implementar tambieacuten el meacutetodo onUpgrade Idealmente eacuteste deberiacutea portar las tablasde la versioacuten antigua a la versioacuten nueva copiando todos los datos Nosotros vamos aeliminar directamente la tabla que tenemos con la sentencia SQL DROP TABLE IF

EXISTS + NOMBRE_TABLA y volveremos a crearlabull En el constructor de MiAdaptadorBD debemos obtener en el campo db la base de

datos utilizando MiOpenHelper

24 Base de datos insercioacuten y borrado (05 puntos)

Continuamos trabajando con el proyecto anterior En este ejercicio completaremos elcoacutedigo relacionado con las sentencias de insercioacuten y borrado Para realizar la insercioacutenvamos a hacer uso de un mecanismo que no hemos visto en la sesioacuten de teoriacutea y queconsiste en utilizar una sentencia de insercioacuten SQL precompilada que se completaraacute conlos valores concretos a insertar en la base de datos justo antes de realizar dicha insercioacutenLa ventaja de las sentencias compiladas es que evitan que se produzcan ataques deinyeccioacuten de coacutedigo

Como se puede observar se ha incluido un atributo a la clase que representa la insercioacutenSQL

private static final String INSERT = insert into + NOMBRE_TABLA +(+COLUMNAS[1]+) values ()

En esta sentencia no se indica ninguacuten valor concreto para la columna nombre En el lugaren el que deberiacutean aparecer los valores de dicho campo se ha escrito simplemente unsiacutembolo de interrogacioacuten Tambieacuten se ha antildeadido una instancia de la claseSQLiteStatement como parte de los atributos de la clase

private SQLiteStatement insertStatement

Realizamos los siguientes pasos

bull En el constructor de la clase MiAdaptadorBD llevamos a cabo la compilacioacuten de lasentencia

thisinsertStatement = thisdbcompileStatement(INSERT)

Persistencia

17Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Una vez hecho esto cada vez que deseemos insertar un nuevo usuario en la base dedatos deberemos dar valores concretos a la columna nombre y ejecutar la sentenciaPara ello debemos antildeadir el siguiente coacutedigo dentro del meacutetodo insert de la claseMiAdaptadorBD

thisinsertStatementbindString(1 nombreUsuario)return thisinsertStatementexecuteInsert()

bull Por uacuteltimo completamos el coacutedigo del meacutetodo deleteAll cuyo contenido es eliminara todos los usuarios de la base de datos Para que ademaacutes se devuelva el nuacutemero defilas afectadas podemos insertar la siguiente liacutenea en dicho meacutetodo

return dbdelete(NOMBRE_TABLA null null)

Una vez hechos todos estos cambios podremos utilizar la clase MiAdaptadorBD parahacer operaciones con la base de datos de usuarios de manera transparente

25 Base de datos probar nuestro adaptador (05 puntos)

Antildeade coacutedigo en la actividad Main para eliminar todos los usuarios de la base de datosantildeadir dos cualesquiera y listarlos por medio de la vista de tipo TextView que seencuentra en el layout de dicha actividad

Podemos comprobar mediante la liacutenea de comandos (comando adb shell) que la basede datos ha sido creada Para ello puede ser uacutetil hacer uso de los siguientes comandos

cd datadataesuajtechandroidbasedatosdatabasessqlite3 misusuariosdbsqlitegt schemasqlitegt tablessqlitegt select from usuarios

26 Base de datos cambios en la base de datos (05 puntos)

Ahora vamos a cambiar en la clase MiAdaptadorBD el nombre de la segunda columnaque en lugar de nombre se va a llamar nombres Ejecutamos la aplicacioacuten y comprobamosque sale con una excepcioacuten Comprueba por medio de LogCat cuaacutel ha sido el erroriquestCoacutemo lo podemos solucionar

NotaPista conforme hemos programado la clase MiAdaptadorBD y siguiendo el patroacuten de disentildeode SQLiteOpenHelper es posible arreglar el problema simplemente modificando el valor deun campo

Persistencia

18Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

3 Persistencia en Android proveedores de contenidos ySharedPreferences

31 Shared Preferences

Comenzamos esta sesioacuten hablando de una de las teacutecnicas maacutes simples de persistencia enAndroid junto al uso de ficheros Shared Preferences Se trata de un mecanismo ligeropara almacenar datos basado en pares clave-valor utilizado normalmente para guardarpreferencias u opciones de la aplicacioacuten Tambieacuten puede ser utilizado para almacenar elestado de la interfaz graacutefica para contemplar el caso de que nuestra actividad debafinalizar abruptamente su ejecucioacuten Otro posible uso es el de compartir informacioacutenentre componentes de una misma aplicacioacuten

NotaLos Shared Preferences nunca son compartidos entre diferentes aplicaciones

Este meacutetodo se basa en el uso de la clase SharedPreferences Es posible utilizarla paraguardar datos en muy diversos formatos booleanos cadenas flotantes y enteros

311 Guardar Shared Preferences

Para crear o modificar un Shared Preference llamamos al meacutetodogetSharedPreferences del contexto de la aplicacioacuten pasando como paraacutemetro elidentificador del conjunto de valores de preferencias con el que queremos trabajar Pararealizar la modificacioacuten del valor de un Shared Preference hacemos uso de la claseSharedPreferencesEditor Para obtener el objeto editor invocamos el meacutetodo edit

del objeto Shared Preferences que queremos modificar Los cambios se guardan medianteel meacutetodo commit Veamos un ejemplo

int modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences = getSharedPreferences(Mispreferencias modo)

Obtenemos un editor para modificar las preferenciasSharedPreferencesEditor editor = misSharedPreferencesedit()

Guardamos nuevos valores en el objeto SharedPreferenceseditorputBoolean(isTruetrue)editorputFloat(unFloat10)editorputInt(numeroEntero12)editorputLong(otroNumero2)editorputString(unaCadenavalor)

Guardamos los cambioseditorcommit()

Persistencia

19Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

312 Leer Shared Preferences

Para leer Shared Preferences utilizamos el meacutetodo getSharedPreferences tal como seha comentado anteriormente Pasamos como paraacutemetro el identificador del conjunto deShared Preferences al que deseamos acceder Una vez hecho esto ya podemos hacer usode meacutetodos de tipo get para acceder a preferencias individuales Hay diferentes meacutetodosget para diferentes tipos de datos Cada uno de ellos requiere como paraacutemetro la claveque identifica la preferencia cuyo valor deseamos obtener y un valor por defecto el cualse usaraacute en el caso concreto en el que no existiera la preferencia con la clave pasada comoprimer paraacutemetro Podemos ver un ejemplo a continuacioacuten

public static String MIS_PREFS = MIS_PREFS

public void cargarPreferences() Obtener las preferencias almacenadasint modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences =

getSharedPreferences(MIS_PREFS modo)

boolean isTrue = misSharedPreferencesgetBoolean(isTruefalse)float unFloat = misSharedPreferencesgetFloat(unFloat0)int numeroEntero = misSharedPreferencesgetInt(numeroEntero0)long otroNumero = misSharedPreferencesgetLong(otroNumero0)String unaCadena = misSharedPreferencesgetString(unaCadena)

313 Interfaces para Shared Preferences

Android proporciona una plataforma basada en XML para la creacioacuten de interfacesgraacuteficas para el manejo de preferencias Estas interfaces tendraacuten un aspecto similar al delas del resto de aplicaciones del sistema Al utilizarla nos estaremos asegurando de quenuestras actividades para el manejo de preferencias seraacuten consistentes con las del resto deaplicaciones del sistema De esta forma los usuarios estaraacuten familiarizados con el manejode esta parte de nuestra aplicacioacuten

La plataforma consiste en tres elementos

bull Layout de la actividad de preferencias se trata de un archivo XML que definiraacute lainterfaz graacutefica de la actividad que muestre los controles para modificar los valores delas preferencias Permite especificar las vistas a mostrar los posibles valores que sepueden introducir y a queacute clave de Shared Preferences se corresponde cada vista

bull Actividad de preferencias una subclase de PreferenceActivity que secorresponderaacute con la pantalla de preferencias de nuestra aplicacioacuten

bull Shared Preference Change Listener una implementacioacuten de la claseonSharedPreferenceChangeListener que actuaraacute en el caso en el que cambie elvalor de alguna Shared Preference

314 Definiendo una pantalla de preferencias con un layout en XML

Persistencia

20Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esta es sin duda la parte maacutes importante de una actividad de preferencias Se trata de unarchivo XML utilizado para definir varios aspectos de dicha actividad Al contrario queen el caso de los archivos de layout para interfaces graacuteficas que vimos en la sesioacutencorrespondiente del moacutedulo de Android estos archivos de layout se guardan en la carpetaresxml de los recursos de la aplicacioacuten

Aunque conceptualmente estos layouts son similares a los utilizados para definirinterfaces graacuteficas los layouts de actividades de preferencias utilizan un conjuntoespecializado de componentes especiacuteficos para este tipo de actividades Estaacuten pensadospara proporcionar interfaces con un aspecto similar al del resto de actividades depreferencias del sistema Estos controles se describen en maacutes detalle en la siguienteseccioacuten

Los layout de preferencias contendraacuten un elemento PreferenceScreen

ltxml version=10 encoding=utf-8gtltPreferenceScreen

xmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceScreengt

Se pueden antildeadir maacutes elementos de este tipo si se desea Al hacerlo eacutestos serepresentaraacuten como un elemento seleccionable en un listado que mostraraacute una nuevaventana de preferencias en caso de que sea seleccionado

Dentro de cada elemento PreferenceScreen podemos incluir tantos elementos de tipoPreferenceCategory y elementos especiacuteficos para antildeadir controles como se desee Loselementos PreferenceCategory son utilizados para dividir cada pantalla de preferenciasen subcategoriacuteas mediante una barra de tiacutetulo Su sintaxis es la siguiente

ltPreferenceCategoryandroidtitle=My Preference Categorygt

ltPreferenceCategorygt

Una vez dividida la pantalla en subcategoriacuteas tan soacutelo quedariacutea antildeadir los elementoscorrespondientes a los controles especiacuteficos que veremos en la siguiente seccioacuten Losatributos de los elementos XML para estos controles pueden variar aunque existe unconjunto de atributos comunes

bull androidkey la clave de Shared Preference que se usaraacute para guardar el valor delcontrol

bull androidtitle texto a mostrar para describir la preferenciabull androidsummary una descripcioacuten maacutes larga a mostrar debajo del texto definido con

el campo anteriorbull androiddefaultValue el valor por defecto que se mostraraacute inicialmente y tambieacuten

el que finalmente se guardaraacute si ninguacuten otro valor fue asignado a este campo

El siguiente listado muestra una pantalla de preferencias muy simple con una uacutenicacategoriacutea y un uacutenico control (un check box)

ltxml version=10 encoding=utf-8gt

Persistencia

21Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltPreferenceScreenxmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceCategory

androidtitle=Mi categoriacuteagtltCheckBoxPreference

androidkey=MI_CHECK_BOXandroidtitle=Preferencia Check Boxandroidsummary=Descripcioacuten de la preferencia

Check BoxandroiddefaultValue=true

gtltPreferenceCategorygt

ltPreferenceScreengt

Esta pantalla de preferencias se mostrariacutea de la siguiente forma

Ejemplo sencillo de ventana de preferencias

315 Controles nativos para preferencias

Android incluye diferentes controles que podemos antildeadir a nuestras ventanas depreferencias por medio de los elementos XML que se indican a continuacioacuten

bull CheckBoxPreference un check box estaacutendar que puede ser utilizado parapreferencias de tipo booleano

bull EditTextPreference permite al usuario introducir una cadena como valor para unapreferencia Al seleccionar el texto se mostraraacute un diaacutelogo con el que introducir elnuevo valor

bull ListPreference el equivalente a un Spinner Seleccionar este elemento de lainterfaz mostraraacute un diaacutelogo con las diferentes opciones que es posible seleccionar Seutilizan arrays para asociar valores y textos a las opciones de la lista

bull RingtonePreference un tipo especiacutefico de preferencia de tipo listado que permiteseleccionar entre diferentes tonos de teleacutefono Esta opcioacuten es particularmente uacutetil en

Persistencia

22Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

el caso en el que se esteacute creando una pantalla de preferencias para configurar lasopciones de notificacioacuten de una aplicacioacuten o componente de la misma

Tambieacuten es posible crear nuestros propios controles personalizados definiendo unasubclase de Preference (o cualquiera de sus subclases) Se puede encontrar maacutesinformacioacuten en la documentacioacuten de AndroidhttpdeveloperandroidcomreferenceandroidpreferencePreferencehtml

316 Actividades de preferencias

Para mostrar una pantalla de preferencias debemos definir una subclase dePreferenceActivity

public class MisPreferencias extends PreferenceActivity

La interfaz graacutefica de la interfaz se puede crear a partir del layout en el meacutetodo onCreatemediante una llamada a addPreferencesFromResource

Overridepublic void onCreate(Bundle savedInstaceState)

superonCreate(savedInstanceState)addPreferencesFromResource(Rxmlmilayout)

Como en el caso de cualquier otra actividad la actividad de preferencias que hayamosdefinido debe ser incluida en el Manifest de la aplicacioacuten

ltactivity androidname=MisPreferenciasandroidlabel=Mis preferenciasgt

ltactivitygt

Eso es todo lo que necesitamos para crear este tipo de actividades y mostrar una ventanacon las preferencias definidas en el layout Esta actividad puede ser iniciada comocualquier otra mediante un Intent ya sea a traveacutes de una llamada a startActivity obien a startActivityForResult

Intent i = new Intent(this MisPreferenciasclass)startActivityForResult(i MOSTRAR_PREFERENCIAS)

Los valores para las diferentes preferencias de la actividad son almacenadas en elcontexto de la aplicacioacuten Esto permite a cualquier componente de dicha aplicacioacutenincluyendo a cualquier actividad y servicio de la misma acceder a los valores tal como semuestra en el siguiente coacutedigo de ejemplo

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto) PENDIENTE hacer uso de meacutetodos get para obtener los valores

317 Shared Preference Change Listeners

Persistencia

23Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El uacuteltimo elemento que nos queda por examinar en esta plataforma de Shared Preferenceses la interfaz onSharedPreferenceChangeListener que es utilizada para invocar unevento cada vez que una preferencia es antildeadida eliminada o modificada Para registrarListeners usamos un coacutedigo como el que se muestra a continuacioacuten en el que la parte maacutesimportante es sin duda el meacutetodo onSharedPreferenceChanged dentro del cualdeberemos determinar queacute preferencia ha cambiado de estado a partir de sus paraacutemetrospara llevar a cabo las acciones oportunas

public class MiActividad extends Activity implementsOnSharedPreferenceChangeListener

Overridepublic void onCreate(Bundle SavedInstanceState)

Registramos este objetoOnSharedPreferenceChangeListener

Context contexto = getApplicationContext()SharedPreferences prefs =

PreferenceManagergetDefaultSharedPreferences(contexto)prefsregisterOnSharedPreferenceChangeListener(this)

public void onSharedPreferenceChanged(SharedPreferences prefsString clave)

PENDIENTE comprobar queacute clave concreta ha cambiado ysu nuevo valor

para modificar la interfaz de una actividad o elcomportamiento de

alguacuten componente

Otra forma maacutes sencilla de controlar los cambios en las preferencias es registrar unlistener en el constructor de la actividad principal o aquella que haga uso de los valores depreferencias de la forma

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto)

mListener = new OnSharedPreferenceChangeListener() public void onSharedPreferenceChanged(SharedPreferences

sharedPreferences String key)

if( keyequals(MICLAVE) )boolean valor =

sharedPreferencesgetBoolean(MICLAVE false)

prefsregisterOnSharedPreferenceChangeListener(mListener)

NotaEn este segundo ejemplo mListener es una variable miembro de la clase Para este caso esnecesario registrar el listener de esta forma porque sino el garbage collector lo eliminariacutea (debidoa particularidades en la programacioacuten de los listeners asociados a las SharedPreferences)

Persistencia

24Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

32 Proveedores de contenidos

Los proveedores de contenidos o ContentProvider proporcionan una interfaz parapublicar y consumir datos identificando la fuente de datos con una direccioacuten URI queempieza por content Son una forma maacutes estaacutendar que los adaptadores (como el quevimos en la sesioacuten anterior en la seccioacuten dedicada a SQLite) de desacoplar la capa deaplicacioacuten de la capa de datos

Podemos encontrar dos tipos principales de proveedores de contenidos en Android Losproveedores nativos son proveedores que el propio sistema nos proporciona para poderacceder a datos del dispostivo incluyendo audio viacutedeo imaacutegenes informacioacuten personaletc Por otra parte tambieacuten es posible que creemos nuestros propios proveedores con talde permitir a nuestra aplicacioacuten compartir datos con el resto del sistema En esta sesioacutenhablaremos de ambos tipos

321 Proveedores nativos

Android incluye una serie de proveedores de contenidos que nos pueden proporcionarinformacioacuten de diferentes tipos informacioacuten sobre nuestros contactos informacioacuten delcalendario e incluso ficheros multimedia Ejemplos de estos proveedores de contenidosnativos son el Browser CallLog ContactsContract MediaStore Settings y elUserDictionary Para poder hacer uso de estos proveedores de contenidos y acceder alos datos que nos proporcionan debemos antildeadir los permisos adecuados al ficheroAndroidManifestxml Por ejemplo para acceder al listiacuten telefoacutenico podemos antildeadir elpermiso correspondiente de la siguiente forma

ltuses-sdk androidminSdkVersion=8 gtltuses-permission androidname=androidpermissionREAD_CONTACTSgt

ltmanifestgt

Para obtener informacioacuten de un ContentProvider debemos hacer uso del meacutetodo query

de la clase ContentResolver El primero de los paraacutemetros de dicho meacutetodo seraacute la URIdel proveedor de contenidos al que deseamos acceder Este meacutetodo devuelve un cursorque nos permitiraacute iterar entre los resultados tal como se vio en la sesioacuten anterior en elapartado de cursores

ContentResolverquery(Uri uriString[] projectionString selectionString[] selectionArgsString sortOrder)

Por ejemplo la siguiente llamada nos proporcionaraacute un cursor que nos permitiraacute acceder atodos los contactos de nuestro teleacutefono moacutevil Para ello hemos hecho uso de la constante

Persistencia

25Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ContactsContractContactsCONTENT_URI que almacena la URI del proveedor decontenidos correspondiente Al resto de paraacutemetros se le ha asignado el valor null

ContentResolver cr = getContentResolver()Cursor cursor = crquery(ContactsContractContactsCONTENT_URI

null null null null)

NotaEn versiones anteriores a Android 20 las estructuras de datos de los contactos son diferentes y nose accede a esta URI

Una vez obtenido el cursor es posible mapear sus campos con alguacuten componente de lainterfaz graacutefica de la actividad De esta forma cualquier cambio que se produzca en elcursor se reflejaraacute automaacuteticamente en el componente graacutefico sin que sea necesario queprogramemos el coacutedigo que lo refresque Para esto tenemos que llamar al meacutetodosetNotificationUri del cursor con lo cual nos aseguraremos de que los listeners seraacutennotificados cuando se produzca alguacuten cambio en los datos referenciados por dicho cursor

cursorsetNotificationUri(crContactsContractContactsCONTENT_URI)

Para asignar un adaptador a una lista de la interfaz graacutefica de la actividad maacutesconcretamente una ListView utilizamos el meacutetodo setAdapter(Adapter)

ListView lv = (ListView)findViewById(RidListView01)SimpleCursorAdapter adapter = new SimpleCursorAdapter(

getApplicationContext()Rlayouttextviewlayoutcursornew String[]

ContactsContractContacts_IDContactsContractContactsDISPLAY_NAME

new int[]RidTextView1RidTextView2)

lvsetAdapter(adapter)

En este ejemplo los identificadores RidTextView1 y RidTextView2 se correspondencon views del layout que define cada fila de la ListView como se puede ver en elarchivo textviewlayoutxml que tendriacuteamos que crear

ltxml version=10 encoding=utf-8gt

ltLinearLayout xmlnsandroid=httpschemasandroidcomapkresandroidandroidorientation=horizontalandroidlayout_width=fill_parentandroidlayout_height=wrap_contentgt

ltTextView androidid=+idTextView1androidtextStyle=bold

Persistencia

26Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

androidems=2androidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltTextView androidid=+idTextView2androidtextStyle=boldandroidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltLinearLayoutgt

La lista en siacute (identificada por RidListView01 en el presente ejemplo) estariacutea en otroXML layout por ejemplo en mainxml

Se debe tener en cuenta que en el caso de este ejemplo si quisieacuteramos obtener losnuacutemeros de teleacutefono de cada uno de nuestros contactos tendriacuteamos que recorrer el cursorobtenido a partir del proveedor de contenidos y por cada persona en nuestra agenda crearun nuevo cursor que recorriera los teleacutefonos ya que una persona puede tener asignadosvarios teleacutefonos

Puedes acceder a un listado completo de los proveedores de contenidos nativos existentesen Android consultando su manual de desarrollo Concretamente leyendo la seccioacutendedicada al paquete androidprovider enhttpdeveloperandroidcomreferenceandroidproviderpackage-summaryhtml

322 Proveedores propios crear un nuevo proveedor de contenidos

Para acceder a nuestras fuentes de datos propias de manera estaacutendar nos interesaimplementar nuestros propios ContentProvider Gracias a que proporcionamos nuestrosdatos a partir de una URI quien use nuestro proveedor de contenidos no deberaacutepreocuparse de doacutende provienen los datos indicados por dicha URI podriacutean provenir deficheros locales en la tarjeta de memoria de un servidor en Internet o de una base dedatos SQLite Dada una URI nuestro proveedor de contenidos deberaacute proporcionar en suinterfaz los meacutetodos necesarios para realizar las operaciones baacutesicas en una base de datosinsercioacuten lectura actualizacioacuten y borrado

Para crear un nuevo proveedor de contenidos definimos una subclase deContentProvider Utilizaremos una sobrecarga del meacutetodo onCreate para crear einicializar la fuente de datos que queramos hacer puacuteblica a traveacutes de dicho proveedor Unesqueleto de subclase de ContentProvide podriacutea ser el siguiente

public class MiProveedor extends ContentProvider

Overridepublic boolean onCreate()

Inicializar la base de datosreturn true

Dentro de la clase deberemos crear un atributo puacuteblico y estaacutetico de nombreCONTENT_URI en el cual almacenaremos el URI completo del proveedor que estamos

Persistencia

27Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creando Este URI debe ser uacutenico por lo que una buena idea puede ser tomar como baseel nombre de paquete de nuestra aplicacioacuten La forma general de un URI es la siguiente

content[NOMBRE_PAQUETE]provider[NOMBRE_APLICACION][RUTA_DATOS]

Por ejemplo

contentesuajtechandroidprovidermiaplicacionelementos

En general (y por simplicidad) no es necesario antildeadir la cadena provider al nombre delpaquete por lo que podemos utilizar directamente el nombre del paquete seguido de laruta de datos de la forma

contentesuajtechandroidmiaplicacionelementos

Estos URIs se pueden presentar en dos formas La URI anterior representa una consultadirigida a obtener todos los valores de ese tipo (en este caso todos los elementos) Si a laURI anterior antildeadimos un sufijo [NUMERO_FILA] estamos representando una consultadestinada a obtener un uacutenico elemento (por ejemplo el quinto elemento en el siguienteejemplo)

contentesuajtechandroidprovidermiaplicacionelementos5

NotaSe considera una buena praacutectica de programacioacuten el proporcionar acceso a nuestro proveedor decontenidos utilizando cualquiera de estas dos formas

La forma maacutes simple de determinar cuaacutel de las dos formas anteriores de URI se utilizoacutepara hacer una peticioacuten a nuestro proveedor de contenidos es mediante un Uri MatcherCrearemos y configuramos un elemento Uri Matcher para analizar una URI y determinara cuaacutel de las dos formas se corresponde En el siguiente coacutedigo se muestra el coacutedigobaacutesico que deberemos emplear para implementar este patroacuten

public class MiProveedor extends ContentProvider

private static final String miURI =contentesuajtechandroidprovidermiaplicacionelementos

public static final Uri CONTENT_URI = Uriparse(miUri)

Creamos las constantes que nos van a permitir diferenciar entre los dos tipos distintos de peticiones a partir de la URIprivate static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

Inicializamos el objeto UriMatcher Una URI que termine en elementos se corresponderaacute con una consulta para todas las filas mientras que una URI con el sufijo elementos[FILA] representaraacute una uacutenica filastatic

uriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidprovidermiaplicacion

elementos TODAS_FILAS)

Persistencia

28Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

uriMatcheraddURI(esuajtechandroidprovidermiaplicacionelementos UNA_FILA)

Overridepublic boolean onCreate()

PENDIENTE inicializar la base de datosreturn true

Esta misma teacutecnica se puede emplear para proporcionar diferentes alternativas de URIpara diferentes subconjuntos de datos como por ejemplo diferentes tablas en una base dedatos a traveacutes del mismo proveedor de contenidos

323 Proveedores propios crear la interfaz de consultas

Para permitir realizar operaciones con los datos publicados a traveacutes de nuestro proveedorde contenidos deberemos implementar los meacutetodos delete insert update y query

para el borrado insercioacuten actualizacioacuten y realizacioacuten de consultas respectivamenteEstos meacutetodos son la interfaz estaacutendar utilizada con proveedores de contenidos de formaque para compartir datos entre aplicaciones no se tendraacuten interfaces distintos paradiferentes fuentes de datos

Lo maacutes habitual suele ser implementar un proveedor de contenidos como un mecanismopara permitir el acceso a una base de datos SQLite privada a una aplicacioacuten aunque atraveacutes de estos meacutetodos indicados se podriacutea en principio acceder a cualquier fuente dedatos

El siguiente coacutedigo extiende el ejemplo anterior (MiProveedor) y muestra el esqueleto deestos meacutetodos dentro de una subclase de ContentProvider Obseacutervese que se hace usodel objeto UriMatcher para determinar que tipo de consulta se estaacute realizando

Overridepublic Cursor query(Uri uri

String[] projectionString selectionString[] selectionArgsString sort)

Si se trata de una consulta para obtener una uacutenica filalimitamos

los resultados a obtener de la fuente de datos por medio de una claacuteusula whereswitch(UriMatchermatch(uri))

case UNA_FILA PENDIENTE modifica la sentencia SELECT

mediante una claacuteusula where obteniendo el identificador de fila

como numeroFila = urigetPathSegments()get(1))

return null

Override

Persistencia

29Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public Uri insert(Uri _uri ContentValues _valores) long idFila = [ Antildeadir un nuevo elemento ]

Devolvemos la URI del elemento recieacuten antildeadidoif (idFila gt 0)

return ContentUriswithAppendendId(CONTENT_URI idFila)

throw new SQLException(No se pudo antildeadir un nuevo elemento a +_uri)

Overridepublic int delete(Uri uri String where String[] whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

Overridepublic int update(Uri uri ContentValues valores String where String[]whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

324 Proveedores propios tipo MIME

El uacuteltimo paso para crear un proveedor de contenidos es definir el tipo MIME queidentifica el tipo de datos que devuelve el proveedor Debemos sobrecargar el meacutetodogetType para que devuelva una cadena que identifique nuestro tipo de datos de manerauacutenica Se deberiacutean definir dos tipos posibles uno para el caso de una uacutenica fila y otro parael caso de devolver todos los resultados Se debe utilizar una sintaxis similar a la delsiguiente ejemplo

bull Un uacutenico elementovndesuajtechandroidmiaplicacioncursoritemmiproveedorcontent

bull Todos los elementosvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

Como se puede observar la cadena comienza siempre por vnd A continuacioacutentendriacuteamos el nombre del paquete de nuestra aplicacioacuten Lo siguiente es cursor ya quenuestro proveedor de contenidos estaacute devolviendo datos de tipo Cursor Justo antes de labarra pondremos item en el caso del tipo MIME para un uacutenico elemento y dir para elcaso del tipo MIME para todos los elementos Finalmente tras la barra pondremos elnombre de nuestra clase (en minuacutesculas) seguido de content En el siguiente coacutedigo deejemplo se muestra coacutemo sobrecargar getType seguacuten la URI

Persistencia

30Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Overridepublic String getType(Uri _uri)

switch (uriMatchermatch(_uri)) case TODAS_FILAS

returnvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

case UNA_FILAreturn

vndesuajtechandroidmiaplicacioncursoritemmiproveedorcontentdefault

throw new IllegalArgumentException(URI nosoportada + _uri)

325 Proveedores propios registrar el proveedor

No debemos olvidar incorporar nuestro proveedor de contenidos al Manifest de laaplicacioacuten una vez que lo hemos completado Esto se haraacute por medio del elementoprovider cuyo atributo authorities podremos utilizar para especificar la URI base talcomo se puede ver en el siguiente ejemplo

ltprovider androidname=MiProveedorandroidauthorities=esuajtechandroidmiaplicaciongt

326 Content Resolvers

Aunque en la seccioacuten de proveedores nativos ya se ha dado alguacuten ejemplo de coacutemo haceruso de un Content Resolver para realizar una consulta ahora que hemos podido observaren detalle la estructura de un proveedor de contenidos propio es un buen momento paraver en detalle coacutemo realizar cada una de las operaciones que eacutestos permiten

El contexto de una aplicacioacuten incluye siempre una instancia de la claseContentResolver a la cual se puede acceder mediante el meacutetodo getContentResolver

ContentResolver cr = getContentResolver()

Esta clase incorpora diversos meacutetodos para realizar consultas a proveedores decontenidos Cada uno de estos meacutetodos acepta como paraacutemetro una URI que indica conqueacute proveedor de contenidos se desea interactuar

Las consultas realizadas sobre un proveedor de contenidos recuerdan a las consultasrealizadas sobre bases de datos Los resultados de las consultas se devuelven comoobjetos de la clase Cursor Se pueden extraer valores del cursor de la misma forma en laque se explicoacute en el apartado de bases de datos de la sesioacuten anterior El meacutetodo query dela clase ContentResolver acepta los siguientes paraacutemetros

bull La URI del proveedor del contenidos al que se desea realizar la consultabull Una proyeccioacuten que enumera las columnas que se quieren incluir en los resultados

obtenidosbull Una claacuteusula where que define las filas a obtener Se pueden utilizar comodines que

Persistencia

31Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

se reemplazaraacuten por valores incluidos en el siguiente paraacutemetrobull Una matriz de cadenas que se pueden utilizar para reemplazar los comodines en la

claacuteusula where anteriorbull Una cadena que describa la ordenacioacuten de los elementos devueltos

El siguiente coacutedigo muestra un ejemplo

ContentResolver cr = getContentResolver() Devolver todas las filasCursor todasFilas = crquery(MiProveedorCONTENT_URI null null nullnull) Devolver todas columnas de las filas para las que la columna 3 tiene un valor determinado ordenadas seguacuten el valor de la columna 5String where = COL3 + = + valorString orden = COL5Cursor algunasFilas = crquery(MiProveedorCONTENT_URI null where nullorden)

327 Otras operaciones con Content Resolvers

Para realizar otro tipo de operaciones con los datos de un proveedor de contenidosharemos uso de los meacutetodos delete update e insert de un objeto ContentResolver

Con respecto a la insercioacuten la clase ContentResolver proporciona en su interfaz dosmeacutetodos diferentes insert y bulkInsert Ambos aceptan como paraacutemetro la URI delproveedor de contenidos pero mientras que el primer meacutetodo tan solo toma comoparaacutemetro un objeto de la clase ContentValues (ya vistos en la seccioacuten de bases dedatos) el segundo toma como paraacutemetro una matriz de elementos de este tipo El meacutetodoinsert devolveraacute la URI del elemento recieacuten antildeadido mientras que bulkInsert

devolveraacute el nuacutemero de filas correctamente insertadas

Obtener el Content ResolverContentResolver cr = getContentResolver()

Crear una nueva fila de valores a insertarContentValues nuevosValores = new ContentValues()

Asignar valores a cada columnanuevosValoresput(NOMBRE_COLUMNA nuevoValor)[ Repetir para cada columna ]

Uri miFilaUri = crinsert(MiProveedorCONTENT_URI nuevosValores)

Insertamos ahora una matriz de nuevas filasContentValues[] arrayValores = new ContentValues[5] PENDIENTE rellenar el array con los valores correspondientesint count = crbulkInsert(MiProveedorCONTENT_URI arrayValores)

Para el caso del borrado hacemos uso del meacutetodo delete del Content Resolver pasandocomo paraacutemetro la URI de la fila que deseamos eliminar de la fuente de datos Otraposibilidad es incluir una claacuteusula where para eliminar muacuteltiples filas Ambas teacutecnicas semuestran en el siguiente ejemplo

ContentResolver cr = getContentResolver()

Persistencia

32Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Eliminar una fila especiacuteficacrdelete(miFilaUri null null) Eliminar las primeras cinco filasString where = _id lt 5crdelete(MiProveedorCONTENT_URI where null)

Una actualizacioacuten se lleva a cabo mediante el meacutetodo update del objeto ContentResolver Este meacutetodo toma como paraacutemetro la URI del proveedor de contenidosobjetivo un objeto de la clase ContentValues que empareja nombres de columna convalores actualizados y una claacuteusula where que indica queacute filas deben ser actualizadas Elresultado de la actualizacioacuten seraacute que en cada fila en la que se cumpla la condicioacuten de laclaacuteusula where se cambiaraacuten los valores de las columnas especificados por el objeto de laclase ContentValues tambieacuten se devolveraacute el nuacutemero de filas correctamenteactualizadas A continuacioacuten se muestra un ejemplo

ContentValues nuevosValores = new ContentValues()

Utilizamos el objeto ContentValues para especificar en queacute columnas queremos que se produzca un cambio y cuaacutel debe ser el nuevo valor de dichas columnasnuevosValuesput(NOMBRE_COLUMNA nuevoValor)

Aplicamos los cambios a las primeras cinco filasString where = _id lt 5

getContentResolver()update(MiProveedorCONTENT_URI nuevosValores wherenull)

Persistencia

33Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

4 Ejercicios - Persistencia en Android proveedores de contenidos ySharedPreferences

41 Compartir datos entre actividades con Shared Preferences (075 puntos)

Descarga de las plantillas el proyecto Dni Dicho proyecto estaacute compuesto de dosactividades Formulario y Resumen El objetivo del ejercicio es conseguir que al pulsar elbotoacuten Siguiente en la actividad Formulario se muestre en la actividad Resumen cuaacutelesfueron los datos introducidos

En este ejercicio vamos a hacer uso de SharedPreferences para pasar los datos de unaactividad a la siguiente Al pulsar en Siguiente en Formulario deberemos guardar el valorde todos los campos mediante esta plataforma Al mostrarse la actividad Resumendeberemos leer estos datos tambieacuten a partir de SharedPreferences para mostrarlos porpantalla

Por uacuteltimo crea un meacutetodo que valide el DNI (debe tratarse de una secuencia de ochodiacutegitos entre 0 y 9 y una letra al final) Si se pulsa Siguiente y el DNI no tiene el formatocorrecto no se deberaacute pasar a la actividad Resumen En lugar de esto se mostraraacute unToast en pantalla con un mensaje de error

42 Actividad de preferencias (075 puntos)

En este ejercicio seguimos trabajando con el proyecto del ejercicio anterior Vamos aantildeadir un menuacute de opciones que permita escoger en queacute momento se comprobaraacute lavalidez del DNI Para ello antildeade en primer lugar un menuacute a la actividad Formulario conuna uacutenica opcioacuten cuyo nombre seraacute Opciones

Al seleccionar esta opcioacuten se deberaacute mostrar una actividad de preferencias cuyo aspectoseraacute el siguiente

Persistencia

34Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Preferencias de la aplicacioacuten Dni

El significado de las opciones seraacute el siguiente

bull En el campo con esta opcioacuten seleccionada al pulsar sobre el campo de DNI en elformulario deberaacute mostrarse un Toast indicando si la sintaxis del DNI es correcta ono

bull Al pulsar si se desactiva esta opcioacuten (que deberaacute estar activada por defecto) no seharaacute la comprobacioacuten del DNI al pulsar el botoacuten Siguiente por lo que siempre seraacuteposible pasar de la actividad Formulario a la actividad Resumen sea cual sea elformato del DNI introducido

bull El uacuteltimo check box se encontraraacute siempre deshabilitado iquestQueacute atributo debemosutilizar para conseguir esto

Crea dos variables booleanas llamadas enElCampo y alPulsar Su valor dependeraacute delestado de los checkboxes anteriores y se actualizaraacute por medio del eventoonSharedPreferenceChange Utiliza el valor de estas variables en el coacutedigo de tuactividad para que esta tenga el comportamiento indicado

43 Proveedor de contenidos propio (075 puntos)

Vamos a implementar otra forma de acceder a la base de datos de usuarios de laaplicacioacuten BaseDatos desarrollada durante los ejercicios de la sesioacuten anterior siguiendoesta vez el patroacuten de disentildeo ContentProvider de Android Seguiremos trabajando puescon dicho proyecto

bull Creamos una nueva clase llamada UsuariosProvider que herede deContentProvider Esto nos obligaraacute a sobrecargar una serie de meacutetodos abstractosAntes de implementar la query vamos a configurar el provider

bull Antildeadimos algunos campos tiacutepicos de los content provider

Persistencia

35Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public static final Uri CONTENT_URI =Uriparse(contentesuajtechandroidbasedatosusuarios)

private static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

staticuriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

TODAS_FILAS)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

UNA_FILA)

bull Vamos a acceder a la misma base de datos de usuarios utilizada en los ejercicios de lasesioacuten anterior pero no vamos a hacerlo a traveacutes del adaptador que tuvimos queimplementar sino que vamos a copiar de eacutel el coacutedigo que nos haga falta Copia loscampos que definen el nombre de la base de datos de la tabla de las columnas laversioacuten asiacute como la referencia al contexto y a la base de datos La sentenciacompilada del insert ya no va a hacer falta Inicializa los valores necesarios en elconstructor Para inicializar la referencia a la base de datos vamos a utilizar una vezmaacutes MiOpenHelper Podemos copiarlo del adaptador que ya implementamos en losejercicios de la sesioacuten anterior

bull Implementa de forma apropiada el getType para devolver un tipo MIME diferenteseguacuten si se trata de una URI de una fila o de todas las filas Para ello ayuacutedate deluriMatcher

bull Implementa el meacutetodo query Simplemente se trata de devolver el cursor queobtenemos al hacer una consulta a la base de datos SQLite Algunos de los paraacutemetrosque le pasaremos los recibimos como paraacutemetros del meacutetodo del provider Los que notengamos iraacuten con valor null

bull Aunque no tenemos todos los meacutetodos del UsuariosProvider implementadospodemos probarlo Para ello debemos registarlo en el AndroidManifestxml

ltprovider androidname=UsuariosProvider

androidauthorities=esuajtechandroidbasedatosgtltapplicationgt

ltmanifestgt

bull En la clase Main se antildeadioacute codigo para insertar una serie de valores en la base dedatos y mostrarlos en el campo de texto Manteniendo este coacutedigo vamos a antildeadir alcampo de texto el resultado obtenido con la consulta del UsuariosProvider paracomprobar que tanto el adaptador de la base de datos como el proveedor nosdevuelven el mismo resultado

44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)

Porque es la forma estaacutendar que establece Android de acceder a contenidos Ademaacutes elproveedor de contenidos nos permitiraacute notificar al ContentResolver de los cambiosocurridos Asiacute componentes en la pantalla podraacuten refrescarse de forma automaacutetica

Persistencia

36Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Utiliza el proyecto ProveedorContenidos de las plantillas Implementa la insercioacuten enel proveedor de contenidos Prueacutebala insertando algunos usuarios de ejemplo en laclase Main Implementa tambieacuten el OnClickListener del botoacuten que inserta nuevosusuarios El nombre del nuevo usuario iraacute indicado en el EditText

bull Comprueba que la insercioacuten funciona y que gracias a la siguiente liacutenea y al estarusando un proveedor de contenidos la lista se actualiza automaacuteticamente cuandoocurre alguacuten cambio sin necesidad de pedir expliacutecitamente la actualizacioacuten al pulsarel botoacuten

cursorsetNotificationUri(cr UsuariosProviderCONTENT_URI)

NotaEsto no va a funcionar si se notifica que se ha producido un cambio al insertar o borrar unelemento Para ello usamosgetContext()getContentResolver()notifyChange(UsuariosProviderCONTENT_URI null)

bull Implementa el meacutetodo delete del proveedor de contenidos Prueacutebalo en la claseMain Termina de implementar el onCreateContextMenuListener que se ejecutaraacutecada vez que se haga una pulsacioacuten larga sobre alguna entrada de la lista Compruebaque funciona (eliminando el usuario correspondiente y no otro)

Persistencia

37Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

5 Persistencia de datos en iOS Ficheros y SQLite

51 Introduccioacuten

La persistencia de datos en iOS se realiza en una pequentildea memoria flash que tienenuestro dispositivo la cual es equivalente a un disco duro de tamantildeo limitado Los datosque ahiacute se almacenen se conservaraacuten aunque el dispositivo se apague Por motivos deseguridad el SO de iOS no permite que las aplicaciones accedan directamente a lamemoria interna pero a cambio existe una API completa de acceso a la porcioacuten dememoria que le corresponde a la app que desarrollemos

En una aplicacioacuten iOS encontraremos baacutesicamente 4 modos de almacenamiento deinformacioacuten de forma persistente

bull Ficheros de propiedades (plist)bull Bases de datos embebidas SQLitebull Datos de usuario (User Defaults)bull Core Data

En esta primera sesioacuten veremos coacutemo usar los ficheros de texto (Property Lists) y elalmacenamiento en base de datos de tipo SQLite

52 Ficheros plist (Property Lists)

El almacenamiento de datos en ficheros de propiedades es uno de los maacutes usados en eldesarrollo de las aplicaciones para iOS Se usa principalmente para guardar datos deconfiguracioacuten de la aplicacioacuten u otra informacioacuten poco compleja y acceder a ella deforma sencilla Si estamos desarrollando un juego podemos usarlos para la definicioacuten delos niveles almacenar las puntuaciones del jugador etc

521 Leyendo datos desde ficheros plist

Los ficheros plist (Property Lists) tienen un formato XML aunque XCode nosproporciona una interfaz que nos permite acceder a ellos de forma muy sencilla Paraentender el funcionamiento de este tipo de ficheros vamos a realizar un ejemplo en el queprimero crearemos un fichero plist lo completaremos con datos de ejemplo y despueacutes loleeremos para mostrarlo por pantalla

Un ejemplo de un fichero plist podriacutea ser el siguiente

Persistencia

38Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Fichero plist

Antes de nada creamos un proyecto nuevo en XCode de tipo Window-based

Application al que llamaremos sesion03-ejemplo1 Para crear un fichero plistdebemos hacer click derecho sobre el directorio Supporting Files de nuestro proyectoy seleccionamos iOS gt Resource gt Property List Lo guardaremos con el nombreconfigUsuario Seguidamente ya podemos acceder al fichero recieacuten creado haciendoclick sobre el en este momento XCode nos mostraraacute un pequentildeo editor totalmente vacioen el que iremos rellenando con datos Podemos verlo tambieacuten en formato XML sihacemos click derecho sobre el y seleccionamos Open As gt Preview

Para empezar a rellenar el fichero que acabamos de crear hacemos click derecho dentrode su contenido (ahora vacio) y seleccionamos Add row Entonces nos apareceraacute unafila vaciacutea Dentro del campo Key escribimos la clave un nombre descriptivo quedeberemos utilizar maacutes adelante para acceder a los datos En nuestro caso vamos aescribir config como clave Dentro de la columna Type seleccionamosDictionary Veremos que aparece una flecha en el lado izquierdo que indica quepueden haber subniveles dentro de nuestro diccionario

Ahora antildeadimos un nuevo item que cuelgue del diccionario que acabamos de crear paraello hacemos click sobre la flecha de la izquierda esta se giraraacute hacia abajo entonceshacemos click ahora sobre el siacutembolo + (maacutes) Nos apareceraacute una nueva liacutenea que cuelgadel diccionario

En esta nueva liacutenea dentro del campo key escribimos nombre el campo type lodejamos como de tipo String ya que va a ser una cadena de texto y en la columna deValue escribimos nuestro nombre por ejemplo Javi

Ahora vamos a antildeadir otro campo nuevo en nuestra configuracioacuten para ello hacemosclick sobre el siacutembolo + y nos apareceraacute una nueva liacutenea justo al mismo nivel que laanterior y colgando del diccionario Dentro del campo de Key escribimos ciudad yen Value escribimos Alicante El campo de Type lo dejamos como String

Persistencia

39Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora dentro de nuestro fichero plist vamos a indicar los dispositivos que dispone elusuario para ello creamos una nueva linea dentro del diccionario y le pondremos comoclave dispositivos Seleccionamos el tipo Array y seguimos los mismos pasosque al crear el diccionario para ello hacemos click sobre la flecha de la izquierda ydespueacutes sobre el siacutembolo + (maacutes) Ahora nos apareceraacute una nueva fila con clave Item 0Seleccionamos el tipo String y en el campo Value iPhone

Ahora repetimos el paso 3 veces indicando como values iPad Android y

Blackberry

Una vez seguidos todos estos pasos tendremos nuestro fichero de propiedades listo Debede quedar como se muestra a continuacioacuten

Fichero plist terminado

Este fichero se almacenaraacute dentro del directorio de bundle cuando compilemos laaplicacioacuten En el caso de que queramos escribir en el debermos almacenar el ficherodentro del directorio de Documents del binario

Ahora desde nuestro coacutedigo vamos a acceder a eacutel y a mostrarlo por pantalla para elloescribimos el siguiente coacutedigo dentro del meacutetodo didFinishLaunchingWithOptions dela clase delegada

Cargamos el fichero PLISTNSString mainBundlePath = [[NSBundle mainBundle] bundlePath]NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]NSDictionary diccionario = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionario delraiz

NSString nombre = [dicConfig objectForKeynombre]

Persistencia

40Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

[selfwindow makeKeyAndVisible]return YES

Si todo ha ido correctamente deberemos obtener la siguiente salida por la consola

Lista de dispositivos

522 Escribiendo datos en ficheros plist

Partiendo del ejemplo anterior vamos a escribir ahora en el fichero para ello primerodebemos comprobar que el fichero plist se encuentre dentro del directorio de Documents

del dispositivo en el caso de que no lo esteacute (por ejemplo si es la primera vez que arrancala aplicacioacuten) debemos copiarlo a ese directorio Una vez que tenemos el fichero dentrodel directorio de documentos ya podemos escribir en el

Para realizar todo el proceso de comprobacioacuten de la existencia del fichero dentro deldirectorio de documentos copiarlo en ese directorio y leer los datos debemos de sustituirel coacutedigo que hay dentro del meacutetodo didFinishLaunchingWithOptions de la clasedelegada por este otro

AtencioacutenPara que funcione la escritura en un fichero plist este debe de estar dentro del directorio deDocuments de nuestro dispositivo iOS no en el boundle

NSDictionary diccionarioRaiz

Ruta del directorio de documentos de nuestro dispositivoNSArray paths =

NSSearchPathForDirectoriesInDomains(NSDocumentDirectoryNSUserDomainMask YES)

if ([paths count] gt 0)

Persistencia

41Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

Primero comprobamos si existe el fichero en eldirectorio de documentos

BOOL fileExists = [[NSFileManager defaultManager]fileExistsAtPathdocumentsFilename]if (fileExists)

NSLog(Fichero encontrado en directorio de documentosOK

documentsFilename)

Si existe ya cargamos los datos desde aquidirectamente

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

else

NSLog(No se encuentra el fichero configUsuarioplisten

el directorio de documentos-gtLo creamos)

Si no existe primero cargamos el fichero PLISTdesde el Boundle

NSString mainBundlePath = [[NSBundle mainBundle]bundlePath]

NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Y despues escribimos los datos cargados (eldiccionario completo)

a un fichero nuevo de la carpeta de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

NSLog(plist de configUsuario creado)

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionariodel raiz

NSString nombre = [dicConfig objectForKeynombre]NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

Persistencia

42Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Por ultimo liberamos la memoria del diccionario raiz[diccionarioRaiz release]

[selfwindow makeKeyAndVisible]return YES

Para comprobar el funcionamiento arrancamos la aplicacioacuten y veremos que la primera vezse crearaacute el fichero dentro de la carpeta de Documents de nuestro dispositivo y en lassiguientes ya no se crearaacute y simplemente lo leeraacute desde la carpeta de Documents

Ahora que ya tenemos la gestioacuten de ficheros baacutesica completada vamos a escribir sobre elalguacuten dato Para ello simplemente escribimos el siguiente fragmento de coacutedigo justo antesde la liacutenea [selfwindow makeKeyAndVisible]

Cambiamos el nombre y lo guardamos en el fichero Ruta del directorio de documentos de nuestro dispositivo

if ([paths count] gt 0)

Cargamos el fichero desde el directorio dedocumentos del dispositivo

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cambiamos el valor del nombre en el diccionario[dicConfig setValueOtro nombre forKeynombre]

Escribimos todo el diccionario de nuevo al fichero del directorio de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

Por ultimo liberamos el diccionario de lamemoria

[diccionarioRaiz release]

Lo que hemos hecho en el coacutedigo anterior es cargar todo el diccionario de nuevo desde eldirectorio de documentos modificar los datos que queremos cambiar o incluso antildeadir ydespueacutes guardar el diccionario en el fichero

Volvemos a arrancar la aplicacioacuten y veremos que ha cambiado el campo de nombre

Nota

Persistencia

43Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

La lectura y escritura de datos en un fichero plist se debe de hacer de una soacutela vez nunca porpartes Siempre se debe de cargar o escribir un diccionario NSDictionary como elementoprincipal del fichero

53 SQLite

SQLite es una base de datos relacional ligera con la que se puede trabajar de formasencilla mediante sentencias SQL La base de datos se almacenaraacute en un fichero dentrode nuestra aplicacioacuten Este meacutetodo de persistencia puede ser el adecuado cuando los datosa almacenar esteacuten en un formato de tablas filas y columnas y se necesite que los datossean accesibles de forma raacutepida

531 Herramientas disponibles

Para explicar el funcionamiento de las librerias de SQLite de Cocoa Touch vamos a seguirun ejemplo completo Comenzamos creando la base de datos para ello podemos utilizaruno de los frontends que facilitan la gestioacuten del SQLite uno bastante sencillo yrecomendado es el SQLite Database Browser y otro es fmdb

Nosotros utilizaremos el primero (SQLite Database Browser) Abrimos la aplicacioacuten yhacemos click sobre el botoacuten de crear nueva base de datos que llamaremospersonasDBsqlite Seguidamente creamos las tablas para simplificar en nuestroejemplo crearemos una soacutela tabla llamada personas Esta tabla tendraacute las siguientescolumnas id (NUMERIC) nombre (TEXT) apellidos (TEXT) dni (TEXT) edad

(NUMERIC) y localidad (TEXT)

Persistencia

44Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Estructura de la Base de Datos

Una vez creada la tabla pasamos a insertar datos dentro de ella para ello abrimos lapestantildea de Browse Data y hacemos click en New Record Cuando tengamos al menos 3registros metidos dentro de la tabla ya podemos guardar la base de datos Hacemos clicksobre guardar le ponemos el nombre que queramos y elegimos cualquier directorio delMAC

Datos de la Base de Datos

532 Lectura de datos

Una vez que tenemos la base de datos SQLite creada ya podemos empezar a escribir elcoacutedigo para leer y mostrar los datos dentro de nuestra aplicacioacuten

Primero empezamos creando un proyecto nuevo en XCode usando la plantilla basada envistas View-based application y lo llamaremos sesion03-ejemplo2 Ahora antildeadimosel framework de sqlite a nuestro proyecto para ello hacemos click sobre el raiz delproyecto y seleccionamos la pestantildea Build Phases Desplegamos el listado de Link

Binary With Libraries y hacemos click sobre el botoacuten (+) El el listadoseleccionamos el framework libsqlite3dylib y hacemos click en Add

Persistencia

45Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Antildeadiendo el framework libsqlite3dylib

Ahora antildeadimos a nuestro proyecto la base de datos que hemos creado anteriormentepara ello arrastramos el fichero personasDBsqlite a nuestro directorio Supporting

Files Asegurate de seleccionar la opcioacuten de Copy items to destination groups

folder (if needed) para que se copie el fichero dentro de la carpeta del proyecto

Para acelerar el proceso de lectura de la base de datos y ahorrar memoria vamos a cargarprimero todos los datos en objetos para ello vamos a crear una clase que se encargue dealmacenar los datos Hacemos click derecho sobre el raiz del proyecto New file gtObjective-C class seleccionamos NSObject como clase principal y le damos a NextEscribimos como nombre de la clase Persona y hacemos click en save

Personah

imports iniciales

interface Persona NSObject int _uIdNSString _nombreNSString _apellidos

Persistencia

46Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSInteger _edadNSString _localidad

property (nonatomic assign) int uIdproperty (nonatomic copy) NSString nombreproperty (nonatomic copy) NSString apellidosproperty (nonatomic) NSInteger edadproperty (nonatomic copy) NSString localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidos edad(NSInteger)edadlocalidad(NSString )localidad

end

Personam

import Personah

implementation Persona

synthesize uId = _uIdsynthesize nombre = _nombresynthesize apellidos = _apellidossynthesize edad = _edadsynthesize localidad = _localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidosedad(NSInteger)edad localidad(NSString )localidad

if ((self = [super init])) selfuId = uIdselfnombre = nombreselfapellidos = apellidosselfedad = edadselflocalidad = localidad

return self

- (void) dealloc selfnombre = nilselfapellidos = nilselflocalidad = nil[super dealloc]

end

Una vez creada la clase Persona vamos a crear la clase encargada de manejar la basede datos sqlite3 Otra opcioacuten seriacutea cargar todos los datos directamente desde la clasedelegada al arrancar la aplicacioacuten pero no es recomendable ya que si en alguacuten momentotenemos que cambiar de motor de base de datos lo tendremos maacutes complicado

Persistencia

47Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creamos entonces una clase dentro de nuestro proyecto llamada BDPersistencia queherede de NSObject y escribimos el siguiente coacutedigo

BDPersistenciah

imports iniciales

interface BDPersistencia NSObject sqlite3 _database

+ (BDPersistencia)database- (NSArray )arrayPersonas

end

BDPersistenciam

import BDPersistenciahimport Personah

implementation BDPersistencia

static BDPersistencia _database

+ (BDPersistencia)database if (_database == nil)

_database = [[BDPersistencia alloc] init]return _database

- (id)init if ((self = [super init]))

NSString sqLiteDb = [[NSBundle mainBundle]pathForResourcepersonasDB

ofTypesqlite]

if (sqlite3_open([sqLiteDb UTF8String]amp_database) = SQLITE_OK)

NSLog(Fallo al abrir la BD)

return self

- (void)dealloc sqlite3_close(_database)[super dealloc]

- (NSArray )arrayPersonas

NSMutableArray arraySalida = [[[NSMutableArray alloc]init]

autorelease]

Persistencia

48Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString query = SELECT id nombre apellidoslocalidad edad

FROM personas ORDER BY apellidos DESC

sqlite3_stmt statementif (sqlite3_prepare_v2(_database [query UTF8String]

-1ampstatement nil)

== SQLITE_OK) while (sqlite3_step(statement) == SQLITE_ROW)

int uniqueId = sqlite3_column_int(statement0)

char nombreChar = (char )sqlite3_column_text(statement 1)

char apellidosChar = (char )sqlite3_column_text(statement 2)

char localidadChar = (char )sqlite3_column_text(statement 3)

int edad = sqlite3_column_int(statement 4)

NSString nombre = [[NSString alloc]initWithUTF8StringnombreChar]NSString apellidos = [[NSString alloc]initWithUTF8StringapellidosChar]NSString localidad = [[NSString alloc]initWithUTF8StringlocalidadChar]Persona persona = [[Persona alloc]initWithUiduniqueId nombrenombreapellidosapellidos edadedad

localidadlocalidad]

[arraySalida addObjectpersona][nombre release][apellidos release][localidad release][persona release]

sqlite3_finalize(statement)

return arraySalida

end

Con esto ya podemos leer de nuestra base de datos las personas Para ver que funcionatodo a la perfeccioacuten escribimos el siguiente coacutedigo dentro de la clase delegada en elmeacutetodo applicationDidFinishLaunching Antes debemos incluir en la parte superiorlas clases creadas

import BDPersistenciahimport Personah

NSArray personas = [BDPersistenciadatabase]arrayPersonas

for (Persona persona in personas) NSLog(d d personauId

personanombrepersonaapellidos personalocalidad personaedad)

Persistencia

49Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Si ejecutamos el proyecto obtendremos la siguiente salida por consola

Listado de personas

533 Mostrando los datos en una tabla

Una vez que tenemos el coacutedigo para obtener todas las filas de la tabla de personas de labase de datos queda lo maacutes faacutecil mostrarlas dentro de una vista de la aplicacioacuten En estecaso utilizaremos la vista de tabla UITableView

Comenzamos creando una nueva vista en nuestro proyecto para ello hacemos clickderecho sobre el raiz y seleccionamos New File gt UIVIewController SubclassAsegurate de marcar Subclass of UITableViewController y la opcoacuten de With

XIB for user interface Hacemos click en Next y guardamos el fichero comoPersonasViewController por ejemplo

Ahora abrimos el fichero PersonasViewControllerh y antildeadimos un NSArray queseraacute el que almacene las personas de la base de datos

imports iniciales

interface PersonasViewController UITableViewController

NSArray _listadoPersonas

property (nonatomic retain) NSArray listadoPersonas

end

Ahora abrimos el fichero PersonasViewControllerm antildeadimos el syntetizeimportamos los ficheros de gestioacuten de la base de datos la clase Persona y todo el coacutedigobaacutesico para la gestioacuten de la vista de la tabla

import PersonasViewControllerhimport Personahimport BDPersistenciah

implementation PersonasViewController

synthesize listadoPersonas = _listadoPersonas

Persistencia

50Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

- (id)initWithStyle(UITableViewStyle)style

self = [super initWithStylestyle]if (self)

Custom initializationreturn self

- (void)dealloc

[super dealloc]

selflistadoPersonas = nil

- (void)didReceiveMemoryWarning

Releases the view if it doesnt have a superview[super didReceiveMemoryWarning]

Release any cached data images etc that arent inuse

pragma mark - View lifecycle

- (void)viewDidLoad

[super viewDidLoad]

selftitle = Listado de personas

selflistadoPersonas = [BDPersistenciadatabase]arrayPersonas

- (void)viewDidUnload

[super viewDidUnload] Release any retained subviews of the main view eg selfmyOutlet = nil

- (void)viewWillAppear(BOOL)animated

[super viewWillAppearanimated]

- (void)viewDidAppear(BOOL)animated

[super viewDidAppearanimated]

- (void)viewWillDisappear(BOOL)animated

[super viewWillDisappearanimated]

- (void)viewDidDisappear(BOOL)animated

[super viewDidDisappearanimated]

- (BOOL)shouldAutorotateToInterfaceOrientation

Persistencia

51Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

(UIInterfaceOrientation)interfaceOrientation

Return YES for supported orientationsreturn (interfaceOrientation ==

UIInterfaceOrientationPortrait)

pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView(UITableView)tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection

(NSInteger)section

Return the number of rows in the sectionreturn [selflistadoPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath

(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]if (cell == nil)

cell = [[[UITableViewCell alloc]initWithStyleUITableViewCellStyleSubtitle

reuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona persona = (Persona )[selflistadoPersonas

objectAtIndexindexPathrow]celltextLabeltext = [NSString stringWithFormat

(d) personanombrepersonaapellidos personaedad]celldetailTextLabeltext = personalocalidad

return cell

pragma mark - Table view delegate

- (void)tableView(UITableView )tableViewdidSelectRowAtIndexPath

(NSIndexPath )indexPath

Navigation logic may go here Create and pushanother view controller

end

Persistencia

52Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora soacutelo nos queda incorporar la vista que acabamos de crear dentro de nuestraaplicacioacuten Para ello vamos a llamarla desde el Delegate y a meterla dentro de unNavigation Controller Primero debemos de antildeadir un Outlet en la clasedelegada hacia un Navigation Controller

imports iniciales

interface sesion03_ejemplo1AppDelegate NSObjectltUIApplicationDelegategt

UIWindow _windowUINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet

UINavigationController navController

end

Ahora hacemos click sobre la vista MainWindowxib y arrastramos un Navigation

Controller desde el listado de la columna de la derecha hacia la columna de laizquierda donde pone Objects Nos apareceraacute el Navigation Controller dibujadoen pantalla Ahora desplegamos el Navigation Controller y hacemos click sobre elView Controller que hay dentro Nos vamos a la pestantildea de Identity Inspector

de la columna de la derecha y escribimos dentro de Class el nombre de la vista dellistado de personas PersonasViewController

Finalmente hacemos click sobre el objeto Delegate vamos a la pestantildea de Show

Connections Inspector y arrastramos desde navController hasta el objetoNavigation Controller de la columna de la izquierda Ya tenemos los controladoresy las vistas conectadas

Una vez hecho esto nos queda indicar dentro de nuestro coacutedigo que la aplicacioacuten arranquecon el Navigation Controller para ello escribimos lo siguiente al final del meacutetododidFinishLaunchingWithOptions de la implementacioacuten de la clase delegada

selfwindowrootViewController = selfnavController[selfwindow makeKeyAndVisible]return YES

Ya estaacute listo todo y preparado para compilar Si ejecutamos el proyecto veremos que nosaparece el listado de personas en la vista de tabla

Persistencia

53Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios

61 Creando y leyendo un fichero plist (15 puntos)

En este primer ejercicio vamos a crear nuestra primera aplicacioacuten usando el meacutetodo depersistencia de ficheros La aplicacioacuten mostraraacute en una vista de tabla TableView unlistado de series en la que si seleccionamos una veremos su informacioacuten maacutes importanteen otra vista Comenzaremos creando un fichero de propiedades (plist) siguiendo unaestructura determinada y posteriormente mostraremos en una vista de tabla todos susdatos iexclComenzamos

1) Crear un proyecto usando la plantilla Master-Detail application Lo llamaremosejercicio-plist-ios como identificador de empresa escribiremos esuajtech y como prefijode clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremos UseAutomatic Reference Counting para olvidarnos de la gestioacuten de memoria El resto deopciones las dejaremos sin marcar

2) Crear un archivo de propiedades (plist) con el nombre de series

3) Editar el archivo plist creando la siguiente estructura de datos (5 series miacutenimo)

Plist series

4) Rellenar el archivo plist con datos de series reales

5) Escribir el coacutedigo necesario en UAMasterViewControllerm que recorra el archivoplist que acabamos de crear y muestre por consola los titulos de las series

6) Mostrar en la tabla que hay implementada los tiacutetulos de la serie Para ello deberemosde completar todos los meacutetodos del protocolo del TableView

7) Ejecutar la aplicacioacuten y comprobar que funciona correctamente

Persistencia

54Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

62 Implementar vista de detalle de la serie (05 puntos)

Dentro del proyecto anterior (no hace falta crear uno nuevo) vamos a mostrar toda lainformacioacuten adicional de cada una de las series en una vista de detalle Cuandoseleccionemos una fila de la tabla nos deberaacute aparecer una nueva vista con el resto deinformacioacuten (antildeo director principal sinopsis y duracioacuten)

63 Uso baacutesico de una base de datos SQLite (1 punto)

En este ejercicio vamos a crear la misma aplicacioacuten que en el ejercicio anterior perousando el meacutetodo de persistencia de SQLite Para completar el ejercicio deberemos seguirlos siguientes pasos

1) Crear un proyecto usando la plantilla Master-Detail application lo llamaremosejercicio-sqlite-ios como identificador de empresa escribiremos esuajtech y comoprefijo de clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremosUse Automatic Reference Counting para olvidarnos de la gestioacuten de memoria El restode opciones las dejaremos sin marcar

2) Nos descargamos e instalamos el programa SQLite Database Browser desde estadireccioacuten

3) Disentildear la estructura de la base de datos usando SQLite Database Browser tal y comose muestra en la imagen siguiente

Sqlite series

4) Insertar al menos 5 series en la base de datos usando el programa SQLite DatabaseBrowser

5) Cargar todos los datos de la base de datos desde el meacutetododidFinishLaunchingWithOptions de la clase UAAppDelegate Mostrar por consola losdatos cargados

6) Mostrar los datos cargados en el paso anterior en la vista de tabla UITableView quehay creada en la clase UAMasterViewController

Persistencia

55Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7) Arrancar la aplicacioacuten y comprobar que se muestran las series en la tabla de formacorrecta

Persistencia

56Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7 Persistencia de datos en iOS User Defaults y Core Data

71 Introduccioacuten

En esta sesioacuten veremos como utilizar dos nuevos meacutetodos de persistencia en iOS ambosmuy interesantes y usados muy frecuentemente

bull User Defaults Muy uacutetil para almacenar datos simples y de uso repetido dentro denuestras aplicaciones iOS

bull Core Data Meacutetodo avanzado de almacenamiento de objetos enteros dentro de lamemoria del dispositivo iOS Muy uacutetil para almacenar datos complejos conrelaciones etc

72 User Defaults

Este meacutetodo es muy uacutetil cuando queremos almacenar pequentildeas cantidades de datos comopuntuaciones informacioacuten de usuario el estado de nuestra aplicacioacuten configuracionesetc

Este meacutetodo es el maacutes raacutepido de usar ya que no requiere de una base de datos ni ficherosni ninguna otra capa intermedia Los datos se almacenan directamente en la memoria deldispositivo dentro de un objeto de la clase NSUserDefaults

Para aprender a usar este meacutetodo de almacenamiento de datos vamos a crear unaaplicacioacuten muy simple que lea un nombre y una edad de pantalla y las guarde en unobjeto NSUserDefaults Para ello seguimos los siguientes pasos

bull 1) Comenzamos creando un nuevo proyecto llamado sesion04-ejemplo1 dentrode XCode

bull 2) Abrimos la vista sesion04_ejemplo1ViewController y arrastramos dos TextField un botoacuten y dos labels quedando la vista de la siguiente manera

Persistencia

57Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista de la aplicacioacuten

bull 3) Abrimos el fichero sesion04_ejemplo1ViewControllerh y definimos el Outletdel evento onclick del botoacuten El fichero quedaraacute de la siguiente manera

imports iniciales

interface sesion04_ejemplo1ViewController UIViewController UITextField tfNombreUITextField tfEdad

property(nonatomic retain) IBOutlet UITextField tfNombreproperty(nonatomic retain) IBOutlet UITextField tfEdad

-(IBAction) guardaDatos

end

bull 4) Ahora implementamos el coacutedigo para el meacutetodo del botoacuten dentro desesion04_ejemplo1ViewControllerm

synthesize tfEdadsynthesize tfNombre

-(IBAction) guardaDatos NSLog(Guardamos los datos)

Persistencia

58Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creamos el objeto NSUserDefaultsNSUserDefaults datos = [NSUserDefaults standardUserDefaults]

guardamos el nombre (NSString)[datos setObjecttfNombretext forKeynombre]

guardamos la edad (Integer)[datos setInteger[tfEdadtext integerValue] forKeyedad]

almacenamos los datos en la memoria[datos synchronize]

bull 5) Tenemos que enlazar el botoacuten con el evento que hemos programado en el pasoanterior para ello abrimos la vista sesion04_ejemplo1ViewControllerxib y conla tecla ctlr apretada arrastramos desde el botoacuten hasta Files Owner (columna de laizquierda parte superior) y seleccionamos el meacutetodo que hemos creado(guardaDatos) De esta forma ya tenemos conectado el botoacuten

bull 6) Ahora hacemos lo mismo con los Text Field Tenemos que conectarlos alcontrolador para ello seleccionamos el objeto Files Owner y abrimos la pestantildeade conexiones (la que estaacute maacutes a la derecha en la columna de la derecha) Hacemosclick en tfEdad y arrastramos hasta el Text Field de edad Hacemos lo mismo para eltext field de nombre Ya tenemos todos los elementos de la vista conectados

Conexioacuten de Outlets

bull 7) Arrancamos la aplicacioacuten y la probamos

Persistencia

59Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Aplicacioacuten

bull 8) Ahora vamos a hacer que la aplicacioacuten cargue los datos que se han guardado alarrancar la vista para ello simplemente tenemos que escribir el siguiente coacutedigo en elmeacutetodo (void)viewDidLoad de la clase sesion04_ejemplo1ViewControllerm

- (void)viewDidLoad

[super viewDidLoad]

NSUserDefaults datos = [NSUserDefaults standardUserDefaults]

obtenemos un NSStringNSString nombre = [datos stringForKeynombre]

obtenemos un NSIntegerNSInteger edad = [datos integerForKeyedad]

if (nombre=nil ampamp edad=0)NSLog(Nombre cargado edad dnombreedad)

else

NSLog(Sin datos)

bull 9) Ya podemos arrancar de nuevo la aplicacioacuten y veremos que si escribimos cualquier

Persistencia

60Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

texto en los Text Fields estos se almacenaraacuten dentro del objeto NSUserDefaults Alapagar la aplicacioacuten y volver a arrancarla estos datos se cargaraacuten (apareceraacuten enconsola)

Consola

NotaLos datos permaneceraacuten en nuestro dispositivo hasta que se elimine la aplicacioacuten en ese casotodos los datos guardados en NSUserDefaults se borraraacuten

bull 10) Probar eliminar la aplicacioacuten del dispositivosimulador y volver arrancarComprobamos que los datos se han borrado

Consola

73 Core Data

De todas las formas de persistencia de datos que existen en iOS Core Data es la maacutesapropiada para usar en caso de manejar datos no triviales con relaciones algo maacutescomplejas entre ellos El uso de Core Data dentro de nuestra aplicacioacuten optimiza almaacuteximo el consumo de memoria mejora el tiempo de respuesta y reduce la cantidad decoacutedigo redundante a escribir

Core Data utiliza de fondo el meacutetodo de almacenamiento SQLite visto en la sesioacuten 3 deeste moacutedulo por tanto para explicar el funcionamiento de este meacutetodo de persistenciavamos a utilizar el mismo modelo de datos que usamos en esa sesioacuten Persona -gtDetallePersona

El el siguiente ejemplo haremos la misma aplicacioacuten que hicimos con SQLite perousando este nuevo meacutetodo de persistencia

731 Creando el proyecto

Para empezar tenemos que crearnos un nuevo proyecto en XCode Para ello abrimosXCode y seleccionamos crear una nueva aplicacioacuten basada en ventanas (Window-basedapplication) Hacemos click en Next escribimos como nombre del proyectosesion04-ejemplo2 y seleccionamos Use Core Data

Persistencia

61Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creando un proyecto nuevo

Con esto ya tenemos la estructura baacutesica para empezar a programar Antes de nadahechamos un vistazo raacutepido a toda la estructura de directorios creada y vemos que dentrodel directorio principal tenemos un archivo que se llamasesion04_ejemplo2xcdatamodeld Al hacer click sobre el archivo se nos abre uneditor especial que en principio estaacute vacio y que maacutes adelante utilizaremos para disentildearnuestro modelo de datos

Ahora hechamos un vistazo a la clase delegada sesion04_ejemplo2AppDelegatemvemos que hay unos cuantos meacutetodos nuevos que serviraacuten para configurar el Core DataVamos a explicar los maacutes importantes

bull - (NSManagedObjectModel )managedObjectModel NSManagedObjectModel esla clase que contiene la definicioacuten de cada uno de los objetos o entidades quealmacenamos en la base de datos Normalmente este meacutetodo no lo utilizaremos ya quenosotros vamos a utilizar el editor que hemos visto antes con el que podremos crearnuevas entidades crear sus atributos y relaciones

bull - (NSPersistentStoreCoordinator )persistentStoreCoordinator Aquiacute esdonde configuramos los nombres y las ubicaciones de las bases de datos que se usaraacutenpara almacenar los objetos Cuando un managed object necesite guardar algo pasaraacutepor este meacutetodo

bull - (NSManagedObjectContext )managedObjectContext Esta claseNSManagedObjectContext seraacute la maacutes usada con diferencia de las tres y por tanto la

Persistencia

62Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

maacutes importante La utilizaremos baacutesicamente para obtener objetos insertarlos oborrarlos

732 Definiendo el modelo de datos

En el ejemplo que hicimos en la sesioacuten anterior de SQLite creamos dos tablas una paralos datos baacutesicos de la persona y otra para el detalle Ahora haremos lo mismo perousando Core Data la diferencia principal es que mientras que con SQLite obteniamos soacutelolos datos que necesitaacutebamos de la base de datos aquiacute obtenemos el objeto completo

Para definir nuestro modelo de datos basado en dos objetos (Persona y DetallePersona)abrimos el editor visual haciendo click sobre el ficherosesion04_ejemplo2xcdatamodeld Empezaremos creando una nueva entidad para ellohacemos click sobre el botoacuten Add Entity (+) que se encuentra en la parte inferior dela pantalla A la nueva entidad le pondremos como nombre Persona

Dentro de la seccioacuten de Attributes hacemos click sobre (+) y creamos un nuevoatributo para nuestra entidad le llamamos nombre y le asignamos como tipo StringRealizamos este mismo paso 2 veces maacutes para antildeadir apellidos y edad este uacutetlimode tipo Integer 16

Atributos Persona

Ahora creamos la entidad de DetallePersona de la misma manera que la anterior perocreando los atributos localidad pais direccion cp y telefono Todasde tipo String

Atributos DetallePersona

Finalmente nos queda relacionar de alguacuten modo las dos entidades para ello antildeadimos una

Persistencia

63Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

relacioacuten dentro de la entidad de Persona hacia DetallePersona simplementehaciendo click sobre el botoacuten (+) de la seccioacuten de Relationships A la relacioacuten leponemos como nombre detalle seleccionamos como destino la entidadDetallePersona y seraacute No inverse Este tipo de relacioacuten es uno a uno Por dentroCore Data realizaraacute crearaacute una clave ajena en la tabla de persona que apunte aDetallePersona pero eso a nosotros no nos interesa

Vista general modelo de datos (1)

Apple recomienda que siempre que hagamos una relacioacuten de una entidad a otra hagamostambieacuten la relacioacuten inversa por lo tanto hacemos justo lo anterior pero al reveacutes (deDetallePersona a Persona) con la diferencia que en la columna de Inverse

seleccionamos datosPersona

Ya tenemos las entidades y las relaciones creadas Si hacemos click en el botoacuten deEditor Style en la parte de abajo a la derecha del editor podemos ver de una formamaacutes clara la estructura que hemos disentildeado

Persistencia

64Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista general modelo de datos (2)

733 Probando el modelo

Una vez que tenemos el modelo creado pasamos a probarlo para comprobar que estaacute todocorrecto para ello abrimos la clase delegada (sesion04_ejemplo2AppDelegatem) yantildeadimos el siguiente fragmento de coacutedigo arriba del meacutetodoapplicationDidFinishLaunching

NSManagedObjectContext context = [self managedObjectContext]NSManagedObject persona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

[persona setValueJuan forKeynombre][persona setValueMartinez forKeyapellidos][persona setValue[NSNumber numberWithInt28] forKeyedad]

NSManagedObject detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersonainManagedObjectContextcontext]

[detallePersona setValueCalle Falsa 123 forKeydireccion][detallePersona setValueAlicante forKeylocalidad][detallePersona setValueEspantildea forKeypais][detallePersona setValue965656565 forKeytelefono]

[detallePersona setValuepersona forKeydatosPersona][persona setValuedetallePersona forKeydetalle]

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Persistencia

65Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Con este coacutedigo hemos creado un objeto Persona enlazado con otro objetoDetallePersona iexclComo puedes ver no hemos necesitado nada de coacutedigo SQLPrimero creamos un objeto del tipo NSManagedObjectContext que almacena el conextode Core Data Despueacutes creamos el objeto Persona en siacute con NSManagedObject A esteobjeto le pondremos los valores para cada una de las propiedades definidas en el modeloHacemos lo mismo con DetallePersona Finalmente relacionamos el objetoPersona con el objeto DetallePersona

Compilamos el proyecto comprobamos que todo funciona bien y no salen errores

Para comprobar que los datos se han almacenado correctamente en nuestro dispositivo losmostramos en la consola de la siguiente manera

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

intValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(------------------)

[fetchRequest release]

El coacutedigo de arriba lo escribiremos dentro del meacutetododidFinishLaunchingWithOptions de la clase delegada Lo que hace este coacutedigo esrecorrer todos los objetos almacenados en memoria y mostrarlos por consola Siarrancamos la aplicacioacuten veremos que se muestran todos los datos de los objetosalmacenados Cada vez que arranquemos la aplicacioacuten se mostraraacuten maacutes objetos ya quetambieacuten los creamos

Para listar los objetos usamos la clase NSFetchRequest Esta clase es equivalente aejecutar una sentencia Select de SQL Al objeto NSFetchRequest le asignamos laentidad que queremos listar (SELECT FROM ) en nuestro caso seraacute PersonaEl listado de los objetos se obtiene mediante el meacutetodo executeFetchRequest de laclase NSManagedObjectContext que hemos definido en el bloque anterior de coacutedigoEste meacutetodo devolveraacute un NSArray con todos los objetos NSManagedObject

Persistencia

66Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Salida de consola

NotaSi queremos ver queacute sentencias SQL estaacute ejecutando Core Data por debajo tenemos que activarel flag de debug para ello seleccionamos la opcioacuten Product gt Edit Scheme Enla columna de la izquierda seleccionamos el esquema que estamos utilizando para Debug y en laparte de la derecha en el cuadro de Arguments Passed on Launch antildeadimos 2paraacutemetros -comappleCoreDataSQLDebug y 1

Ahora arrancamos de nuevo la aplicacioacuten y veremos que en la consola apareceraacuten lassentencias SQL utilizadas por Core Data

()

INSERT INTO ZDETALLEPERSONA(Z_PK Z_ENT Z_OPT ZDATOSPERSONA ZTELEFONOZLOCALIDAD ZDIRECCION ZPAIS)VALUES( )CoreData sql SELECT 0 t0Z_PK t0Z_OPT t0ZTELEFONO t0ZLOCALIDADt0ZDIRECCION t0ZPAIS t0ZDATOSPERSONAFROM ZDETALLEPERSONA t0 WHERE t0Z_PK =

()

734 Generando los modelos automaacuteticamente

Generar los modelos a mano directamente desde el editor no es lo maacutes recomendado yaque podemos cometer errores de escritura errores de tipo etc La mejor manera dehacerlo es creando un archivo para cada entidad Esto se puede hacer desde 0 peroXCode tiene un generador de clases que es muy faacutecil de usar y ahorremos bastantetiempo iexclVamos a usarlo

Hacemos click sobre el raiz de nuestro proyecto y seleccionamos New File En lacolumna de la izquierda seleccionamos iOS gt Core Data y en el cuadro de laderecha NSManagedObject subclass Click en next En la siguiente pantallaseleccionamos nuestro modelo de datos sesion04_ejemplo2 y click en next Ahoranos aseguramos de seleccionar las dos entidades que hemos creado anteriormentePersona y DetallePersona click en next Seleccionamos el raiz de nuestroproyecto para guardar los ficheros que se generen y aceptamos

Persistencia

67Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ventana de seleccioacuten de entidad

Ahora veremos que xsource ha creado automaacuteticamente 4 ficheros nuevos dentro denuestro proyecto Personamh y DetallePersonamh con todos los atributosespecificados Estas nuevas clases que heredan de NSManagedObject seraacuten las queutilicemos a partir de ahora

Cambiamos el coacutedigo de la clase delegada por el siguiente

NSManagedObjectContext context = [self managedObjectContext]Persona datosPersona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

datosPersonanombre = JuandatosPersonaapellidos = MartinezdatosPersonaedad = [NSNumber numberWithInt28]

DetallePersona detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersona

inManagedObjectContextcontext]detallePersonadireccion = Calle Falsa 123

Persistencia

68Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

detallePersonalocalidad = AlicantedetallePersonapais = EspantildeadetallePersonatelefono = 965656565

detallePersonadatosPersona = datosPersonadatosPersonadetalle = detallePersona

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Listamos los datosNSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

integerValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(-----------------)[fetchRequest release]

Ejecutamos el proyecto y comprobamos que en la consola aparece lo mismo que antespero ahora tenemos el coacutedigo bastante maacutes claro

735 Creando la vista de tabla

Ahora vamos a mostrar los objetos que antes hemos listado por consola en una vista detabla UITableView Para ello creamos un UITableViewController

Click en New file gt UIViewController subclass En la siguiente pantallanos aseguramos de seleccionar Subclass of UITableViewController de esta formanos apareceraacuten ya todos los meacutetodos de la tabla creados por defecto Guardamos elarchivo con el nombre que queramos por ejemplo PersonasViewController

Abrimos ahora PersonasViewControllerh y antildeadimos 2 atributos a la clase

bull Un NSArray que se seraacute el listado de personas (objetos de la clase Persona)bull Una referencia a NSManagedObjectContext

El fichero PersonasViewControllerh quedaraacute de la siguiente manera

Persistencia

69Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

imports iniciales

interface PersonasViewController UITableViewController NSArray _listaPersonasNSManagedObjectContext _context

property (nonatomic retain) NSArray listaPersonasproperty (nonatomic retain) NSManagedObjectContext context

end

Ahora en el m importamos en el encabezado las clases Personah yDetallePersonah y en el meacutetodo viewDidLoad creamos el array listaPersonas contodos los objetos Persona de forma similar a cuando lo hicimos en el apartado anterior

import PersonasViewControllerh

import Personahimport DetallePersonah

implementation PersonasViewController

synthesize listaPersonas = _listaPersonassynthesize context = _context

En viewDidLoad

- (void)viewDidLoad

[super viewDidLoad]

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContext_context]

[fetchRequest setEntityentity]NSError errorselflistaPersonas = [_context executeFetchRequestfetchRequest

erroramperror]selftitle = Listado Personas[fetchRequest release]

Ahora completamos el resto de meacutetodos heredados de UITableViewController de lamisma forma que hicimos en sesiones anteriores

- (NSInteger)numberOfSectionsInTableView(UITableView )tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection(NSInteger)section

Persistencia

70Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Return the number of rows in the sectionreturn [_listaPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]

if (cell == nil) cell = [[[UITableViewCell alloc]

initWithStyleUITableViewCellStyleSubtitlereuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona p = (Persona )[_listaPersonas objectAtIndexindexPathrow]celltextLabeltext = pnombrecelldetailTextLabeltext = [NSString stringWithFormatd antildeos

[pedad intValue]]

return cell

Ahora para poder mostrar la tabla dentro de nuestra aplicacioacuten debemos asignarla dentrode la clase delegada para ello abrimos la vista MainWindowxib y arrastramos unacontroladora de navegacioacuten Navigation Controller a la lista de objetos de laizquierda La desplegamos y dentro de View Controller en la pestantildea de la derechade Identity Inspector escribimos el nombre de nuestra clase que tiene la vista de latabla PersonasViewController

Pantalla del disentildeador

Ahora abrimos el fichero sesion04_ejemplo2AppDelegateh y creamos un

Persistencia

71Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

UINavigationController dentro de la declaracioacuten de variables dejando el ficherocomo sigue

imports iniciales

interface sesion04_ejemplo2AppDelegate NSObject ltUIApplicationDelegategt

UINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet UINavigationControllernavController

property (nonatomic retain readonly) NSManagedObjectContextmanagedObjectContextproperty (nonatomic retain readonly) NSManagedObjectModelmanagedObjectModelproperty (nonatomic retain readonly) NSPersistentStoreCoordinatorpersistentStoreCoordinator

- (void)saveContext- (NSURL )applicationDocumentsDirectory

end

Dentro del m importamos la clase PersonasViewControllerh y completamos elsynthetize

synthesize navController = _navController

Y por uacuteltimo justo antes de la llamada al meacutetodo makeKeyAndVisible escribimos elsiguiente coacutedigo para asignar el contexto de la clase delegada alPersonasViewController

PersonasViewController root = (PersonasViewController ) [_navControllertopViewController]

rootcontext = [self managedObjectContext][selfwindow addSubview_navControllerview]

Ahora compilamos y ejecutamos nuestro proyecto y si todo ha ido bien deberiacutea de salirla tabla con los datos de las personas

Persistencia

72Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Listado de personas

736 Migraciones de bases de datos con Core Data

Una vez que tenemos disentildeada nuestra base de datos inicial iquestqueacute pasariacutea si queremosmodificarla para antildeadirle un campo maacutes a una tabla iquestQueacute haraacute Core Data para realizar lamigracioacuten de la versioacuten anterior a la actual

Siguiendo con el ejemplo anterior si despueacutes de ejecutar la aplicacioacuten en nuestrodispositivo o simulador modificamos la estructura de la base de datos antildeadiendo unatributo nuevo en la entidad DetallePersona por ejemplo codigoPostal con el tipoInteger 16 veremos que al volver a arrancar la aplicacioacuten nos apareceraacute un error entiempo de ejecucioacuten similar al siguiente

Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 Theoperationcouldnrsquot be completed(Cocoa error 134100) UserInfo=0x6b6c680 metadata=ltCFBasicHash 0x6b68380[0x1337b38]gttype = immutable dict count = 7entries =gt

2 ltCFString 0x6b70320 [0x1337b38]gtcontents =NSStoreModelVersionIdentifiers

=ltCFArray 0x6b706e0 [0x1337b38]gttype = immutable count = 1

values = (0 ltCFString 0x1332cd8 [0x1337b38]gtcontents =

)4 ltCFString 0x6b70350 [0x1337b38]gtcontents =

NSPersistenceFrameworkVersion=

Persistencia

73Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltCFNumber 0x6b6f2c0 [0x1337b38]gtvalue = +386 type =kCFNumberSInt64Type

6 ltCFString 0x6b70380 [0x1337b38]gtcontents =NSStoreModelVersionHashes =

ltCFBasicHash 0x6b707e0 [0x1337b38]gttype = immutable dict count =2entries =gt

1 ltCFString 0x6b70700 [0x1337b38]gtcontents = Persona =ltCFData 0x6b70740

[0x1337b38]gtlength = 32 capacity = 32 bytes =0x1c50e5a379ff0ddc3cda7b82a3934c5e 75649ac3c919beea2 ltCFString 0x6b70720 [0x1337b38]gtcontents = DetallePersona

=ltCFData 0x6b70790[0x1337b38]gtlength = 32 capacity = 32 bytes =0xb7ba2eb498f9a1acdd6630d56f6cd12c e8963dd3e488ba95

7 ltCFString 0x10e3aa8 [0x1337b38]gtcontents = NSStoreUUID =ltCFString 0x6b70510[0x1337b38]gtcontents = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E8 ltCFString 0x10e3948 [0x1337b38]gtcontents = NSStoreType =ltCFString 0x10e3958[0x1337b38]gtcontents = SQLite9 ltCFString 0x6b703b0 [0x1337b38]gtcontents =

_NSAutoVacuumLevel = ltCFString0x6b70830 [0x1337b38]gtcontents = 210 ltFString 0x6b703d0 [0x1337b38]gtcontents =

NSStoreModelVersionHashesVersion=ltCFNumber 0x6b61950 [0x1337b38]gtvalue = +3 type =

kCFNumberSInt32Type reason=The model used to open the store is incompatible with the oneused tocreate the store

metadata = NSPersistenceFrameworkVersion = 386NSStoreModelVersionHashes =

DetallePersona = ltb7ba2eb4 98f9a1ac dd6630d5 6f6cd12c f890007746b16f00

e8963dd3 e488ba95gtPersona = lt1c50e5a3 79ff0ddc 3cda7b82 a3934c5e 5cb4ee4b

4ac98838 75649ac3c919beeagt

NSStoreModelVersionHashesVersion = 3NSStoreModelVersionIdentifiers = (

)NSStoreType = SQLiteNSStoreUUID = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E_NSAutoVacuumLevel = 2

reason = The model used to open the store is incompatible with the

one usedto create the store

Este error es muy comuacuten cuando estamos desarrollando aplicaciones con Core DataBaacutesicamente nos dice que el modelo de base de datos que estaacute intentando cargar dememoria es distinto al que se indica en la aplicacioacuten esto es porque no hemos indicadoninguacuten meacutetodo de migracioacuten Realmente existen dos formas de solucionar este error

Persistencia

74Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Por un lado y soacutelo cuando estemos desarrollando y no tengamos ninguna versioacutende nuestra aplicacioacuten lanzada en la App Store podremos eliminar la base de datosdel dispositivo simulador simplemente borrando la aplicacioacuten de el Tambieacutenpodemos ejecutar el siguiente coacutedigo una soacutela vez [[NSFileManagerdefaultManager] removeItemAtURLstoreURL errornil]

bull En el caso de que estemos realizando una migracioacuten sobre una versioacuten ya publicadaen la App Store el meacutetodo anterior no nos serviriacutea ya que obligariacuteamos al usuario aborrar su aplicacioacuten del dispositivo con lo que todos los datos que tuviera seperderiacutean Esto no es viable por lo que necesitaremos usar otro meacutetodo para realizarla migracioacuten y solucionar el error de una forma maacutes limpia este meacutetodo es el quevamos a comentar a continuacioacuten

XCode soporta Versioning para Core Data esto es que podemos crear versiones de labase de datos de una forma sencilla si seguimos una serie de pasos

1) Antildeadimos las siguientes opciones en forma de NSDictionary a nuestro coacutedigo delpersistentStoreCoordinator

NSDictionary options = [NSDictionary dictionaryWithObjectsAndKeys[NSNumber numberWithBoolYES]NSMigratePersistentStoresAutomaticallyOption[NSNumber numberWithBoolYES]NSInferMappingModelAutomaticallyOptionnil]

Y modificamos el meacutetodo addPersistentStoreWithType pasaacutendole como paraacutemetro eldiccionario options

if ([__persistentStoreCoordinatoraddPersistentStoreWithTypeNSSQLiteStoreTypeconfigurationnil URLstoreURL optionsoptions erroramperror])

Con el coacutedigo anterior indicamos al persistentStoreCoordinator que la migracioacuten enel caso de que se deba de realizar sea de forma automaacutetica

AtencioacutenDeberemos de tener en cuenta que este tipo de migracioacuten que estamos estudiando en este puntosoacutelo es vaacutelida para cambios simples en Core Data lo que Apple llama como lightweightmigration Este tipo de migracioacuten admite una serie de cambios que se indican dentro de ladocumentacioacuten de Apple para cambios maacutes complejos deberemos realizar migracionespersonalizadas y mucho maacutes complejas El documento en donde Apple explica el tipo demigraciones que existen para Core Data y su forma de implementarlas lo podemos consultardesde esta web

2) Ahora deberemos de crear la versioacuten del esquema de Core Data dentro de XCode para

Persistencia

75Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ello seleccionamos el fichero del esquema sesion04_ejemplo2xcdatamodeld yseleccionamos en el menuacute principal Editor gt Add Model Version A la nueva versioacuten lallamaremos sesion04_ejemplo_2 (versioacuten 2) Una vez que hayamos pulsado sobreFinish se nos habraacute creado la nueva versioacuten del esquema dentro de la principal y estaraacutemarcada con un tick la versioacuten anterior

3) Una vez que tenemos creada la nueva versioacuten deberemos modificarla antildeadiendole elnuevo atributo Seleccionamos la versioacuten recieacuten creadasesion04_ejemplo_2xcdatamodel y antildeadimos a la entidad DetallePersona elatributo codigoPostal Atencioacuten Si este atributo lo hemos antildeadido anteriormentedeberemos de borrarlo de la versioacuten anterior y antildeadirlo en la segunda versioacuten

4) Ahora nos queda indicar a Core Data que queremos usar la versioacuten 2 de la base dedatos para nuestra aplicacioacuten Para ello seleccionamos el esquema principal (el raiz)sesion04_ejemplo2xcdatamodeld y en la ventana de la derecha en la pestantildea de FileInspector seleccionamos como Versioacuten actual la versioacuten 2 esto lo hacemos dentro delapartado de Versioned Core Data Model Automaacuteticamente veremos que el siacutembolo detick cambia a la versioacuten 2

5) Ahora ya podemos volver a compilar y veremos que no nos aparece el error y funcionatodo seguacuten lo esperado

Magical RecordExisten varias librerias de coacutedigo libre que facilitan enormemente el uso de Core Data ennuestras aplicaciones iOS Una de ellas es Magical Record Mediante esta libreriacutea podremos usarCore Data sin tener que preocuparnos de todo el proceso de configuracioacuten en las controladorasde la configuracioacuten de migraciones etc Su instalacioacuten en un proyecto de XCode es muy faacutecil yaltamente recomendable ya que proporciona una API muy clara y sencilla de usar

Persistencia

76Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios

81 Usando User Defaults (1 punto)

En este ejercicio vamos a usar las variables de usuario para almacenar y mostrar los datosdel dispositivo que estamos usando y las veces que hemos arrancado la aplicacioacuten

Crear un proyecto nuevo usando la plantilla Single View Application con el nombreejercicio-userdefaults-ios Se debe de ejecutar en iPhone e iPad (Universal) Comoidentificador de empresa escribiremos esuajtech y como prefijo de clase UA Debe detener el ARC activado (desmarcar el resto de opciones)

Ayuda meacutetodos de UIDevicePara obtener los datos del dispositivo usaremos el singleton UIDevice estos son algunos de losmeacutetodos que podemos usar [[UIDevice currentDevice] name] [[UIDevicecurrentDevice] systemName] [[UIDevice currentDevice]systemVersion] [[UIDevice currentDevice] model] [[UIDevicecurrentDevice] localizedModel]

82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)

En este ejercicio disentildearemos un modelo de datos de dos tablas relacionadas entre ellasantildeadiremos datos por coacutedigo y los mostraremos en una vista de tabla La base de datosque vamos a implementar constaraacute de dos tablas por un lado una que contendraacute los tiacutetulosde series y otra que tendraacute los detalles A la primera la llamaremos Serie y la segundaDetalleSerie Para realizar correctamente el ejercicio deberemos seguir los siguientespasos

1) Crear un proyecto nuevo usando la plantilla Master-Detail Application con elnombre ejercicio-coredata-ios Se debe de ejecutar en iPhone como identificador deempresa escribiremos esuajtech y como prefijo de clase UA Debe de tener el ARCactivado y la opcioacuten de Use Core Data (desmarcar el resto de opciones)

2) Disentildear el siguiente modelo de datos

Persistencia

77Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esquema modelo de datos

3) Revisar el fichero UAAppDelegatem y comprobar que tenemos todos los meacutetodosnecesarios para que funcione Core Data en nuestra aplicacioacuten

4) Modificar el fichero UAMasterViewControllerm para que se muestren los tiacutetulos delas series en la tabla

5) Probar la aplicacioacuten antildeadiendo unos tiacutetulos de prueba

83 Migrando datos con Core Data (05 puntos)

Una vez que hemos disentildeado la Base de Datos con Core Data vamos a modificarlaantildeadiendo un atributo maacutes a la tabla de DetalleSerie valoracion

a) iquestQueacute pasa si cambiamos el modelo de datos y arrancamos la aplicacioacuten iquestQueacute errornos da y por queacute

b) iquestCoacutemo podemos evitar este tipo de errores cuando estemos desarrollando iquestY siestamos en produccioacuten con la aplicacioacuten ya a la venta

c) Crea una nueva versioacuten del modelo

d) Modifica el coacutedigo necesario dentro de la clase UAAppDelegate para evitar este tipo deerrores cuando estemos en un entorno de produccioacuten

e) Vuelve a arrancar la aplicacioacuten y comprueba que funciona bien

Persistencia

78Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Persistencia

79Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

  • 1 Persistencia en Android ficheros y SQLite
    • 11 Introduccioacuten
    • 12 Manejo de ficheros tradicionales en Android
      • 121 Apertura de ficheros
      • 122 Ficheros como recursos
      • 123 Operar con ficheros
      • 124 Almacenar datos en la tarjeta de memoria
        • 13 Base de datos SQLite
          • 131 Content Values
          • 132 Cursores
          • 133 Trabajar con bases de datos SQLite
          • 134 La clase SQLiteOpenHelper
          • 135 Crear una base de datos sin SQLiteHelper
          • 136 Realizar una consulta
          • 137 Extraer resultados de un cursor
          • 138 Antildeadir actualizar y borrar filas
              • 2 Ejercicios - Persistencia en Android ficheros y SQLite
                • 21 Uso de ficheros (05 puntos)
                • 22 Persistencia con ficheros (05 puntos)
                • 23 Base de datos SQLiteOpenHelper (05 puntos)
                • 24 Base de datos insercioacuten y borrado (05 puntos)
                • 25 Base de datos probar nuestro adaptador (05 puntos)
                • 26 Base de datos cambios en la base de datos (05 puntos)
                  • 3 Persistencia en Android proveedores de contenidos y SharedPreferences
                    • 31 Shared Preferences
                      • 311 Guardar Shared Preferences
                      • 312 Leer Shared Preferences
                      • 313 Interfaces para Shared Preferences
                      • 314 Definiendo una pantalla de preferencias con un layout en XML
                      • 315 Controles nativos para preferencias
                      • 316 Actividades de preferencias
                      • 317 Shared Preference Change Listeners
                        • 32 Proveedores de contenidos
                          • 321 Proveedores nativos
                          • 322 Proveedores propios crear un nuevo proveedor de contenidos
                          • 323 Proveedores propios crear la interfaz de consultas
                          • 324 Proveedores propios tipo MIME
                          • 325 Proveedores propios registrar el proveedor
                          • 326 Content Resolvers
                          • 327 Otras operaciones con Content Resolvers
                              • 4 Ejercicios - Persistencia en Android proveedores de contenidos y SharedPreferences
                                • 41 Compartir datos entre actividades con Shared Preferences (075 puntos)
                                • 42 Actividad de preferencias (075 puntos)
                                • 43 Proveedor de contenidos propio (075 puntos)
                                • 44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)
                                  • 5 Persistencia de datos en iOS Ficheros y SQLite
                                    • 51 Introduccioacuten
                                    • 52 Ficheros plist (Property Lists)
                                      • 521 Leyendo datos desde ficheros plist
                                      • 522 Escribiendo datos en ficheros plist
                                        • 53 SQLite
                                          • 531 Herramientas disponibles
                                          • 532 Lectura de datos
                                          • 533 Mostrando los datos en una tabla
                                              • 6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios
                                                • 61 Creando y leyendo un fichero plist (15 puntos)
                                                • 62 Implementar vista de detalle de la serie (05 puntos)
                                                • 63 Uso baacutesico de una base de datos SQLite (1 punto)
                                                  • 7 Persistencia de datos en iOS User Defaults y Core Data
                                                    • 71 Introduccioacuten
                                                    • 72 User Defaults
                                                    • 73 Core Data
                                                      • 731 Creando el proyecto
                                                      • 732 Definiendo el modelo de datos
                                                      • 733 Probando el modelo
                                                      • 734 Generando los modelos automaacuteticamente
                                                      • 735 Creando la vista de tabla
                                                      • 736 Migraciones de bases de datos con Core Data
                                                          • 8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios
                                                            • 81 Usando User Defaults (1 punto)
                                                            • 82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)
                                                            • 83 Migrando datos con Core Data (05 puntos)
Page 11: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de

Al hacer una llamada a cualquiera de los dos meacutetodos anteriores se invocaraacuten losmanejadores de evento adecuados Asiacute pues si por ejemplo la base de datos no existieraen disco se ejecutariacutea el coacutedigo de onCreate Si por otra parte la versioacuten de la base dedatos se hubiera modificado se disparariacutea el manejador onUpgrade Lo que sucedeentonces es que al hacer una llamada a getWritableDatabase o getReadableDatabase

se devolveraacute una base de datos nueva actualizada o ya existente seguacuten el caso

135 Crear una base de datos sin SQLiteHelper

Tambieacuten es posible crear y abrir bases de datos sin necesidad de utilizar una subclase deSQLiteHelper Para ello haremos uso del meacutetodo openOrCreateDatabase En este casoseraacute necesario tambieacuten hacer uso del meacutetodo execSQL sobre la instancia de la base dedatos devuelta por el meacutetodo anterior para ejecutar los comandos SQL que permitiraacutencrear las tablas de la base de datos y establecer las relaciones entre ellas El siguientecoacutedigo muestra un ejemplo

private static final String NOMBRE_BASE_DATOS = mibasededatosdbprivate static final String TABLA_BASE_DATOS = tablaPrincipal

private static final String CREAR_BASE_DATOS =create table + TABLA_BASE_DATOS + _id integer primare key

autoincrement +columna_uno text not null)

SQLiteDatabase db

private void crearBaseDatos() db = openOrCreateDatabase(NOMBRE_BASE_DATOS ContextMODE_PRIVATE

null)dbexecSQL(CREAR_BASE_DATOS)

NotaAunque no es necesariamente obligatorio es recomendable que cada tabla incluya un campo detipo clave primaria con autoincremento a modo de iacutendice para cada fila En el caso en el que sedesee compartir la base de datos mediante un proveedor de contenidos un campo uacutenico de estetipo es obligatorio

136 Realizar una consulta

El resultado de cualquier consulta a una base de datos seraacute un objeto de la clase CursorEsto permite a Android manejar los recursos de manera maacutes eficiente de tal forma que enlugar de devolver todos los resultados eacutestos se van proporcionando conforme senecesitan

Para realizar una consulta a una base de datos haremos uso del meacutetodo query de lainstancia correspondiente de la clase SQLiteDatabase Los paraacutemetros que requiere este

Persistencia

11Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

meacutetodo son los siguientes

bull Un booleano opcional que permite indicar si el resultado deberiacutea contener tan soacutelovalores uacutenicos

bull El nombre de la tabla sobre la que realizar la consultabull Un array de cadenas que contenta un listado de las columnas que deben incluirse en el

resultadobull Una claacuteusula where que defina las filas que se deben obtener de la tabla Se puede

utilizar el comodiacuten el cual seraacute reemplazado por los valores que se pasen a traveacutesdel siguiente paraacutemetro

bull Un array de cadenas correspondientes a argumentos de seleccioacuten que reemplazaraacutenlos siacutembolos en la claacuteusula where

bull Una claacuteusula group by que define coacutemo se agruparaacuten las filas obtenidas comoresultado

bull Un filtro having que indique que grupos incluir en el resultado en el caso en el que sehaya hecho uso del paraacutemetro anterior

bull Una cadena que describa la ordenacioacuten que se llevaraacute a cabo sobre las filas obtenidascomo resultado

bull Una cadena opcional para definir un liacutemite en el nuacutemero de resultados a devolver

NotaComo se puede observar la mayoriacutea de estos paraacutemetros hacen referencia al lenguaje SQLTratar este tema queda fuera de los objetivos de este curso por lo que en nuestros ejemplos ledaremos en la mayoriacutea de las ocasiones un valor null a estos paraacutemetros

El siguiente coacutedigo muestra un ejemplo de consultas utilizando diversos paraacutemetros

Devolver los valores de las columnas uno y tres para todas las filas sin duplicadosString[] columnas = new String[] CLAVE_ID CLAVE_COL1 CLAVE_COL3

Cursor todasFilas = dbquery(true TABLA_BASE_DATOS columnas null nullnull null

null null)

Devolvemos los valores de todas las columnas para aquellas filas cuyovalor de la columna 3 sea igual al de una determinada variable Ordenamos las filasseguacuten el valor de la columna 5 Como queremos los valores de todas las columnas le damos al paraacutemetro correspondiente a las columnas a devolver el valor null En este caso no se ha hecho uso del primer paraacutemetro booleano que esoptativoString where = CLAVE_COL3 + = + valorString orden = CLAVE_COL5Cursor miResultado = dbquery(TABLA_BASE_DATOS null where null nullnull orden)

137 Extraer resultados de un cursor

Persistencia

12Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El primer paso para obtener resultados de un cursor seraacute desplazarnos a la posicioacuten apartir de la cual queremos obtener los datos usando cualquiera de los meacutetodosespecificados anteriormente (como por ejemplo moveToFirst o moveToPosition) Unavez hecho esto hacemos uso de meacutetodos get[TIPO] pasando como paraacutemetro el iacutendice dela columna Esto tendraacute como resultado la devolucioacuten del valor para dicha columna de lafila actualmente apuntada por el cursor

String valor = miResultadogetString(indiceColumna)

A continuacioacuten podemos ver un ejemplo maacutes completo en el que se va iterando a lo largode los resultados apuntados por un cursor extrayendo y sumando los valores de unacolumna de flotantes

int COLUMNA_VALORES_FLOTANTES = 2Cursor miResultado = dbquery(Tabla null null null null nullnull)float total = 0

Nos aseguramos de que se haya devuelto al menos una filaif (miResultadomoveToFirst())

Iteramos sobre el cursordo

float valor =miResultadogetFloat(COLUMNA_VALORES_FLOTANTES)

total += valor while (miResultadomoveToNext())

float media = total miResultadogetCount()

NotaDebido a que las columnas de las bases de datos en SQLite son debilmente tipadas podemosrealizar castings cuando sea necesario Por ejemplo los valores guardados como flotantes en labase de datos podriacutean leerse maacutes adelante como cadenas

138 Antildeadir actualizar y borrar filas

La clase SQLiteDatabase proporciona los meacutetodos insert delete y update queencapsulan las instrucciones SQL requeridas para llevar a cabo estas acciones Ademaacutesdisponemos de la posibilidad de utilizar el meacutetodo execSQL el cual nos permite ejecutarcualquier sentencia SQL vaacutelida en nuestras tablas de la base de datos en el caso en el quequeramos llevar a cabo estas (u otras) operaciones manualmente

NotaCada vez que modifiques los valores de la base de datos puedes actualizar un cursor que se puedaver afectado por medio del meacutetodo refreshQuery de la clase Cursor

Para insertar una nueva fila es necesario construir un objeto de la clase ContentValues

y usar su meacutetodo put para proporcionar valor a cada una de sus columnas Para insertar la

Persistencia

13Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

nueva fila pasaremos como paraacutemetro este objeto del tipo ContentValues al meacutetodoinsert de la instancia de la base de datos junto por supuesto con el nombre de la tablaA continuacioacuten se muestra un ejemplo

Crear la nueva filaContentValues nuevaFila = new ContentValues()

Asignamos valores a cada columnanuevaFilaput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas ]

Insertar la nueva fila en la tabladbinsert(TABLA_BASE_DATOS null nuevaFila)

NotaLos ficheros como imaacutegenes o ficheros de audio no se suelen almacenar dentro de tablas en unabase de datos En estos casos se suele optar por almacenar en la base de datos una cadena con laruta al fichero o una URI

La operacioacuten de actualizar una fila tambieacuten puede ser llevada a cabo por medio del usode la clase ContentValues En este caso deberemos llamar al meacutetodo update de lainstancia de la base de datos pasando como paraacutemetro el nombre de la tabla el objetoContentValues y una claacuteusula where que especifique la fila o filas a actualizar tal comose puede ver en el siguiente ejemplo

Creamos el objeto utilizado para definir el contenido a actualizarContentValues valoresActualizar = new ContentValues()

Asignamos valores a las columnas correspondientesvaloresActualizarput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas a actualizar ]

String where = CLAVE_ID + = + idFila

Actualizamos la fila con el iacutendice especificado en la instruccioacutenanteriordbupdate(TABLA_BASE_DATOS valoresActualizar where null)

Por uacuteltimo para borrar una fila simplemente llamaremos al meacutetodo delete de lainstancia de la base de datos especificando en este caso el nombre de la tabla que se veraacuteafectada por la operacioacuten y una claacuteusula where que utilizaremos para especificar las filasque queremos borrar Un ejemplo podriacutea ser el siguiente

dbdelete(TABLA_BASE_DATOS CLAVE_ID + = + idFila null)

Persistencia

14Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

2 Ejercicios - Persistencia en Android ficheros y SQLite

21 Uso de ficheros (05 puntos)

En este ejercicio vamos a crear una aplicacioacuten que muestre un listado de cadenas porpantalla Estas cadenas se almacenaraacuten en un fichero de texto privado para la aplicacioacutenPodremos antildeadir nuevas cadenas a partir de un cuadro de edicioacuten presente en la interfazPartiremos del proyecto Ficheros proporcionado en las plantillas de la aplicacioacuten Debesseguir los siguientes pasos

bull Antildeadir un manejador al botoacuten de la actividad principal para que cada vez que seapulsado se guarde en un fichero de texto El fichero se llamaraacute ficherotxt Al abrirel fichero para escritura se utilizaraacute el paraacutemetro ContextMODE_APPEND con lo quecada nueva cadena se antildeadiraacute al final del fichero en el caso en el que eacuteste ya existieraPara escribir en el fichero utilizaremos el meacutetodo writeBytes del objetoDataOutputStream correspondiente

bull Al pulsar el botoacuten tambieacuten se deberaacute borrar el contenido del elemento EditText (leasignamos a la vista una cadena vaciacutea con el meacutetodo setText)

bull Ejecutamos la aplicacioacuten e introducimos algunas liacuteneas en el fichero Vamos acomprobar ahora que todo ha funcionado correctamente Para ello accedemos alsistema de ficheros de nuestro dispositivo ejecutando el comando adb shell

dentro de la carpeta platform-tools de nuestra carpeta de instalacioacuten del SDK deAndroid

bull Comprueba que se ha creado el fichero ficherotxt en la carpetadatadataesuajtechandroidficherosfiles Examina su contenido ejecutando cat

ficherotxt Si algo no ha salido bien siempre podraacutes eliminar el fichero con rm

ficherotxtbull En la interfaz de la actividad se ha incluido una vista de tipo TextView debajo del

botoacuten Antildeade un manejador para dicha vista de tal forma que cada vez que se hagaclick sobre ella se muestre el contenido del fichero ficherotxt Para ello leeremos elfichero liacutenea a liacutenea antildeadiendo cada una al TextView por medio del meacutetodo append

Persistencia

15Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Interfaz de la aplicacioacuten Ficheros

22 Persistencia con ficheros (05 puntos)

Seguramente habraacutes observado que si abandonas la aplicacioacuten anterior (pulsando el botoacutenBACK del dispositivo) al volver a ejecutarla el contenido del TextView ha desaparecidoDebes volver a pulsar sobre la vista para que se vuelva a mostrar el contenido del ficheroHaz las modificaciones pertinentes para que esto no sea asiacute es decir para que cuandovuelva a mostrarse la actividad tras haber sido eliminada de la memoria se muestre elTextView tal cual se veiacutea antes de abandonar la aplicacioacuten

AvisoEn este ejercicio no se estaacute pidiendo que cargues el contenido del fichero en el TextView nadamaacutes arrancar la aplicacioacuten Puede darse el caso de que lo que esteacute mostrando el TextViewcuando la actividad sea eliminada de memoria no se corresponda con el contenido actualizado delfichero

NotaPara poder completar el ejercicio deberaacutes repasar los manejadores de evento de la primera sesioacutendel moacutedulo de Android relacionados con el ciclo de vida de ejecucioacuten de actividades

23 Base de datos SQLiteOpenHelper (05 puntos)

En las plantillas de la aplicacioacuten se incluye la aplicacioacuten BaseDatos En el proyecto de laaplicacioacuten se incluye el esqueleto de una clase MiAdaptadorBD que tendremos quecompletar Se trata de un patroacuten que nos va a permitir acceder a una base de datos de

Persistencia

16Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

usuarios dentro de la aplicacioacuten sin necesidad de hacer uso de coacutedigo SQL

La clase MiAdaptadorBD incluye a su vez otro patroacuten en este caso implementado comouna subclase de SQLiteOpenHelper Eacuteste nos obliga a definir queacute ocurre cuando la basede datos todaviacutea no existe y debe ser creada y queacute ocurre si ya existe pero debe seractualizada porque ha cambiado de versioacuten Asiacute el SQLiteOpenHelper queimplementemos en este caso MiOpenHelper nos devolveraacute siempre una base de datosseparaacutendonos de la loacutegica encargada de comprobar si la base de datos existe o no

En este primer ejercicio se pide hacer lo siguiente

bull Ejecutar la sentencia de creacioacuten de bases de datos (la tenemos declarada comoconstante de la clase) en el meacutetodo MiOpenHelperonCreate

bull Implementar tambieacuten el meacutetodo onUpgrade Idealmente eacuteste deberiacutea portar las tablasde la versioacuten antigua a la versioacuten nueva copiando todos los datos Nosotros vamos aeliminar directamente la tabla que tenemos con la sentencia SQL DROP TABLE IF

EXISTS + NOMBRE_TABLA y volveremos a crearlabull En el constructor de MiAdaptadorBD debemos obtener en el campo db la base de

datos utilizando MiOpenHelper

24 Base de datos insercioacuten y borrado (05 puntos)

Continuamos trabajando con el proyecto anterior En este ejercicio completaremos elcoacutedigo relacionado con las sentencias de insercioacuten y borrado Para realizar la insercioacutenvamos a hacer uso de un mecanismo que no hemos visto en la sesioacuten de teoriacutea y queconsiste en utilizar una sentencia de insercioacuten SQL precompilada que se completaraacute conlos valores concretos a insertar en la base de datos justo antes de realizar dicha insercioacutenLa ventaja de las sentencias compiladas es que evitan que se produzcan ataques deinyeccioacuten de coacutedigo

Como se puede observar se ha incluido un atributo a la clase que representa la insercioacutenSQL

private static final String INSERT = insert into + NOMBRE_TABLA +(+COLUMNAS[1]+) values ()

En esta sentencia no se indica ninguacuten valor concreto para la columna nombre En el lugaren el que deberiacutean aparecer los valores de dicho campo se ha escrito simplemente unsiacutembolo de interrogacioacuten Tambieacuten se ha antildeadido una instancia de la claseSQLiteStatement como parte de los atributos de la clase

private SQLiteStatement insertStatement

Realizamos los siguientes pasos

bull En el constructor de la clase MiAdaptadorBD llevamos a cabo la compilacioacuten de lasentencia

thisinsertStatement = thisdbcompileStatement(INSERT)

Persistencia

17Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Una vez hecho esto cada vez que deseemos insertar un nuevo usuario en la base dedatos deberemos dar valores concretos a la columna nombre y ejecutar la sentenciaPara ello debemos antildeadir el siguiente coacutedigo dentro del meacutetodo insert de la claseMiAdaptadorBD

thisinsertStatementbindString(1 nombreUsuario)return thisinsertStatementexecuteInsert()

bull Por uacuteltimo completamos el coacutedigo del meacutetodo deleteAll cuyo contenido es eliminara todos los usuarios de la base de datos Para que ademaacutes se devuelva el nuacutemero defilas afectadas podemos insertar la siguiente liacutenea en dicho meacutetodo

return dbdelete(NOMBRE_TABLA null null)

Una vez hechos todos estos cambios podremos utilizar la clase MiAdaptadorBD parahacer operaciones con la base de datos de usuarios de manera transparente

25 Base de datos probar nuestro adaptador (05 puntos)

Antildeade coacutedigo en la actividad Main para eliminar todos los usuarios de la base de datosantildeadir dos cualesquiera y listarlos por medio de la vista de tipo TextView que seencuentra en el layout de dicha actividad

Podemos comprobar mediante la liacutenea de comandos (comando adb shell) que la basede datos ha sido creada Para ello puede ser uacutetil hacer uso de los siguientes comandos

cd datadataesuajtechandroidbasedatosdatabasessqlite3 misusuariosdbsqlitegt schemasqlitegt tablessqlitegt select from usuarios

26 Base de datos cambios en la base de datos (05 puntos)

Ahora vamos a cambiar en la clase MiAdaptadorBD el nombre de la segunda columnaque en lugar de nombre se va a llamar nombres Ejecutamos la aplicacioacuten y comprobamosque sale con una excepcioacuten Comprueba por medio de LogCat cuaacutel ha sido el erroriquestCoacutemo lo podemos solucionar

NotaPista conforme hemos programado la clase MiAdaptadorBD y siguiendo el patroacuten de disentildeode SQLiteOpenHelper es posible arreglar el problema simplemente modificando el valor deun campo

Persistencia

18Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

3 Persistencia en Android proveedores de contenidos ySharedPreferences

31 Shared Preferences

Comenzamos esta sesioacuten hablando de una de las teacutecnicas maacutes simples de persistencia enAndroid junto al uso de ficheros Shared Preferences Se trata de un mecanismo ligeropara almacenar datos basado en pares clave-valor utilizado normalmente para guardarpreferencias u opciones de la aplicacioacuten Tambieacuten puede ser utilizado para almacenar elestado de la interfaz graacutefica para contemplar el caso de que nuestra actividad debafinalizar abruptamente su ejecucioacuten Otro posible uso es el de compartir informacioacutenentre componentes de una misma aplicacioacuten

NotaLos Shared Preferences nunca son compartidos entre diferentes aplicaciones

Este meacutetodo se basa en el uso de la clase SharedPreferences Es posible utilizarla paraguardar datos en muy diversos formatos booleanos cadenas flotantes y enteros

311 Guardar Shared Preferences

Para crear o modificar un Shared Preference llamamos al meacutetodogetSharedPreferences del contexto de la aplicacioacuten pasando como paraacutemetro elidentificador del conjunto de valores de preferencias con el que queremos trabajar Pararealizar la modificacioacuten del valor de un Shared Preference hacemos uso de la claseSharedPreferencesEditor Para obtener el objeto editor invocamos el meacutetodo edit

del objeto Shared Preferences que queremos modificar Los cambios se guardan medianteel meacutetodo commit Veamos un ejemplo

int modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences = getSharedPreferences(Mispreferencias modo)

Obtenemos un editor para modificar las preferenciasSharedPreferencesEditor editor = misSharedPreferencesedit()

Guardamos nuevos valores en el objeto SharedPreferenceseditorputBoolean(isTruetrue)editorputFloat(unFloat10)editorputInt(numeroEntero12)editorputLong(otroNumero2)editorputString(unaCadenavalor)

Guardamos los cambioseditorcommit()

Persistencia

19Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

312 Leer Shared Preferences

Para leer Shared Preferences utilizamos el meacutetodo getSharedPreferences tal como seha comentado anteriormente Pasamos como paraacutemetro el identificador del conjunto deShared Preferences al que deseamos acceder Una vez hecho esto ya podemos hacer usode meacutetodos de tipo get para acceder a preferencias individuales Hay diferentes meacutetodosget para diferentes tipos de datos Cada uno de ellos requiere como paraacutemetro la claveque identifica la preferencia cuyo valor deseamos obtener y un valor por defecto el cualse usaraacute en el caso concreto en el que no existiera la preferencia con la clave pasada comoprimer paraacutemetro Podemos ver un ejemplo a continuacioacuten

public static String MIS_PREFS = MIS_PREFS

public void cargarPreferences() Obtener las preferencias almacenadasint modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences =

getSharedPreferences(MIS_PREFS modo)

boolean isTrue = misSharedPreferencesgetBoolean(isTruefalse)float unFloat = misSharedPreferencesgetFloat(unFloat0)int numeroEntero = misSharedPreferencesgetInt(numeroEntero0)long otroNumero = misSharedPreferencesgetLong(otroNumero0)String unaCadena = misSharedPreferencesgetString(unaCadena)

313 Interfaces para Shared Preferences

Android proporciona una plataforma basada en XML para la creacioacuten de interfacesgraacuteficas para el manejo de preferencias Estas interfaces tendraacuten un aspecto similar al delas del resto de aplicaciones del sistema Al utilizarla nos estaremos asegurando de quenuestras actividades para el manejo de preferencias seraacuten consistentes con las del resto deaplicaciones del sistema De esta forma los usuarios estaraacuten familiarizados con el manejode esta parte de nuestra aplicacioacuten

La plataforma consiste en tres elementos

bull Layout de la actividad de preferencias se trata de un archivo XML que definiraacute lainterfaz graacutefica de la actividad que muestre los controles para modificar los valores delas preferencias Permite especificar las vistas a mostrar los posibles valores que sepueden introducir y a queacute clave de Shared Preferences se corresponde cada vista

bull Actividad de preferencias una subclase de PreferenceActivity que secorresponderaacute con la pantalla de preferencias de nuestra aplicacioacuten

bull Shared Preference Change Listener una implementacioacuten de la claseonSharedPreferenceChangeListener que actuaraacute en el caso en el que cambie elvalor de alguna Shared Preference

314 Definiendo una pantalla de preferencias con un layout en XML

Persistencia

20Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esta es sin duda la parte maacutes importante de una actividad de preferencias Se trata de unarchivo XML utilizado para definir varios aspectos de dicha actividad Al contrario queen el caso de los archivos de layout para interfaces graacuteficas que vimos en la sesioacutencorrespondiente del moacutedulo de Android estos archivos de layout se guardan en la carpetaresxml de los recursos de la aplicacioacuten

Aunque conceptualmente estos layouts son similares a los utilizados para definirinterfaces graacuteficas los layouts de actividades de preferencias utilizan un conjuntoespecializado de componentes especiacuteficos para este tipo de actividades Estaacuten pensadospara proporcionar interfaces con un aspecto similar al del resto de actividades depreferencias del sistema Estos controles se describen en maacutes detalle en la siguienteseccioacuten

Los layout de preferencias contendraacuten un elemento PreferenceScreen

ltxml version=10 encoding=utf-8gtltPreferenceScreen

xmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceScreengt

Se pueden antildeadir maacutes elementos de este tipo si se desea Al hacerlo eacutestos serepresentaraacuten como un elemento seleccionable en un listado que mostraraacute una nuevaventana de preferencias en caso de que sea seleccionado

Dentro de cada elemento PreferenceScreen podemos incluir tantos elementos de tipoPreferenceCategory y elementos especiacuteficos para antildeadir controles como se desee Loselementos PreferenceCategory son utilizados para dividir cada pantalla de preferenciasen subcategoriacuteas mediante una barra de tiacutetulo Su sintaxis es la siguiente

ltPreferenceCategoryandroidtitle=My Preference Categorygt

ltPreferenceCategorygt

Una vez dividida la pantalla en subcategoriacuteas tan soacutelo quedariacutea antildeadir los elementoscorrespondientes a los controles especiacuteficos que veremos en la siguiente seccioacuten Losatributos de los elementos XML para estos controles pueden variar aunque existe unconjunto de atributos comunes

bull androidkey la clave de Shared Preference que se usaraacute para guardar el valor delcontrol

bull androidtitle texto a mostrar para describir la preferenciabull androidsummary una descripcioacuten maacutes larga a mostrar debajo del texto definido con

el campo anteriorbull androiddefaultValue el valor por defecto que se mostraraacute inicialmente y tambieacuten

el que finalmente se guardaraacute si ninguacuten otro valor fue asignado a este campo

El siguiente listado muestra una pantalla de preferencias muy simple con una uacutenicacategoriacutea y un uacutenico control (un check box)

ltxml version=10 encoding=utf-8gt

Persistencia

21Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltPreferenceScreenxmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceCategory

androidtitle=Mi categoriacuteagtltCheckBoxPreference

androidkey=MI_CHECK_BOXandroidtitle=Preferencia Check Boxandroidsummary=Descripcioacuten de la preferencia

Check BoxandroiddefaultValue=true

gtltPreferenceCategorygt

ltPreferenceScreengt

Esta pantalla de preferencias se mostrariacutea de la siguiente forma

Ejemplo sencillo de ventana de preferencias

315 Controles nativos para preferencias

Android incluye diferentes controles que podemos antildeadir a nuestras ventanas depreferencias por medio de los elementos XML que se indican a continuacioacuten

bull CheckBoxPreference un check box estaacutendar que puede ser utilizado parapreferencias de tipo booleano

bull EditTextPreference permite al usuario introducir una cadena como valor para unapreferencia Al seleccionar el texto se mostraraacute un diaacutelogo con el que introducir elnuevo valor

bull ListPreference el equivalente a un Spinner Seleccionar este elemento de lainterfaz mostraraacute un diaacutelogo con las diferentes opciones que es posible seleccionar Seutilizan arrays para asociar valores y textos a las opciones de la lista

bull RingtonePreference un tipo especiacutefico de preferencia de tipo listado que permiteseleccionar entre diferentes tonos de teleacutefono Esta opcioacuten es particularmente uacutetil en

Persistencia

22Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

el caso en el que se esteacute creando una pantalla de preferencias para configurar lasopciones de notificacioacuten de una aplicacioacuten o componente de la misma

Tambieacuten es posible crear nuestros propios controles personalizados definiendo unasubclase de Preference (o cualquiera de sus subclases) Se puede encontrar maacutesinformacioacuten en la documentacioacuten de AndroidhttpdeveloperandroidcomreferenceandroidpreferencePreferencehtml

316 Actividades de preferencias

Para mostrar una pantalla de preferencias debemos definir una subclase dePreferenceActivity

public class MisPreferencias extends PreferenceActivity

La interfaz graacutefica de la interfaz se puede crear a partir del layout en el meacutetodo onCreatemediante una llamada a addPreferencesFromResource

Overridepublic void onCreate(Bundle savedInstaceState)

superonCreate(savedInstanceState)addPreferencesFromResource(Rxmlmilayout)

Como en el caso de cualquier otra actividad la actividad de preferencias que hayamosdefinido debe ser incluida en el Manifest de la aplicacioacuten

ltactivity androidname=MisPreferenciasandroidlabel=Mis preferenciasgt

ltactivitygt

Eso es todo lo que necesitamos para crear este tipo de actividades y mostrar una ventanacon las preferencias definidas en el layout Esta actividad puede ser iniciada comocualquier otra mediante un Intent ya sea a traveacutes de una llamada a startActivity obien a startActivityForResult

Intent i = new Intent(this MisPreferenciasclass)startActivityForResult(i MOSTRAR_PREFERENCIAS)

Los valores para las diferentes preferencias de la actividad son almacenadas en elcontexto de la aplicacioacuten Esto permite a cualquier componente de dicha aplicacioacutenincluyendo a cualquier actividad y servicio de la misma acceder a los valores tal como semuestra en el siguiente coacutedigo de ejemplo

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto) PENDIENTE hacer uso de meacutetodos get para obtener los valores

317 Shared Preference Change Listeners

Persistencia

23Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El uacuteltimo elemento que nos queda por examinar en esta plataforma de Shared Preferenceses la interfaz onSharedPreferenceChangeListener que es utilizada para invocar unevento cada vez que una preferencia es antildeadida eliminada o modificada Para registrarListeners usamos un coacutedigo como el que se muestra a continuacioacuten en el que la parte maacutesimportante es sin duda el meacutetodo onSharedPreferenceChanged dentro del cualdeberemos determinar queacute preferencia ha cambiado de estado a partir de sus paraacutemetrospara llevar a cabo las acciones oportunas

public class MiActividad extends Activity implementsOnSharedPreferenceChangeListener

Overridepublic void onCreate(Bundle SavedInstanceState)

Registramos este objetoOnSharedPreferenceChangeListener

Context contexto = getApplicationContext()SharedPreferences prefs =

PreferenceManagergetDefaultSharedPreferences(contexto)prefsregisterOnSharedPreferenceChangeListener(this)

public void onSharedPreferenceChanged(SharedPreferences prefsString clave)

PENDIENTE comprobar queacute clave concreta ha cambiado ysu nuevo valor

para modificar la interfaz de una actividad o elcomportamiento de

alguacuten componente

Otra forma maacutes sencilla de controlar los cambios en las preferencias es registrar unlistener en el constructor de la actividad principal o aquella que haga uso de los valores depreferencias de la forma

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto)

mListener = new OnSharedPreferenceChangeListener() public void onSharedPreferenceChanged(SharedPreferences

sharedPreferences String key)

if( keyequals(MICLAVE) )boolean valor =

sharedPreferencesgetBoolean(MICLAVE false)

prefsregisterOnSharedPreferenceChangeListener(mListener)

NotaEn este segundo ejemplo mListener es una variable miembro de la clase Para este caso esnecesario registrar el listener de esta forma porque sino el garbage collector lo eliminariacutea (debidoa particularidades en la programacioacuten de los listeners asociados a las SharedPreferences)

Persistencia

24Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

32 Proveedores de contenidos

Los proveedores de contenidos o ContentProvider proporcionan una interfaz parapublicar y consumir datos identificando la fuente de datos con una direccioacuten URI queempieza por content Son una forma maacutes estaacutendar que los adaptadores (como el quevimos en la sesioacuten anterior en la seccioacuten dedicada a SQLite) de desacoplar la capa deaplicacioacuten de la capa de datos

Podemos encontrar dos tipos principales de proveedores de contenidos en Android Losproveedores nativos son proveedores que el propio sistema nos proporciona para poderacceder a datos del dispostivo incluyendo audio viacutedeo imaacutegenes informacioacuten personaletc Por otra parte tambieacuten es posible que creemos nuestros propios proveedores con talde permitir a nuestra aplicacioacuten compartir datos con el resto del sistema En esta sesioacutenhablaremos de ambos tipos

321 Proveedores nativos

Android incluye una serie de proveedores de contenidos que nos pueden proporcionarinformacioacuten de diferentes tipos informacioacuten sobre nuestros contactos informacioacuten delcalendario e incluso ficheros multimedia Ejemplos de estos proveedores de contenidosnativos son el Browser CallLog ContactsContract MediaStore Settings y elUserDictionary Para poder hacer uso de estos proveedores de contenidos y acceder alos datos que nos proporcionan debemos antildeadir los permisos adecuados al ficheroAndroidManifestxml Por ejemplo para acceder al listiacuten telefoacutenico podemos antildeadir elpermiso correspondiente de la siguiente forma

ltuses-sdk androidminSdkVersion=8 gtltuses-permission androidname=androidpermissionREAD_CONTACTSgt

ltmanifestgt

Para obtener informacioacuten de un ContentProvider debemos hacer uso del meacutetodo query

de la clase ContentResolver El primero de los paraacutemetros de dicho meacutetodo seraacute la URIdel proveedor de contenidos al que deseamos acceder Este meacutetodo devuelve un cursorque nos permitiraacute iterar entre los resultados tal como se vio en la sesioacuten anterior en elapartado de cursores

ContentResolverquery(Uri uriString[] projectionString selectionString[] selectionArgsString sortOrder)

Por ejemplo la siguiente llamada nos proporcionaraacute un cursor que nos permitiraacute acceder atodos los contactos de nuestro teleacutefono moacutevil Para ello hemos hecho uso de la constante

Persistencia

25Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ContactsContractContactsCONTENT_URI que almacena la URI del proveedor decontenidos correspondiente Al resto de paraacutemetros se le ha asignado el valor null

ContentResolver cr = getContentResolver()Cursor cursor = crquery(ContactsContractContactsCONTENT_URI

null null null null)

NotaEn versiones anteriores a Android 20 las estructuras de datos de los contactos son diferentes y nose accede a esta URI

Una vez obtenido el cursor es posible mapear sus campos con alguacuten componente de lainterfaz graacutefica de la actividad De esta forma cualquier cambio que se produzca en elcursor se reflejaraacute automaacuteticamente en el componente graacutefico sin que sea necesario queprogramemos el coacutedigo que lo refresque Para esto tenemos que llamar al meacutetodosetNotificationUri del cursor con lo cual nos aseguraremos de que los listeners seraacutennotificados cuando se produzca alguacuten cambio en los datos referenciados por dicho cursor

cursorsetNotificationUri(crContactsContractContactsCONTENT_URI)

Para asignar un adaptador a una lista de la interfaz graacutefica de la actividad maacutesconcretamente una ListView utilizamos el meacutetodo setAdapter(Adapter)

ListView lv = (ListView)findViewById(RidListView01)SimpleCursorAdapter adapter = new SimpleCursorAdapter(

getApplicationContext()Rlayouttextviewlayoutcursornew String[]

ContactsContractContacts_IDContactsContractContactsDISPLAY_NAME

new int[]RidTextView1RidTextView2)

lvsetAdapter(adapter)

En este ejemplo los identificadores RidTextView1 y RidTextView2 se correspondencon views del layout que define cada fila de la ListView como se puede ver en elarchivo textviewlayoutxml que tendriacuteamos que crear

ltxml version=10 encoding=utf-8gt

ltLinearLayout xmlnsandroid=httpschemasandroidcomapkresandroidandroidorientation=horizontalandroidlayout_width=fill_parentandroidlayout_height=wrap_contentgt

ltTextView androidid=+idTextView1androidtextStyle=bold

Persistencia

26Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

androidems=2androidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltTextView androidid=+idTextView2androidtextStyle=boldandroidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltLinearLayoutgt

La lista en siacute (identificada por RidListView01 en el presente ejemplo) estariacutea en otroXML layout por ejemplo en mainxml

Se debe tener en cuenta que en el caso de este ejemplo si quisieacuteramos obtener losnuacutemeros de teleacutefono de cada uno de nuestros contactos tendriacuteamos que recorrer el cursorobtenido a partir del proveedor de contenidos y por cada persona en nuestra agenda crearun nuevo cursor que recorriera los teleacutefonos ya que una persona puede tener asignadosvarios teleacutefonos

Puedes acceder a un listado completo de los proveedores de contenidos nativos existentesen Android consultando su manual de desarrollo Concretamente leyendo la seccioacutendedicada al paquete androidprovider enhttpdeveloperandroidcomreferenceandroidproviderpackage-summaryhtml

322 Proveedores propios crear un nuevo proveedor de contenidos

Para acceder a nuestras fuentes de datos propias de manera estaacutendar nos interesaimplementar nuestros propios ContentProvider Gracias a que proporcionamos nuestrosdatos a partir de una URI quien use nuestro proveedor de contenidos no deberaacutepreocuparse de doacutende provienen los datos indicados por dicha URI podriacutean provenir deficheros locales en la tarjeta de memoria de un servidor en Internet o de una base dedatos SQLite Dada una URI nuestro proveedor de contenidos deberaacute proporcionar en suinterfaz los meacutetodos necesarios para realizar las operaciones baacutesicas en una base de datosinsercioacuten lectura actualizacioacuten y borrado

Para crear un nuevo proveedor de contenidos definimos una subclase deContentProvider Utilizaremos una sobrecarga del meacutetodo onCreate para crear einicializar la fuente de datos que queramos hacer puacuteblica a traveacutes de dicho proveedor Unesqueleto de subclase de ContentProvide podriacutea ser el siguiente

public class MiProveedor extends ContentProvider

Overridepublic boolean onCreate()

Inicializar la base de datosreturn true

Dentro de la clase deberemos crear un atributo puacuteblico y estaacutetico de nombreCONTENT_URI en el cual almacenaremos el URI completo del proveedor que estamos

Persistencia

27Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creando Este URI debe ser uacutenico por lo que una buena idea puede ser tomar como baseel nombre de paquete de nuestra aplicacioacuten La forma general de un URI es la siguiente

content[NOMBRE_PAQUETE]provider[NOMBRE_APLICACION][RUTA_DATOS]

Por ejemplo

contentesuajtechandroidprovidermiaplicacionelementos

En general (y por simplicidad) no es necesario antildeadir la cadena provider al nombre delpaquete por lo que podemos utilizar directamente el nombre del paquete seguido de laruta de datos de la forma

contentesuajtechandroidmiaplicacionelementos

Estos URIs se pueden presentar en dos formas La URI anterior representa una consultadirigida a obtener todos los valores de ese tipo (en este caso todos los elementos) Si a laURI anterior antildeadimos un sufijo [NUMERO_FILA] estamos representando una consultadestinada a obtener un uacutenico elemento (por ejemplo el quinto elemento en el siguienteejemplo)

contentesuajtechandroidprovidermiaplicacionelementos5

NotaSe considera una buena praacutectica de programacioacuten el proporcionar acceso a nuestro proveedor decontenidos utilizando cualquiera de estas dos formas

La forma maacutes simple de determinar cuaacutel de las dos formas anteriores de URI se utilizoacutepara hacer una peticioacuten a nuestro proveedor de contenidos es mediante un Uri MatcherCrearemos y configuramos un elemento Uri Matcher para analizar una URI y determinara cuaacutel de las dos formas se corresponde En el siguiente coacutedigo se muestra el coacutedigobaacutesico que deberemos emplear para implementar este patroacuten

public class MiProveedor extends ContentProvider

private static final String miURI =contentesuajtechandroidprovidermiaplicacionelementos

public static final Uri CONTENT_URI = Uriparse(miUri)

Creamos las constantes que nos van a permitir diferenciar entre los dos tipos distintos de peticiones a partir de la URIprivate static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

Inicializamos el objeto UriMatcher Una URI que termine en elementos se corresponderaacute con una consulta para todas las filas mientras que una URI con el sufijo elementos[FILA] representaraacute una uacutenica filastatic

uriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidprovidermiaplicacion

elementos TODAS_FILAS)

Persistencia

28Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

uriMatcheraddURI(esuajtechandroidprovidermiaplicacionelementos UNA_FILA)

Overridepublic boolean onCreate()

PENDIENTE inicializar la base de datosreturn true

Esta misma teacutecnica se puede emplear para proporcionar diferentes alternativas de URIpara diferentes subconjuntos de datos como por ejemplo diferentes tablas en una base dedatos a traveacutes del mismo proveedor de contenidos

323 Proveedores propios crear la interfaz de consultas

Para permitir realizar operaciones con los datos publicados a traveacutes de nuestro proveedorde contenidos deberemos implementar los meacutetodos delete insert update y query

para el borrado insercioacuten actualizacioacuten y realizacioacuten de consultas respectivamenteEstos meacutetodos son la interfaz estaacutendar utilizada con proveedores de contenidos de formaque para compartir datos entre aplicaciones no se tendraacuten interfaces distintos paradiferentes fuentes de datos

Lo maacutes habitual suele ser implementar un proveedor de contenidos como un mecanismopara permitir el acceso a una base de datos SQLite privada a una aplicacioacuten aunque atraveacutes de estos meacutetodos indicados se podriacutea en principio acceder a cualquier fuente dedatos

El siguiente coacutedigo extiende el ejemplo anterior (MiProveedor) y muestra el esqueleto deestos meacutetodos dentro de una subclase de ContentProvider Obseacutervese que se hace usodel objeto UriMatcher para determinar que tipo de consulta se estaacute realizando

Overridepublic Cursor query(Uri uri

String[] projectionString selectionString[] selectionArgsString sort)

Si se trata de una consulta para obtener una uacutenica filalimitamos

los resultados a obtener de la fuente de datos por medio de una claacuteusula whereswitch(UriMatchermatch(uri))

case UNA_FILA PENDIENTE modifica la sentencia SELECT

mediante una claacuteusula where obteniendo el identificador de fila

como numeroFila = urigetPathSegments()get(1))

return null

Override

Persistencia

29Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public Uri insert(Uri _uri ContentValues _valores) long idFila = [ Antildeadir un nuevo elemento ]

Devolvemos la URI del elemento recieacuten antildeadidoif (idFila gt 0)

return ContentUriswithAppendendId(CONTENT_URI idFila)

throw new SQLException(No se pudo antildeadir un nuevo elemento a +_uri)

Overridepublic int delete(Uri uri String where String[] whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

Overridepublic int update(Uri uri ContentValues valores String where String[]whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

324 Proveedores propios tipo MIME

El uacuteltimo paso para crear un proveedor de contenidos es definir el tipo MIME queidentifica el tipo de datos que devuelve el proveedor Debemos sobrecargar el meacutetodogetType para que devuelva una cadena que identifique nuestro tipo de datos de manerauacutenica Se deberiacutean definir dos tipos posibles uno para el caso de una uacutenica fila y otro parael caso de devolver todos los resultados Se debe utilizar una sintaxis similar a la delsiguiente ejemplo

bull Un uacutenico elementovndesuajtechandroidmiaplicacioncursoritemmiproveedorcontent

bull Todos los elementosvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

Como se puede observar la cadena comienza siempre por vnd A continuacioacutentendriacuteamos el nombre del paquete de nuestra aplicacioacuten Lo siguiente es cursor ya quenuestro proveedor de contenidos estaacute devolviendo datos de tipo Cursor Justo antes de labarra pondremos item en el caso del tipo MIME para un uacutenico elemento y dir para elcaso del tipo MIME para todos los elementos Finalmente tras la barra pondremos elnombre de nuestra clase (en minuacutesculas) seguido de content En el siguiente coacutedigo deejemplo se muestra coacutemo sobrecargar getType seguacuten la URI

Persistencia

30Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Overridepublic String getType(Uri _uri)

switch (uriMatchermatch(_uri)) case TODAS_FILAS

returnvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

case UNA_FILAreturn

vndesuajtechandroidmiaplicacioncursoritemmiproveedorcontentdefault

throw new IllegalArgumentException(URI nosoportada + _uri)

325 Proveedores propios registrar el proveedor

No debemos olvidar incorporar nuestro proveedor de contenidos al Manifest de laaplicacioacuten una vez que lo hemos completado Esto se haraacute por medio del elementoprovider cuyo atributo authorities podremos utilizar para especificar la URI base talcomo se puede ver en el siguiente ejemplo

ltprovider androidname=MiProveedorandroidauthorities=esuajtechandroidmiaplicaciongt

326 Content Resolvers

Aunque en la seccioacuten de proveedores nativos ya se ha dado alguacuten ejemplo de coacutemo haceruso de un Content Resolver para realizar una consulta ahora que hemos podido observaren detalle la estructura de un proveedor de contenidos propio es un buen momento paraver en detalle coacutemo realizar cada una de las operaciones que eacutestos permiten

El contexto de una aplicacioacuten incluye siempre una instancia de la claseContentResolver a la cual se puede acceder mediante el meacutetodo getContentResolver

ContentResolver cr = getContentResolver()

Esta clase incorpora diversos meacutetodos para realizar consultas a proveedores decontenidos Cada uno de estos meacutetodos acepta como paraacutemetro una URI que indica conqueacute proveedor de contenidos se desea interactuar

Las consultas realizadas sobre un proveedor de contenidos recuerdan a las consultasrealizadas sobre bases de datos Los resultados de las consultas se devuelven comoobjetos de la clase Cursor Se pueden extraer valores del cursor de la misma forma en laque se explicoacute en el apartado de bases de datos de la sesioacuten anterior El meacutetodo query dela clase ContentResolver acepta los siguientes paraacutemetros

bull La URI del proveedor del contenidos al que se desea realizar la consultabull Una proyeccioacuten que enumera las columnas que se quieren incluir en los resultados

obtenidosbull Una claacuteusula where que define las filas a obtener Se pueden utilizar comodines que

Persistencia

31Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

se reemplazaraacuten por valores incluidos en el siguiente paraacutemetrobull Una matriz de cadenas que se pueden utilizar para reemplazar los comodines en la

claacuteusula where anteriorbull Una cadena que describa la ordenacioacuten de los elementos devueltos

El siguiente coacutedigo muestra un ejemplo

ContentResolver cr = getContentResolver() Devolver todas las filasCursor todasFilas = crquery(MiProveedorCONTENT_URI null null nullnull) Devolver todas columnas de las filas para las que la columna 3 tiene un valor determinado ordenadas seguacuten el valor de la columna 5String where = COL3 + = + valorString orden = COL5Cursor algunasFilas = crquery(MiProveedorCONTENT_URI null where nullorden)

327 Otras operaciones con Content Resolvers

Para realizar otro tipo de operaciones con los datos de un proveedor de contenidosharemos uso de los meacutetodos delete update e insert de un objeto ContentResolver

Con respecto a la insercioacuten la clase ContentResolver proporciona en su interfaz dosmeacutetodos diferentes insert y bulkInsert Ambos aceptan como paraacutemetro la URI delproveedor de contenidos pero mientras que el primer meacutetodo tan solo toma comoparaacutemetro un objeto de la clase ContentValues (ya vistos en la seccioacuten de bases dedatos) el segundo toma como paraacutemetro una matriz de elementos de este tipo El meacutetodoinsert devolveraacute la URI del elemento recieacuten antildeadido mientras que bulkInsert

devolveraacute el nuacutemero de filas correctamente insertadas

Obtener el Content ResolverContentResolver cr = getContentResolver()

Crear una nueva fila de valores a insertarContentValues nuevosValores = new ContentValues()

Asignar valores a cada columnanuevosValoresput(NOMBRE_COLUMNA nuevoValor)[ Repetir para cada columna ]

Uri miFilaUri = crinsert(MiProveedorCONTENT_URI nuevosValores)

Insertamos ahora una matriz de nuevas filasContentValues[] arrayValores = new ContentValues[5] PENDIENTE rellenar el array con los valores correspondientesint count = crbulkInsert(MiProveedorCONTENT_URI arrayValores)

Para el caso del borrado hacemos uso del meacutetodo delete del Content Resolver pasandocomo paraacutemetro la URI de la fila que deseamos eliminar de la fuente de datos Otraposibilidad es incluir una claacuteusula where para eliminar muacuteltiples filas Ambas teacutecnicas semuestran en el siguiente ejemplo

ContentResolver cr = getContentResolver()

Persistencia

32Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Eliminar una fila especiacuteficacrdelete(miFilaUri null null) Eliminar las primeras cinco filasString where = _id lt 5crdelete(MiProveedorCONTENT_URI where null)

Una actualizacioacuten se lleva a cabo mediante el meacutetodo update del objeto ContentResolver Este meacutetodo toma como paraacutemetro la URI del proveedor de contenidosobjetivo un objeto de la clase ContentValues que empareja nombres de columna convalores actualizados y una claacuteusula where que indica queacute filas deben ser actualizadas Elresultado de la actualizacioacuten seraacute que en cada fila en la que se cumpla la condicioacuten de laclaacuteusula where se cambiaraacuten los valores de las columnas especificados por el objeto de laclase ContentValues tambieacuten se devolveraacute el nuacutemero de filas correctamenteactualizadas A continuacioacuten se muestra un ejemplo

ContentValues nuevosValores = new ContentValues()

Utilizamos el objeto ContentValues para especificar en queacute columnas queremos que se produzca un cambio y cuaacutel debe ser el nuevo valor de dichas columnasnuevosValuesput(NOMBRE_COLUMNA nuevoValor)

Aplicamos los cambios a las primeras cinco filasString where = _id lt 5

getContentResolver()update(MiProveedorCONTENT_URI nuevosValores wherenull)

Persistencia

33Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

4 Ejercicios - Persistencia en Android proveedores de contenidos ySharedPreferences

41 Compartir datos entre actividades con Shared Preferences (075 puntos)

Descarga de las plantillas el proyecto Dni Dicho proyecto estaacute compuesto de dosactividades Formulario y Resumen El objetivo del ejercicio es conseguir que al pulsar elbotoacuten Siguiente en la actividad Formulario se muestre en la actividad Resumen cuaacutelesfueron los datos introducidos

En este ejercicio vamos a hacer uso de SharedPreferences para pasar los datos de unaactividad a la siguiente Al pulsar en Siguiente en Formulario deberemos guardar el valorde todos los campos mediante esta plataforma Al mostrarse la actividad Resumendeberemos leer estos datos tambieacuten a partir de SharedPreferences para mostrarlos porpantalla

Por uacuteltimo crea un meacutetodo que valide el DNI (debe tratarse de una secuencia de ochodiacutegitos entre 0 y 9 y una letra al final) Si se pulsa Siguiente y el DNI no tiene el formatocorrecto no se deberaacute pasar a la actividad Resumen En lugar de esto se mostraraacute unToast en pantalla con un mensaje de error

42 Actividad de preferencias (075 puntos)

En este ejercicio seguimos trabajando con el proyecto del ejercicio anterior Vamos aantildeadir un menuacute de opciones que permita escoger en queacute momento se comprobaraacute lavalidez del DNI Para ello antildeade en primer lugar un menuacute a la actividad Formulario conuna uacutenica opcioacuten cuyo nombre seraacute Opciones

Al seleccionar esta opcioacuten se deberaacute mostrar una actividad de preferencias cuyo aspectoseraacute el siguiente

Persistencia

34Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Preferencias de la aplicacioacuten Dni

El significado de las opciones seraacute el siguiente

bull En el campo con esta opcioacuten seleccionada al pulsar sobre el campo de DNI en elformulario deberaacute mostrarse un Toast indicando si la sintaxis del DNI es correcta ono

bull Al pulsar si se desactiva esta opcioacuten (que deberaacute estar activada por defecto) no seharaacute la comprobacioacuten del DNI al pulsar el botoacuten Siguiente por lo que siempre seraacuteposible pasar de la actividad Formulario a la actividad Resumen sea cual sea elformato del DNI introducido

bull El uacuteltimo check box se encontraraacute siempre deshabilitado iquestQueacute atributo debemosutilizar para conseguir esto

Crea dos variables booleanas llamadas enElCampo y alPulsar Su valor dependeraacute delestado de los checkboxes anteriores y se actualizaraacute por medio del eventoonSharedPreferenceChange Utiliza el valor de estas variables en el coacutedigo de tuactividad para que esta tenga el comportamiento indicado

43 Proveedor de contenidos propio (075 puntos)

Vamos a implementar otra forma de acceder a la base de datos de usuarios de laaplicacioacuten BaseDatos desarrollada durante los ejercicios de la sesioacuten anterior siguiendoesta vez el patroacuten de disentildeo ContentProvider de Android Seguiremos trabajando puescon dicho proyecto

bull Creamos una nueva clase llamada UsuariosProvider que herede deContentProvider Esto nos obligaraacute a sobrecargar una serie de meacutetodos abstractosAntes de implementar la query vamos a configurar el provider

bull Antildeadimos algunos campos tiacutepicos de los content provider

Persistencia

35Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public static final Uri CONTENT_URI =Uriparse(contentesuajtechandroidbasedatosusuarios)

private static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

staticuriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

TODAS_FILAS)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

UNA_FILA)

bull Vamos a acceder a la misma base de datos de usuarios utilizada en los ejercicios de lasesioacuten anterior pero no vamos a hacerlo a traveacutes del adaptador que tuvimos queimplementar sino que vamos a copiar de eacutel el coacutedigo que nos haga falta Copia loscampos que definen el nombre de la base de datos de la tabla de las columnas laversioacuten asiacute como la referencia al contexto y a la base de datos La sentenciacompilada del insert ya no va a hacer falta Inicializa los valores necesarios en elconstructor Para inicializar la referencia a la base de datos vamos a utilizar una vezmaacutes MiOpenHelper Podemos copiarlo del adaptador que ya implementamos en losejercicios de la sesioacuten anterior

bull Implementa de forma apropiada el getType para devolver un tipo MIME diferenteseguacuten si se trata de una URI de una fila o de todas las filas Para ello ayuacutedate deluriMatcher

bull Implementa el meacutetodo query Simplemente se trata de devolver el cursor queobtenemos al hacer una consulta a la base de datos SQLite Algunos de los paraacutemetrosque le pasaremos los recibimos como paraacutemetros del meacutetodo del provider Los que notengamos iraacuten con valor null

bull Aunque no tenemos todos los meacutetodos del UsuariosProvider implementadospodemos probarlo Para ello debemos registarlo en el AndroidManifestxml

ltprovider androidname=UsuariosProvider

androidauthorities=esuajtechandroidbasedatosgtltapplicationgt

ltmanifestgt

bull En la clase Main se antildeadioacute codigo para insertar una serie de valores en la base dedatos y mostrarlos en el campo de texto Manteniendo este coacutedigo vamos a antildeadir alcampo de texto el resultado obtenido con la consulta del UsuariosProvider paracomprobar que tanto el adaptador de la base de datos como el proveedor nosdevuelven el mismo resultado

44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)

Porque es la forma estaacutendar que establece Android de acceder a contenidos Ademaacutes elproveedor de contenidos nos permitiraacute notificar al ContentResolver de los cambiosocurridos Asiacute componentes en la pantalla podraacuten refrescarse de forma automaacutetica

Persistencia

36Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Utiliza el proyecto ProveedorContenidos de las plantillas Implementa la insercioacuten enel proveedor de contenidos Prueacutebala insertando algunos usuarios de ejemplo en laclase Main Implementa tambieacuten el OnClickListener del botoacuten que inserta nuevosusuarios El nombre del nuevo usuario iraacute indicado en el EditText

bull Comprueba que la insercioacuten funciona y que gracias a la siguiente liacutenea y al estarusando un proveedor de contenidos la lista se actualiza automaacuteticamente cuandoocurre alguacuten cambio sin necesidad de pedir expliacutecitamente la actualizacioacuten al pulsarel botoacuten

cursorsetNotificationUri(cr UsuariosProviderCONTENT_URI)

NotaEsto no va a funcionar si se notifica que se ha producido un cambio al insertar o borrar unelemento Para ello usamosgetContext()getContentResolver()notifyChange(UsuariosProviderCONTENT_URI null)

bull Implementa el meacutetodo delete del proveedor de contenidos Prueacutebalo en la claseMain Termina de implementar el onCreateContextMenuListener que se ejecutaraacutecada vez que se haga una pulsacioacuten larga sobre alguna entrada de la lista Compruebaque funciona (eliminando el usuario correspondiente y no otro)

Persistencia

37Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

5 Persistencia de datos en iOS Ficheros y SQLite

51 Introduccioacuten

La persistencia de datos en iOS se realiza en una pequentildea memoria flash que tienenuestro dispositivo la cual es equivalente a un disco duro de tamantildeo limitado Los datosque ahiacute se almacenen se conservaraacuten aunque el dispositivo se apague Por motivos deseguridad el SO de iOS no permite que las aplicaciones accedan directamente a lamemoria interna pero a cambio existe una API completa de acceso a la porcioacuten dememoria que le corresponde a la app que desarrollemos

En una aplicacioacuten iOS encontraremos baacutesicamente 4 modos de almacenamiento deinformacioacuten de forma persistente

bull Ficheros de propiedades (plist)bull Bases de datos embebidas SQLitebull Datos de usuario (User Defaults)bull Core Data

En esta primera sesioacuten veremos coacutemo usar los ficheros de texto (Property Lists) y elalmacenamiento en base de datos de tipo SQLite

52 Ficheros plist (Property Lists)

El almacenamiento de datos en ficheros de propiedades es uno de los maacutes usados en eldesarrollo de las aplicaciones para iOS Se usa principalmente para guardar datos deconfiguracioacuten de la aplicacioacuten u otra informacioacuten poco compleja y acceder a ella deforma sencilla Si estamos desarrollando un juego podemos usarlos para la definicioacuten delos niveles almacenar las puntuaciones del jugador etc

521 Leyendo datos desde ficheros plist

Los ficheros plist (Property Lists) tienen un formato XML aunque XCode nosproporciona una interfaz que nos permite acceder a ellos de forma muy sencilla Paraentender el funcionamiento de este tipo de ficheros vamos a realizar un ejemplo en el queprimero crearemos un fichero plist lo completaremos con datos de ejemplo y despueacutes loleeremos para mostrarlo por pantalla

Un ejemplo de un fichero plist podriacutea ser el siguiente

Persistencia

38Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Fichero plist

Antes de nada creamos un proyecto nuevo en XCode de tipo Window-based

Application al que llamaremos sesion03-ejemplo1 Para crear un fichero plistdebemos hacer click derecho sobre el directorio Supporting Files de nuestro proyectoy seleccionamos iOS gt Resource gt Property List Lo guardaremos con el nombreconfigUsuario Seguidamente ya podemos acceder al fichero recieacuten creado haciendoclick sobre el en este momento XCode nos mostraraacute un pequentildeo editor totalmente vacioen el que iremos rellenando con datos Podemos verlo tambieacuten en formato XML sihacemos click derecho sobre el y seleccionamos Open As gt Preview

Para empezar a rellenar el fichero que acabamos de crear hacemos click derecho dentrode su contenido (ahora vacio) y seleccionamos Add row Entonces nos apareceraacute unafila vaciacutea Dentro del campo Key escribimos la clave un nombre descriptivo quedeberemos utilizar maacutes adelante para acceder a los datos En nuestro caso vamos aescribir config como clave Dentro de la columna Type seleccionamosDictionary Veremos que aparece una flecha en el lado izquierdo que indica quepueden haber subniveles dentro de nuestro diccionario

Ahora antildeadimos un nuevo item que cuelgue del diccionario que acabamos de crear paraello hacemos click sobre la flecha de la izquierda esta se giraraacute hacia abajo entonceshacemos click ahora sobre el siacutembolo + (maacutes) Nos apareceraacute una nueva liacutenea que cuelgadel diccionario

En esta nueva liacutenea dentro del campo key escribimos nombre el campo type lodejamos como de tipo String ya que va a ser una cadena de texto y en la columna deValue escribimos nuestro nombre por ejemplo Javi

Ahora vamos a antildeadir otro campo nuevo en nuestra configuracioacuten para ello hacemosclick sobre el siacutembolo + y nos apareceraacute una nueva liacutenea justo al mismo nivel que laanterior y colgando del diccionario Dentro del campo de Key escribimos ciudad yen Value escribimos Alicante El campo de Type lo dejamos como String

Persistencia

39Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora dentro de nuestro fichero plist vamos a indicar los dispositivos que dispone elusuario para ello creamos una nueva linea dentro del diccionario y le pondremos comoclave dispositivos Seleccionamos el tipo Array y seguimos los mismos pasosque al crear el diccionario para ello hacemos click sobre la flecha de la izquierda ydespueacutes sobre el siacutembolo + (maacutes) Ahora nos apareceraacute una nueva fila con clave Item 0Seleccionamos el tipo String y en el campo Value iPhone

Ahora repetimos el paso 3 veces indicando como values iPad Android y

Blackberry

Una vez seguidos todos estos pasos tendremos nuestro fichero de propiedades listo Debede quedar como se muestra a continuacioacuten

Fichero plist terminado

Este fichero se almacenaraacute dentro del directorio de bundle cuando compilemos laaplicacioacuten En el caso de que queramos escribir en el debermos almacenar el ficherodentro del directorio de Documents del binario

Ahora desde nuestro coacutedigo vamos a acceder a eacutel y a mostrarlo por pantalla para elloescribimos el siguiente coacutedigo dentro del meacutetodo didFinishLaunchingWithOptions dela clase delegada

Cargamos el fichero PLISTNSString mainBundlePath = [[NSBundle mainBundle] bundlePath]NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]NSDictionary diccionario = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionario delraiz

NSString nombre = [dicConfig objectForKeynombre]

Persistencia

40Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

[selfwindow makeKeyAndVisible]return YES

Si todo ha ido correctamente deberemos obtener la siguiente salida por la consola

Lista de dispositivos

522 Escribiendo datos en ficheros plist

Partiendo del ejemplo anterior vamos a escribir ahora en el fichero para ello primerodebemos comprobar que el fichero plist se encuentre dentro del directorio de Documents

del dispositivo en el caso de que no lo esteacute (por ejemplo si es la primera vez que arrancala aplicacioacuten) debemos copiarlo a ese directorio Una vez que tenemos el fichero dentrodel directorio de documentos ya podemos escribir en el

Para realizar todo el proceso de comprobacioacuten de la existencia del fichero dentro deldirectorio de documentos copiarlo en ese directorio y leer los datos debemos de sustituirel coacutedigo que hay dentro del meacutetodo didFinishLaunchingWithOptions de la clasedelegada por este otro

AtencioacutenPara que funcione la escritura en un fichero plist este debe de estar dentro del directorio deDocuments de nuestro dispositivo iOS no en el boundle

NSDictionary diccionarioRaiz

Ruta del directorio de documentos de nuestro dispositivoNSArray paths =

NSSearchPathForDirectoriesInDomains(NSDocumentDirectoryNSUserDomainMask YES)

if ([paths count] gt 0)

Persistencia

41Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

Primero comprobamos si existe el fichero en eldirectorio de documentos

BOOL fileExists = [[NSFileManager defaultManager]fileExistsAtPathdocumentsFilename]if (fileExists)

NSLog(Fichero encontrado en directorio de documentosOK

documentsFilename)

Si existe ya cargamos los datos desde aquidirectamente

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

else

NSLog(No se encuentra el fichero configUsuarioplisten

el directorio de documentos-gtLo creamos)

Si no existe primero cargamos el fichero PLISTdesde el Boundle

NSString mainBundlePath = [[NSBundle mainBundle]bundlePath]

NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Y despues escribimos los datos cargados (eldiccionario completo)

a un fichero nuevo de la carpeta de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

NSLog(plist de configUsuario creado)

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionariodel raiz

NSString nombre = [dicConfig objectForKeynombre]NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

Persistencia

42Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Por ultimo liberamos la memoria del diccionario raiz[diccionarioRaiz release]

[selfwindow makeKeyAndVisible]return YES

Para comprobar el funcionamiento arrancamos la aplicacioacuten y veremos que la primera vezse crearaacute el fichero dentro de la carpeta de Documents de nuestro dispositivo y en lassiguientes ya no se crearaacute y simplemente lo leeraacute desde la carpeta de Documents

Ahora que ya tenemos la gestioacuten de ficheros baacutesica completada vamos a escribir sobre elalguacuten dato Para ello simplemente escribimos el siguiente fragmento de coacutedigo justo antesde la liacutenea [selfwindow makeKeyAndVisible]

Cambiamos el nombre y lo guardamos en el fichero Ruta del directorio de documentos de nuestro dispositivo

if ([paths count] gt 0)

Cargamos el fichero desde el directorio dedocumentos del dispositivo

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cambiamos el valor del nombre en el diccionario[dicConfig setValueOtro nombre forKeynombre]

Escribimos todo el diccionario de nuevo al fichero del directorio de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

Por ultimo liberamos el diccionario de lamemoria

[diccionarioRaiz release]

Lo que hemos hecho en el coacutedigo anterior es cargar todo el diccionario de nuevo desde eldirectorio de documentos modificar los datos que queremos cambiar o incluso antildeadir ydespueacutes guardar el diccionario en el fichero

Volvemos a arrancar la aplicacioacuten y veremos que ha cambiado el campo de nombre

Nota

Persistencia

43Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

La lectura y escritura de datos en un fichero plist se debe de hacer de una soacutela vez nunca porpartes Siempre se debe de cargar o escribir un diccionario NSDictionary como elementoprincipal del fichero

53 SQLite

SQLite es una base de datos relacional ligera con la que se puede trabajar de formasencilla mediante sentencias SQL La base de datos se almacenaraacute en un fichero dentrode nuestra aplicacioacuten Este meacutetodo de persistencia puede ser el adecuado cuando los datosa almacenar esteacuten en un formato de tablas filas y columnas y se necesite que los datossean accesibles de forma raacutepida

531 Herramientas disponibles

Para explicar el funcionamiento de las librerias de SQLite de Cocoa Touch vamos a seguirun ejemplo completo Comenzamos creando la base de datos para ello podemos utilizaruno de los frontends que facilitan la gestioacuten del SQLite uno bastante sencillo yrecomendado es el SQLite Database Browser y otro es fmdb

Nosotros utilizaremos el primero (SQLite Database Browser) Abrimos la aplicacioacuten yhacemos click sobre el botoacuten de crear nueva base de datos que llamaremospersonasDBsqlite Seguidamente creamos las tablas para simplificar en nuestroejemplo crearemos una soacutela tabla llamada personas Esta tabla tendraacute las siguientescolumnas id (NUMERIC) nombre (TEXT) apellidos (TEXT) dni (TEXT) edad

(NUMERIC) y localidad (TEXT)

Persistencia

44Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Estructura de la Base de Datos

Una vez creada la tabla pasamos a insertar datos dentro de ella para ello abrimos lapestantildea de Browse Data y hacemos click en New Record Cuando tengamos al menos 3registros metidos dentro de la tabla ya podemos guardar la base de datos Hacemos clicksobre guardar le ponemos el nombre que queramos y elegimos cualquier directorio delMAC

Datos de la Base de Datos

532 Lectura de datos

Una vez que tenemos la base de datos SQLite creada ya podemos empezar a escribir elcoacutedigo para leer y mostrar los datos dentro de nuestra aplicacioacuten

Primero empezamos creando un proyecto nuevo en XCode usando la plantilla basada envistas View-based application y lo llamaremos sesion03-ejemplo2 Ahora antildeadimosel framework de sqlite a nuestro proyecto para ello hacemos click sobre el raiz delproyecto y seleccionamos la pestantildea Build Phases Desplegamos el listado de Link

Binary With Libraries y hacemos click sobre el botoacuten (+) El el listadoseleccionamos el framework libsqlite3dylib y hacemos click en Add

Persistencia

45Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Antildeadiendo el framework libsqlite3dylib

Ahora antildeadimos a nuestro proyecto la base de datos que hemos creado anteriormentepara ello arrastramos el fichero personasDBsqlite a nuestro directorio Supporting

Files Asegurate de seleccionar la opcioacuten de Copy items to destination groups

folder (if needed) para que se copie el fichero dentro de la carpeta del proyecto

Para acelerar el proceso de lectura de la base de datos y ahorrar memoria vamos a cargarprimero todos los datos en objetos para ello vamos a crear una clase que se encargue dealmacenar los datos Hacemos click derecho sobre el raiz del proyecto New file gtObjective-C class seleccionamos NSObject como clase principal y le damos a NextEscribimos como nombre de la clase Persona y hacemos click en save

Personah

imports iniciales

interface Persona NSObject int _uIdNSString _nombreNSString _apellidos

Persistencia

46Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSInteger _edadNSString _localidad

property (nonatomic assign) int uIdproperty (nonatomic copy) NSString nombreproperty (nonatomic copy) NSString apellidosproperty (nonatomic) NSInteger edadproperty (nonatomic copy) NSString localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidos edad(NSInteger)edadlocalidad(NSString )localidad

end

Personam

import Personah

implementation Persona

synthesize uId = _uIdsynthesize nombre = _nombresynthesize apellidos = _apellidossynthesize edad = _edadsynthesize localidad = _localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidosedad(NSInteger)edad localidad(NSString )localidad

if ((self = [super init])) selfuId = uIdselfnombre = nombreselfapellidos = apellidosselfedad = edadselflocalidad = localidad

return self

- (void) dealloc selfnombre = nilselfapellidos = nilselflocalidad = nil[super dealloc]

end

Una vez creada la clase Persona vamos a crear la clase encargada de manejar la basede datos sqlite3 Otra opcioacuten seriacutea cargar todos los datos directamente desde la clasedelegada al arrancar la aplicacioacuten pero no es recomendable ya que si en alguacuten momentotenemos que cambiar de motor de base de datos lo tendremos maacutes complicado

Persistencia

47Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creamos entonces una clase dentro de nuestro proyecto llamada BDPersistencia queherede de NSObject y escribimos el siguiente coacutedigo

BDPersistenciah

imports iniciales

interface BDPersistencia NSObject sqlite3 _database

+ (BDPersistencia)database- (NSArray )arrayPersonas

end

BDPersistenciam

import BDPersistenciahimport Personah

implementation BDPersistencia

static BDPersistencia _database

+ (BDPersistencia)database if (_database == nil)

_database = [[BDPersistencia alloc] init]return _database

- (id)init if ((self = [super init]))

NSString sqLiteDb = [[NSBundle mainBundle]pathForResourcepersonasDB

ofTypesqlite]

if (sqlite3_open([sqLiteDb UTF8String]amp_database) = SQLITE_OK)

NSLog(Fallo al abrir la BD)

return self

- (void)dealloc sqlite3_close(_database)[super dealloc]

- (NSArray )arrayPersonas

NSMutableArray arraySalida = [[[NSMutableArray alloc]init]

autorelease]

Persistencia

48Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString query = SELECT id nombre apellidoslocalidad edad

FROM personas ORDER BY apellidos DESC

sqlite3_stmt statementif (sqlite3_prepare_v2(_database [query UTF8String]

-1ampstatement nil)

== SQLITE_OK) while (sqlite3_step(statement) == SQLITE_ROW)

int uniqueId = sqlite3_column_int(statement0)

char nombreChar = (char )sqlite3_column_text(statement 1)

char apellidosChar = (char )sqlite3_column_text(statement 2)

char localidadChar = (char )sqlite3_column_text(statement 3)

int edad = sqlite3_column_int(statement 4)

NSString nombre = [[NSString alloc]initWithUTF8StringnombreChar]NSString apellidos = [[NSString alloc]initWithUTF8StringapellidosChar]NSString localidad = [[NSString alloc]initWithUTF8StringlocalidadChar]Persona persona = [[Persona alloc]initWithUiduniqueId nombrenombreapellidosapellidos edadedad

localidadlocalidad]

[arraySalida addObjectpersona][nombre release][apellidos release][localidad release][persona release]

sqlite3_finalize(statement)

return arraySalida

end

Con esto ya podemos leer de nuestra base de datos las personas Para ver que funcionatodo a la perfeccioacuten escribimos el siguiente coacutedigo dentro de la clase delegada en elmeacutetodo applicationDidFinishLaunching Antes debemos incluir en la parte superiorlas clases creadas

import BDPersistenciahimport Personah

NSArray personas = [BDPersistenciadatabase]arrayPersonas

for (Persona persona in personas) NSLog(d d personauId

personanombrepersonaapellidos personalocalidad personaedad)

Persistencia

49Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Si ejecutamos el proyecto obtendremos la siguiente salida por consola

Listado de personas

533 Mostrando los datos en una tabla

Una vez que tenemos el coacutedigo para obtener todas las filas de la tabla de personas de labase de datos queda lo maacutes faacutecil mostrarlas dentro de una vista de la aplicacioacuten En estecaso utilizaremos la vista de tabla UITableView

Comenzamos creando una nueva vista en nuestro proyecto para ello hacemos clickderecho sobre el raiz y seleccionamos New File gt UIVIewController SubclassAsegurate de marcar Subclass of UITableViewController y la opcoacuten de With

XIB for user interface Hacemos click en Next y guardamos el fichero comoPersonasViewController por ejemplo

Ahora abrimos el fichero PersonasViewControllerh y antildeadimos un NSArray queseraacute el que almacene las personas de la base de datos

imports iniciales

interface PersonasViewController UITableViewController

NSArray _listadoPersonas

property (nonatomic retain) NSArray listadoPersonas

end

Ahora abrimos el fichero PersonasViewControllerm antildeadimos el syntetizeimportamos los ficheros de gestioacuten de la base de datos la clase Persona y todo el coacutedigobaacutesico para la gestioacuten de la vista de la tabla

import PersonasViewControllerhimport Personahimport BDPersistenciah

implementation PersonasViewController

synthesize listadoPersonas = _listadoPersonas

Persistencia

50Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

- (id)initWithStyle(UITableViewStyle)style

self = [super initWithStylestyle]if (self)

Custom initializationreturn self

- (void)dealloc

[super dealloc]

selflistadoPersonas = nil

- (void)didReceiveMemoryWarning

Releases the view if it doesnt have a superview[super didReceiveMemoryWarning]

Release any cached data images etc that arent inuse

pragma mark - View lifecycle

- (void)viewDidLoad

[super viewDidLoad]

selftitle = Listado de personas

selflistadoPersonas = [BDPersistenciadatabase]arrayPersonas

- (void)viewDidUnload

[super viewDidUnload] Release any retained subviews of the main view eg selfmyOutlet = nil

- (void)viewWillAppear(BOOL)animated

[super viewWillAppearanimated]

- (void)viewDidAppear(BOOL)animated

[super viewDidAppearanimated]

- (void)viewWillDisappear(BOOL)animated

[super viewWillDisappearanimated]

- (void)viewDidDisappear(BOOL)animated

[super viewDidDisappearanimated]

- (BOOL)shouldAutorotateToInterfaceOrientation

Persistencia

51Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

(UIInterfaceOrientation)interfaceOrientation

Return YES for supported orientationsreturn (interfaceOrientation ==

UIInterfaceOrientationPortrait)

pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView(UITableView)tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection

(NSInteger)section

Return the number of rows in the sectionreturn [selflistadoPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath

(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]if (cell == nil)

cell = [[[UITableViewCell alloc]initWithStyleUITableViewCellStyleSubtitle

reuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona persona = (Persona )[selflistadoPersonas

objectAtIndexindexPathrow]celltextLabeltext = [NSString stringWithFormat

(d) personanombrepersonaapellidos personaedad]celldetailTextLabeltext = personalocalidad

return cell

pragma mark - Table view delegate

- (void)tableView(UITableView )tableViewdidSelectRowAtIndexPath

(NSIndexPath )indexPath

Navigation logic may go here Create and pushanother view controller

end

Persistencia

52Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora soacutelo nos queda incorporar la vista que acabamos de crear dentro de nuestraaplicacioacuten Para ello vamos a llamarla desde el Delegate y a meterla dentro de unNavigation Controller Primero debemos de antildeadir un Outlet en la clasedelegada hacia un Navigation Controller

imports iniciales

interface sesion03_ejemplo1AppDelegate NSObjectltUIApplicationDelegategt

UIWindow _windowUINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet

UINavigationController navController

end

Ahora hacemos click sobre la vista MainWindowxib y arrastramos un Navigation

Controller desde el listado de la columna de la derecha hacia la columna de laizquierda donde pone Objects Nos apareceraacute el Navigation Controller dibujadoen pantalla Ahora desplegamos el Navigation Controller y hacemos click sobre elView Controller que hay dentro Nos vamos a la pestantildea de Identity Inspector

de la columna de la derecha y escribimos dentro de Class el nombre de la vista dellistado de personas PersonasViewController

Finalmente hacemos click sobre el objeto Delegate vamos a la pestantildea de Show

Connections Inspector y arrastramos desde navController hasta el objetoNavigation Controller de la columna de la izquierda Ya tenemos los controladoresy las vistas conectadas

Una vez hecho esto nos queda indicar dentro de nuestro coacutedigo que la aplicacioacuten arranquecon el Navigation Controller para ello escribimos lo siguiente al final del meacutetododidFinishLaunchingWithOptions de la implementacioacuten de la clase delegada

selfwindowrootViewController = selfnavController[selfwindow makeKeyAndVisible]return YES

Ya estaacute listo todo y preparado para compilar Si ejecutamos el proyecto veremos que nosaparece el listado de personas en la vista de tabla

Persistencia

53Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios

61 Creando y leyendo un fichero plist (15 puntos)

En este primer ejercicio vamos a crear nuestra primera aplicacioacuten usando el meacutetodo depersistencia de ficheros La aplicacioacuten mostraraacute en una vista de tabla TableView unlistado de series en la que si seleccionamos una veremos su informacioacuten maacutes importanteen otra vista Comenzaremos creando un fichero de propiedades (plist) siguiendo unaestructura determinada y posteriormente mostraremos en una vista de tabla todos susdatos iexclComenzamos

1) Crear un proyecto usando la plantilla Master-Detail application Lo llamaremosejercicio-plist-ios como identificador de empresa escribiremos esuajtech y como prefijode clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremos UseAutomatic Reference Counting para olvidarnos de la gestioacuten de memoria El resto deopciones las dejaremos sin marcar

2) Crear un archivo de propiedades (plist) con el nombre de series

3) Editar el archivo plist creando la siguiente estructura de datos (5 series miacutenimo)

Plist series

4) Rellenar el archivo plist con datos de series reales

5) Escribir el coacutedigo necesario en UAMasterViewControllerm que recorra el archivoplist que acabamos de crear y muestre por consola los titulos de las series

6) Mostrar en la tabla que hay implementada los tiacutetulos de la serie Para ello deberemosde completar todos los meacutetodos del protocolo del TableView

7) Ejecutar la aplicacioacuten y comprobar que funciona correctamente

Persistencia

54Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

62 Implementar vista de detalle de la serie (05 puntos)

Dentro del proyecto anterior (no hace falta crear uno nuevo) vamos a mostrar toda lainformacioacuten adicional de cada una de las series en una vista de detalle Cuandoseleccionemos una fila de la tabla nos deberaacute aparecer una nueva vista con el resto deinformacioacuten (antildeo director principal sinopsis y duracioacuten)

63 Uso baacutesico de una base de datos SQLite (1 punto)

En este ejercicio vamos a crear la misma aplicacioacuten que en el ejercicio anterior perousando el meacutetodo de persistencia de SQLite Para completar el ejercicio deberemos seguirlos siguientes pasos

1) Crear un proyecto usando la plantilla Master-Detail application lo llamaremosejercicio-sqlite-ios como identificador de empresa escribiremos esuajtech y comoprefijo de clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremosUse Automatic Reference Counting para olvidarnos de la gestioacuten de memoria El restode opciones las dejaremos sin marcar

2) Nos descargamos e instalamos el programa SQLite Database Browser desde estadireccioacuten

3) Disentildear la estructura de la base de datos usando SQLite Database Browser tal y comose muestra en la imagen siguiente

Sqlite series

4) Insertar al menos 5 series en la base de datos usando el programa SQLite DatabaseBrowser

5) Cargar todos los datos de la base de datos desde el meacutetododidFinishLaunchingWithOptions de la clase UAAppDelegate Mostrar por consola losdatos cargados

6) Mostrar los datos cargados en el paso anterior en la vista de tabla UITableView quehay creada en la clase UAMasterViewController

Persistencia

55Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7) Arrancar la aplicacioacuten y comprobar que se muestran las series en la tabla de formacorrecta

Persistencia

56Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7 Persistencia de datos en iOS User Defaults y Core Data

71 Introduccioacuten

En esta sesioacuten veremos como utilizar dos nuevos meacutetodos de persistencia en iOS ambosmuy interesantes y usados muy frecuentemente

bull User Defaults Muy uacutetil para almacenar datos simples y de uso repetido dentro denuestras aplicaciones iOS

bull Core Data Meacutetodo avanzado de almacenamiento de objetos enteros dentro de lamemoria del dispositivo iOS Muy uacutetil para almacenar datos complejos conrelaciones etc

72 User Defaults

Este meacutetodo es muy uacutetil cuando queremos almacenar pequentildeas cantidades de datos comopuntuaciones informacioacuten de usuario el estado de nuestra aplicacioacuten configuracionesetc

Este meacutetodo es el maacutes raacutepido de usar ya que no requiere de una base de datos ni ficherosni ninguna otra capa intermedia Los datos se almacenan directamente en la memoria deldispositivo dentro de un objeto de la clase NSUserDefaults

Para aprender a usar este meacutetodo de almacenamiento de datos vamos a crear unaaplicacioacuten muy simple que lea un nombre y una edad de pantalla y las guarde en unobjeto NSUserDefaults Para ello seguimos los siguientes pasos

bull 1) Comenzamos creando un nuevo proyecto llamado sesion04-ejemplo1 dentrode XCode

bull 2) Abrimos la vista sesion04_ejemplo1ViewController y arrastramos dos TextField un botoacuten y dos labels quedando la vista de la siguiente manera

Persistencia

57Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista de la aplicacioacuten

bull 3) Abrimos el fichero sesion04_ejemplo1ViewControllerh y definimos el Outletdel evento onclick del botoacuten El fichero quedaraacute de la siguiente manera

imports iniciales

interface sesion04_ejemplo1ViewController UIViewController UITextField tfNombreUITextField tfEdad

property(nonatomic retain) IBOutlet UITextField tfNombreproperty(nonatomic retain) IBOutlet UITextField tfEdad

-(IBAction) guardaDatos

end

bull 4) Ahora implementamos el coacutedigo para el meacutetodo del botoacuten dentro desesion04_ejemplo1ViewControllerm

synthesize tfEdadsynthesize tfNombre

-(IBAction) guardaDatos NSLog(Guardamos los datos)

Persistencia

58Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creamos el objeto NSUserDefaultsNSUserDefaults datos = [NSUserDefaults standardUserDefaults]

guardamos el nombre (NSString)[datos setObjecttfNombretext forKeynombre]

guardamos la edad (Integer)[datos setInteger[tfEdadtext integerValue] forKeyedad]

almacenamos los datos en la memoria[datos synchronize]

bull 5) Tenemos que enlazar el botoacuten con el evento que hemos programado en el pasoanterior para ello abrimos la vista sesion04_ejemplo1ViewControllerxib y conla tecla ctlr apretada arrastramos desde el botoacuten hasta Files Owner (columna de laizquierda parte superior) y seleccionamos el meacutetodo que hemos creado(guardaDatos) De esta forma ya tenemos conectado el botoacuten

bull 6) Ahora hacemos lo mismo con los Text Field Tenemos que conectarlos alcontrolador para ello seleccionamos el objeto Files Owner y abrimos la pestantildeade conexiones (la que estaacute maacutes a la derecha en la columna de la derecha) Hacemosclick en tfEdad y arrastramos hasta el Text Field de edad Hacemos lo mismo para eltext field de nombre Ya tenemos todos los elementos de la vista conectados

Conexioacuten de Outlets

bull 7) Arrancamos la aplicacioacuten y la probamos

Persistencia

59Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Aplicacioacuten

bull 8) Ahora vamos a hacer que la aplicacioacuten cargue los datos que se han guardado alarrancar la vista para ello simplemente tenemos que escribir el siguiente coacutedigo en elmeacutetodo (void)viewDidLoad de la clase sesion04_ejemplo1ViewControllerm

- (void)viewDidLoad

[super viewDidLoad]

NSUserDefaults datos = [NSUserDefaults standardUserDefaults]

obtenemos un NSStringNSString nombre = [datos stringForKeynombre]

obtenemos un NSIntegerNSInteger edad = [datos integerForKeyedad]

if (nombre=nil ampamp edad=0)NSLog(Nombre cargado edad dnombreedad)

else

NSLog(Sin datos)

bull 9) Ya podemos arrancar de nuevo la aplicacioacuten y veremos que si escribimos cualquier

Persistencia

60Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

texto en los Text Fields estos se almacenaraacuten dentro del objeto NSUserDefaults Alapagar la aplicacioacuten y volver a arrancarla estos datos se cargaraacuten (apareceraacuten enconsola)

Consola

NotaLos datos permaneceraacuten en nuestro dispositivo hasta que se elimine la aplicacioacuten en ese casotodos los datos guardados en NSUserDefaults se borraraacuten

bull 10) Probar eliminar la aplicacioacuten del dispositivosimulador y volver arrancarComprobamos que los datos se han borrado

Consola

73 Core Data

De todas las formas de persistencia de datos que existen en iOS Core Data es la maacutesapropiada para usar en caso de manejar datos no triviales con relaciones algo maacutescomplejas entre ellos El uso de Core Data dentro de nuestra aplicacioacuten optimiza almaacuteximo el consumo de memoria mejora el tiempo de respuesta y reduce la cantidad decoacutedigo redundante a escribir

Core Data utiliza de fondo el meacutetodo de almacenamiento SQLite visto en la sesioacuten 3 deeste moacutedulo por tanto para explicar el funcionamiento de este meacutetodo de persistenciavamos a utilizar el mismo modelo de datos que usamos en esa sesioacuten Persona -gtDetallePersona

El el siguiente ejemplo haremos la misma aplicacioacuten que hicimos con SQLite perousando este nuevo meacutetodo de persistencia

731 Creando el proyecto

Para empezar tenemos que crearnos un nuevo proyecto en XCode Para ello abrimosXCode y seleccionamos crear una nueva aplicacioacuten basada en ventanas (Window-basedapplication) Hacemos click en Next escribimos como nombre del proyectosesion04-ejemplo2 y seleccionamos Use Core Data

Persistencia

61Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creando un proyecto nuevo

Con esto ya tenemos la estructura baacutesica para empezar a programar Antes de nadahechamos un vistazo raacutepido a toda la estructura de directorios creada y vemos que dentrodel directorio principal tenemos un archivo que se llamasesion04_ejemplo2xcdatamodeld Al hacer click sobre el archivo se nos abre uneditor especial que en principio estaacute vacio y que maacutes adelante utilizaremos para disentildearnuestro modelo de datos

Ahora hechamos un vistazo a la clase delegada sesion04_ejemplo2AppDelegatemvemos que hay unos cuantos meacutetodos nuevos que serviraacuten para configurar el Core DataVamos a explicar los maacutes importantes

bull - (NSManagedObjectModel )managedObjectModel NSManagedObjectModel esla clase que contiene la definicioacuten de cada uno de los objetos o entidades quealmacenamos en la base de datos Normalmente este meacutetodo no lo utilizaremos ya quenosotros vamos a utilizar el editor que hemos visto antes con el que podremos crearnuevas entidades crear sus atributos y relaciones

bull - (NSPersistentStoreCoordinator )persistentStoreCoordinator Aquiacute esdonde configuramos los nombres y las ubicaciones de las bases de datos que se usaraacutenpara almacenar los objetos Cuando un managed object necesite guardar algo pasaraacutepor este meacutetodo

bull - (NSManagedObjectContext )managedObjectContext Esta claseNSManagedObjectContext seraacute la maacutes usada con diferencia de las tres y por tanto la

Persistencia

62Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

maacutes importante La utilizaremos baacutesicamente para obtener objetos insertarlos oborrarlos

732 Definiendo el modelo de datos

En el ejemplo que hicimos en la sesioacuten anterior de SQLite creamos dos tablas una paralos datos baacutesicos de la persona y otra para el detalle Ahora haremos lo mismo perousando Core Data la diferencia principal es que mientras que con SQLite obteniamos soacutelolos datos que necesitaacutebamos de la base de datos aquiacute obtenemos el objeto completo

Para definir nuestro modelo de datos basado en dos objetos (Persona y DetallePersona)abrimos el editor visual haciendo click sobre el ficherosesion04_ejemplo2xcdatamodeld Empezaremos creando una nueva entidad para ellohacemos click sobre el botoacuten Add Entity (+) que se encuentra en la parte inferior dela pantalla A la nueva entidad le pondremos como nombre Persona

Dentro de la seccioacuten de Attributes hacemos click sobre (+) y creamos un nuevoatributo para nuestra entidad le llamamos nombre y le asignamos como tipo StringRealizamos este mismo paso 2 veces maacutes para antildeadir apellidos y edad este uacutetlimode tipo Integer 16

Atributos Persona

Ahora creamos la entidad de DetallePersona de la misma manera que la anterior perocreando los atributos localidad pais direccion cp y telefono Todasde tipo String

Atributos DetallePersona

Finalmente nos queda relacionar de alguacuten modo las dos entidades para ello antildeadimos una

Persistencia

63Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

relacioacuten dentro de la entidad de Persona hacia DetallePersona simplementehaciendo click sobre el botoacuten (+) de la seccioacuten de Relationships A la relacioacuten leponemos como nombre detalle seleccionamos como destino la entidadDetallePersona y seraacute No inverse Este tipo de relacioacuten es uno a uno Por dentroCore Data realizaraacute crearaacute una clave ajena en la tabla de persona que apunte aDetallePersona pero eso a nosotros no nos interesa

Vista general modelo de datos (1)

Apple recomienda que siempre que hagamos una relacioacuten de una entidad a otra hagamostambieacuten la relacioacuten inversa por lo tanto hacemos justo lo anterior pero al reveacutes (deDetallePersona a Persona) con la diferencia que en la columna de Inverse

seleccionamos datosPersona

Ya tenemos las entidades y las relaciones creadas Si hacemos click en el botoacuten deEditor Style en la parte de abajo a la derecha del editor podemos ver de una formamaacutes clara la estructura que hemos disentildeado

Persistencia

64Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista general modelo de datos (2)

733 Probando el modelo

Una vez que tenemos el modelo creado pasamos a probarlo para comprobar que estaacute todocorrecto para ello abrimos la clase delegada (sesion04_ejemplo2AppDelegatem) yantildeadimos el siguiente fragmento de coacutedigo arriba del meacutetodoapplicationDidFinishLaunching

NSManagedObjectContext context = [self managedObjectContext]NSManagedObject persona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

[persona setValueJuan forKeynombre][persona setValueMartinez forKeyapellidos][persona setValue[NSNumber numberWithInt28] forKeyedad]

NSManagedObject detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersonainManagedObjectContextcontext]

[detallePersona setValueCalle Falsa 123 forKeydireccion][detallePersona setValueAlicante forKeylocalidad][detallePersona setValueEspantildea forKeypais][detallePersona setValue965656565 forKeytelefono]

[detallePersona setValuepersona forKeydatosPersona][persona setValuedetallePersona forKeydetalle]

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Persistencia

65Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Con este coacutedigo hemos creado un objeto Persona enlazado con otro objetoDetallePersona iexclComo puedes ver no hemos necesitado nada de coacutedigo SQLPrimero creamos un objeto del tipo NSManagedObjectContext que almacena el conextode Core Data Despueacutes creamos el objeto Persona en siacute con NSManagedObject A esteobjeto le pondremos los valores para cada una de las propiedades definidas en el modeloHacemos lo mismo con DetallePersona Finalmente relacionamos el objetoPersona con el objeto DetallePersona

Compilamos el proyecto comprobamos que todo funciona bien y no salen errores

Para comprobar que los datos se han almacenado correctamente en nuestro dispositivo losmostramos en la consola de la siguiente manera

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

intValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(------------------)

[fetchRequest release]

El coacutedigo de arriba lo escribiremos dentro del meacutetododidFinishLaunchingWithOptions de la clase delegada Lo que hace este coacutedigo esrecorrer todos los objetos almacenados en memoria y mostrarlos por consola Siarrancamos la aplicacioacuten veremos que se muestran todos los datos de los objetosalmacenados Cada vez que arranquemos la aplicacioacuten se mostraraacuten maacutes objetos ya quetambieacuten los creamos

Para listar los objetos usamos la clase NSFetchRequest Esta clase es equivalente aejecutar una sentencia Select de SQL Al objeto NSFetchRequest le asignamos laentidad que queremos listar (SELECT FROM ) en nuestro caso seraacute PersonaEl listado de los objetos se obtiene mediante el meacutetodo executeFetchRequest de laclase NSManagedObjectContext que hemos definido en el bloque anterior de coacutedigoEste meacutetodo devolveraacute un NSArray con todos los objetos NSManagedObject

Persistencia

66Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Salida de consola

NotaSi queremos ver queacute sentencias SQL estaacute ejecutando Core Data por debajo tenemos que activarel flag de debug para ello seleccionamos la opcioacuten Product gt Edit Scheme Enla columna de la izquierda seleccionamos el esquema que estamos utilizando para Debug y en laparte de la derecha en el cuadro de Arguments Passed on Launch antildeadimos 2paraacutemetros -comappleCoreDataSQLDebug y 1

Ahora arrancamos de nuevo la aplicacioacuten y veremos que en la consola apareceraacuten lassentencias SQL utilizadas por Core Data

()

INSERT INTO ZDETALLEPERSONA(Z_PK Z_ENT Z_OPT ZDATOSPERSONA ZTELEFONOZLOCALIDAD ZDIRECCION ZPAIS)VALUES( )CoreData sql SELECT 0 t0Z_PK t0Z_OPT t0ZTELEFONO t0ZLOCALIDADt0ZDIRECCION t0ZPAIS t0ZDATOSPERSONAFROM ZDETALLEPERSONA t0 WHERE t0Z_PK =

()

734 Generando los modelos automaacuteticamente

Generar los modelos a mano directamente desde el editor no es lo maacutes recomendado yaque podemos cometer errores de escritura errores de tipo etc La mejor manera dehacerlo es creando un archivo para cada entidad Esto se puede hacer desde 0 peroXCode tiene un generador de clases que es muy faacutecil de usar y ahorremos bastantetiempo iexclVamos a usarlo

Hacemos click sobre el raiz de nuestro proyecto y seleccionamos New File En lacolumna de la izquierda seleccionamos iOS gt Core Data y en el cuadro de laderecha NSManagedObject subclass Click en next En la siguiente pantallaseleccionamos nuestro modelo de datos sesion04_ejemplo2 y click en next Ahoranos aseguramos de seleccionar las dos entidades que hemos creado anteriormentePersona y DetallePersona click en next Seleccionamos el raiz de nuestroproyecto para guardar los ficheros que se generen y aceptamos

Persistencia

67Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ventana de seleccioacuten de entidad

Ahora veremos que xsource ha creado automaacuteticamente 4 ficheros nuevos dentro denuestro proyecto Personamh y DetallePersonamh con todos los atributosespecificados Estas nuevas clases que heredan de NSManagedObject seraacuten las queutilicemos a partir de ahora

Cambiamos el coacutedigo de la clase delegada por el siguiente

NSManagedObjectContext context = [self managedObjectContext]Persona datosPersona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

datosPersonanombre = JuandatosPersonaapellidos = MartinezdatosPersonaedad = [NSNumber numberWithInt28]

DetallePersona detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersona

inManagedObjectContextcontext]detallePersonadireccion = Calle Falsa 123

Persistencia

68Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

detallePersonalocalidad = AlicantedetallePersonapais = EspantildeadetallePersonatelefono = 965656565

detallePersonadatosPersona = datosPersonadatosPersonadetalle = detallePersona

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Listamos los datosNSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

integerValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(-----------------)[fetchRequest release]

Ejecutamos el proyecto y comprobamos que en la consola aparece lo mismo que antespero ahora tenemos el coacutedigo bastante maacutes claro

735 Creando la vista de tabla

Ahora vamos a mostrar los objetos que antes hemos listado por consola en una vista detabla UITableView Para ello creamos un UITableViewController

Click en New file gt UIViewController subclass En la siguiente pantallanos aseguramos de seleccionar Subclass of UITableViewController de esta formanos apareceraacuten ya todos los meacutetodos de la tabla creados por defecto Guardamos elarchivo con el nombre que queramos por ejemplo PersonasViewController

Abrimos ahora PersonasViewControllerh y antildeadimos 2 atributos a la clase

bull Un NSArray que se seraacute el listado de personas (objetos de la clase Persona)bull Una referencia a NSManagedObjectContext

El fichero PersonasViewControllerh quedaraacute de la siguiente manera

Persistencia

69Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

imports iniciales

interface PersonasViewController UITableViewController NSArray _listaPersonasNSManagedObjectContext _context

property (nonatomic retain) NSArray listaPersonasproperty (nonatomic retain) NSManagedObjectContext context

end

Ahora en el m importamos en el encabezado las clases Personah yDetallePersonah y en el meacutetodo viewDidLoad creamos el array listaPersonas contodos los objetos Persona de forma similar a cuando lo hicimos en el apartado anterior

import PersonasViewControllerh

import Personahimport DetallePersonah

implementation PersonasViewController

synthesize listaPersonas = _listaPersonassynthesize context = _context

En viewDidLoad

- (void)viewDidLoad

[super viewDidLoad]

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContext_context]

[fetchRequest setEntityentity]NSError errorselflistaPersonas = [_context executeFetchRequestfetchRequest

erroramperror]selftitle = Listado Personas[fetchRequest release]

Ahora completamos el resto de meacutetodos heredados de UITableViewController de lamisma forma que hicimos en sesiones anteriores

- (NSInteger)numberOfSectionsInTableView(UITableView )tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection(NSInteger)section

Persistencia

70Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Return the number of rows in the sectionreturn [_listaPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]

if (cell == nil) cell = [[[UITableViewCell alloc]

initWithStyleUITableViewCellStyleSubtitlereuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona p = (Persona )[_listaPersonas objectAtIndexindexPathrow]celltextLabeltext = pnombrecelldetailTextLabeltext = [NSString stringWithFormatd antildeos

[pedad intValue]]

return cell

Ahora para poder mostrar la tabla dentro de nuestra aplicacioacuten debemos asignarla dentrode la clase delegada para ello abrimos la vista MainWindowxib y arrastramos unacontroladora de navegacioacuten Navigation Controller a la lista de objetos de laizquierda La desplegamos y dentro de View Controller en la pestantildea de la derechade Identity Inspector escribimos el nombre de nuestra clase que tiene la vista de latabla PersonasViewController

Pantalla del disentildeador

Ahora abrimos el fichero sesion04_ejemplo2AppDelegateh y creamos un

Persistencia

71Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

UINavigationController dentro de la declaracioacuten de variables dejando el ficherocomo sigue

imports iniciales

interface sesion04_ejemplo2AppDelegate NSObject ltUIApplicationDelegategt

UINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet UINavigationControllernavController

property (nonatomic retain readonly) NSManagedObjectContextmanagedObjectContextproperty (nonatomic retain readonly) NSManagedObjectModelmanagedObjectModelproperty (nonatomic retain readonly) NSPersistentStoreCoordinatorpersistentStoreCoordinator

- (void)saveContext- (NSURL )applicationDocumentsDirectory

end

Dentro del m importamos la clase PersonasViewControllerh y completamos elsynthetize

synthesize navController = _navController

Y por uacuteltimo justo antes de la llamada al meacutetodo makeKeyAndVisible escribimos elsiguiente coacutedigo para asignar el contexto de la clase delegada alPersonasViewController

PersonasViewController root = (PersonasViewController ) [_navControllertopViewController]

rootcontext = [self managedObjectContext][selfwindow addSubview_navControllerview]

Ahora compilamos y ejecutamos nuestro proyecto y si todo ha ido bien deberiacutea de salirla tabla con los datos de las personas

Persistencia

72Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Listado de personas

736 Migraciones de bases de datos con Core Data

Una vez que tenemos disentildeada nuestra base de datos inicial iquestqueacute pasariacutea si queremosmodificarla para antildeadirle un campo maacutes a una tabla iquestQueacute haraacute Core Data para realizar lamigracioacuten de la versioacuten anterior a la actual

Siguiendo con el ejemplo anterior si despueacutes de ejecutar la aplicacioacuten en nuestrodispositivo o simulador modificamos la estructura de la base de datos antildeadiendo unatributo nuevo en la entidad DetallePersona por ejemplo codigoPostal con el tipoInteger 16 veremos que al volver a arrancar la aplicacioacuten nos apareceraacute un error entiempo de ejecucioacuten similar al siguiente

Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 Theoperationcouldnrsquot be completed(Cocoa error 134100) UserInfo=0x6b6c680 metadata=ltCFBasicHash 0x6b68380[0x1337b38]gttype = immutable dict count = 7entries =gt

2 ltCFString 0x6b70320 [0x1337b38]gtcontents =NSStoreModelVersionIdentifiers

=ltCFArray 0x6b706e0 [0x1337b38]gttype = immutable count = 1

values = (0 ltCFString 0x1332cd8 [0x1337b38]gtcontents =

)4 ltCFString 0x6b70350 [0x1337b38]gtcontents =

NSPersistenceFrameworkVersion=

Persistencia

73Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltCFNumber 0x6b6f2c0 [0x1337b38]gtvalue = +386 type =kCFNumberSInt64Type

6 ltCFString 0x6b70380 [0x1337b38]gtcontents =NSStoreModelVersionHashes =

ltCFBasicHash 0x6b707e0 [0x1337b38]gttype = immutable dict count =2entries =gt

1 ltCFString 0x6b70700 [0x1337b38]gtcontents = Persona =ltCFData 0x6b70740

[0x1337b38]gtlength = 32 capacity = 32 bytes =0x1c50e5a379ff0ddc3cda7b82a3934c5e 75649ac3c919beea2 ltCFString 0x6b70720 [0x1337b38]gtcontents = DetallePersona

=ltCFData 0x6b70790[0x1337b38]gtlength = 32 capacity = 32 bytes =0xb7ba2eb498f9a1acdd6630d56f6cd12c e8963dd3e488ba95

7 ltCFString 0x10e3aa8 [0x1337b38]gtcontents = NSStoreUUID =ltCFString 0x6b70510[0x1337b38]gtcontents = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E8 ltCFString 0x10e3948 [0x1337b38]gtcontents = NSStoreType =ltCFString 0x10e3958[0x1337b38]gtcontents = SQLite9 ltCFString 0x6b703b0 [0x1337b38]gtcontents =

_NSAutoVacuumLevel = ltCFString0x6b70830 [0x1337b38]gtcontents = 210 ltFString 0x6b703d0 [0x1337b38]gtcontents =

NSStoreModelVersionHashesVersion=ltCFNumber 0x6b61950 [0x1337b38]gtvalue = +3 type =

kCFNumberSInt32Type reason=The model used to open the store is incompatible with the oneused tocreate the store

metadata = NSPersistenceFrameworkVersion = 386NSStoreModelVersionHashes =

DetallePersona = ltb7ba2eb4 98f9a1ac dd6630d5 6f6cd12c f890007746b16f00

e8963dd3 e488ba95gtPersona = lt1c50e5a3 79ff0ddc 3cda7b82 a3934c5e 5cb4ee4b

4ac98838 75649ac3c919beeagt

NSStoreModelVersionHashesVersion = 3NSStoreModelVersionIdentifiers = (

)NSStoreType = SQLiteNSStoreUUID = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E_NSAutoVacuumLevel = 2

reason = The model used to open the store is incompatible with the

one usedto create the store

Este error es muy comuacuten cuando estamos desarrollando aplicaciones con Core DataBaacutesicamente nos dice que el modelo de base de datos que estaacute intentando cargar dememoria es distinto al que se indica en la aplicacioacuten esto es porque no hemos indicadoninguacuten meacutetodo de migracioacuten Realmente existen dos formas de solucionar este error

Persistencia

74Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Por un lado y soacutelo cuando estemos desarrollando y no tengamos ninguna versioacutende nuestra aplicacioacuten lanzada en la App Store podremos eliminar la base de datosdel dispositivo simulador simplemente borrando la aplicacioacuten de el Tambieacutenpodemos ejecutar el siguiente coacutedigo una soacutela vez [[NSFileManagerdefaultManager] removeItemAtURLstoreURL errornil]

bull En el caso de que estemos realizando una migracioacuten sobre una versioacuten ya publicadaen la App Store el meacutetodo anterior no nos serviriacutea ya que obligariacuteamos al usuario aborrar su aplicacioacuten del dispositivo con lo que todos los datos que tuviera seperderiacutean Esto no es viable por lo que necesitaremos usar otro meacutetodo para realizarla migracioacuten y solucionar el error de una forma maacutes limpia este meacutetodo es el quevamos a comentar a continuacioacuten

XCode soporta Versioning para Core Data esto es que podemos crear versiones de labase de datos de una forma sencilla si seguimos una serie de pasos

1) Antildeadimos las siguientes opciones en forma de NSDictionary a nuestro coacutedigo delpersistentStoreCoordinator

NSDictionary options = [NSDictionary dictionaryWithObjectsAndKeys[NSNumber numberWithBoolYES]NSMigratePersistentStoresAutomaticallyOption[NSNumber numberWithBoolYES]NSInferMappingModelAutomaticallyOptionnil]

Y modificamos el meacutetodo addPersistentStoreWithType pasaacutendole como paraacutemetro eldiccionario options

if ([__persistentStoreCoordinatoraddPersistentStoreWithTypeNSSQLiteStoreTypeconfigurationnil URLstoreURL optionsoptions erroramperror])

Con el coacutedigo anterior indicamos al persistentStoreCoordinator que la migracioacuten enel caso de que se deba de realizar sea de forma automaacutetica

AtencioacutenDeberemos de tener en cuenta que este tipo de migracioacuten que estamos estudiando en este puntosoacutelo es vaacutelida para cambios simples en Core Data lo que Apple llama como lightweightmigration Este tipo de migracioacuten admite una serie de cambios que se indican dentro de ladocumentacioacuten de Apple para cambios maacutes complejos deberemos realizar migracionespersonalizadas y mucho maacutes complejas El documento en donde Apple explica el tipo demigraciones que existen para Core Data y su forma de implementarlas lo podemos consultardesde esta web

2) Ahora deberemos de crear la versioacuten del esquema de Core Data dentro de XCode para

Persistencia

75Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ello seleccionamos el fichero del esquema sesion04_ejemplo2xcdatamodeld yseleccionamos en el menuacute principal Editor gt Add Model Version A la nueva versioacuten lallamaremos sesion04_ejemplo_2 (versioacuten 2) Una vez que hayamos pulsado sobreFinish se nos habraacute creado la nueva versioacuten del esquema dentro de la principal y estaraacutemarcada con un tick la versioacuten anterior

3) Una vez que tenemos creada la nueva versioacuten deberemos modificarla antildeadiendole elnuevo atributo Seleccionamos la versioacuten recieacuten creadasesion04_ejemplo_2xcdatamodel y antildeadimos a la entidad DetallePersona elatributo codigoPostal Atencioacuten Si este atributo lo hemos antildeadido anteriormentedeberemos de borrarlo de la versioacuten anterior y antildeadirlo en la segunda versioacuten

4) Ahora nos queda indicar a Core Data que queremos usar la versioacuten 2 de la base dedatos para nuestra aplicacioacuten Para ello seleccionamos el esquema principal (el raiz)sesion04_ejemplo2xcdatamodeld y en la ventana de la derecha en la pestantildea de FileInspector seleccionamos como Versioacuten actual la versioacuten 2 esto lo hacemos dentro delapartado de Versioned Core Data Model Automaacuteticamente veremos que el siacutembolo detick cambia a la versioacuten 2

5) Ahora ya podemos volver a compilar y veremos que no nos aparece el error y funcionatodo seguacuten lo esperado

Magical RecordExisten varias librerias de coacutedigo libre que facilitan enormemente el uso de Core Data ennuestras aplicaciones iOS Una de ellas es Magical Record Mediante esta libreriacutea podremos usarCore Data sin tener que preocuparnos de todo el proceso de configuracioacuten en las controladorasde la configuracioacuten de migraciones etc Su instalacioacuten en un proyecto de XCode es muy faacutecil yaltamente recomendable ya que proporciona una API muy clara y sencilla de usar

Persistencia

76Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios

81 Usando User Defaults (1 punto)

En este ejercicio vamos a usar las variables de usuario para almacenar y mostrar los datosdel dispositivo que estamos usando y las veces que hemos arrancado la aplicacioacuten

Crear un proyecto nuevo usando la plantilla Single View Application con el nombreejercicio-userdefaults-ios Se debe de ejecutar en iPhone e iPad (Universal) Comoidentificador de empresa escribiremos esuajtech y como prefijo de clase UA Debe detener el ARC activado (desmarcar el resto de opciones)

Ayuda meacutetodos de UIDevicePara obtener los datos del dispositivo usaremos el singleton UIDevice estos son algunos de losmeacutetodos que podemos usar [[UIDevice currentDevice] name] [[UIDevicecurrentDevice] systemName] [[UIDevice currentDevice]systemVersion] [[UIDevice currentDevice] model] [[UIDevicecurrentDevice] localizedModel]

82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)

En este ejercicio disentildearemos un modelo de datos de dos tablas relacionadas entre ellasantildeadiremos datos por coacutedigo y los mostraremos en una vista de tabla La base de datosque vamos a implementar constaraacute de dos tablas por un lado una que contendraacute los tiacutetulosde series y otra que tendraacute los detalles A la primera la llamaremos Serie y la segundaDetalleSerie Para realizar correctamente el ejercicio deberemos seguir los siguientespasos

1) Crear un proyecto nuevo usando la plantilla Master-Detail Application con elnombre ejercicio-coredata-ios Se debe de ejecutar en iPhone como identificador deempresa escribiremos esuajtech y como prefijo de clase UA Debe de tener el ARCactivado y la opcioacuten de Use Core Data (desmarcar el resto de opciones)

2) Disentildear el siguiente modelo de datos

Persistencia

77Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esquema modelo de datos

3) Revisar el fichero UAAppDelegatem y comprobar que tenemos todos los meacutetodosnecesarios para que funcione Core Data en nuestra aplicacioacuten

4) Modificar el fichero UAMasterViewControllerm para que se muestren los tiacutetulos delas series en la tabla

5) Probar la aplicacioacuten antildeadiendo unos tiacutetulos de prueba

83 Migrando datos con Core Data (05 puntos)

Una vez que hemos disentildeado la Base de Datos con Core Data vamos a modificarlaantildeadiendo un atributo maacutes a la tabla de DetalleSerie valoracion

a) iquestQueacute pasa si cambiamos el modelo de datos y arrancamos la aplicacioacuten iquestQueacute errornos da y por queacute

b) iquestCoacutemo podemos evitar este tipo de errores cuando estemos desarrollando iquestY siestamos en produccioacuten con la aplicacioacuten ya a la venta

c) Crea una nueva versioacuten del modelo

d) Modifica el coacutedigo necesario dentro de la clase UAAppDelegate para evitar este tipo deerrores cuando estemos en un entorno de produccioacuten

e) Vuelve a arrancar la aplicacioacuten y comprueba que funciona bien

Persistencia

78Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Persistencia

79Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

  • 1 Persistencia en Android ficheros y SQLite
    • 11 Introduccioacuten
    • 12 Manejo de ficheros tradicionales en Android
      • 121 Apertura de ficheros
      • 122 Ficheros como recursos
      • 123 Operar con ficheros
      • 124 Almacenar datos en la tarjeta de memoria
        • 13 Base de datos SQLite
          • 131 Content Values
          • 132 Cursores
          • 133 Trabajar con bases de datos SQLite
          • 134 La clase SQLiteOpenHelper
          • 135 Crear una base de datos sin SQLiteHelper
          • 136 Realizar una consulta
          • 137 Extraer resultados de un cursor
          • 138 Antildeadir actualizar y borrar filas
              • 2 Ejercicios - Persistencia en Android ficheros y SQLite
                • 21 Uso de ficheros (05 puntos)
                • 22 Persistencia con ficheros (05 puntos)
                • 23 Base de datos SQLiteOpenHelper (05 puntos)
                • 24 Base de datos insercioacuten y borrado (05 puntos)
                • 25 Base de datos probar nuestro adaptador (05 puntos)
                • 26 Base de datos cambios en la base de datos (05 puntos)
                  • 3 Persistencia en Android proveedores de contenidos y SharedPreferences
                    • 31 Shared Preferences
                      • 311 Guardar Shared Preferences
                      • 312 Leer Shared Preferences
                      • 313 Interfaces para Shared Preferences
                      • 314 Definiendo una pantalla de preferencias con un layout en XML
                      • 315 Controles nativos para preferencias
                      • 316 Actividades de preferencias
                      • 317 Shared Preference Change Listeners
                        • 32 Proveedores de contenidos
                          • 321 Proveedores nativos
                          • 322 Proveedores propios crear un nuevo proveedor de contenidos
                          • 323 Proveedores propios crear la interfaz de consultas
                          • 324 Proveedores propios tipo MIME
                          • 325 Proveedores propios registrar el proveedor
                          • 326 Content Resolvers
                          • 327 Otras operaciones con Content Resolvers
                              • 4 Ejercicios - Persistencia en Android proveedores de contenidos y SharedPreferences
                                • 41 Compartir datos entre actividades con Shared Preferences (075 puntos)
                                • 42 Actividad de preferencias (075 puntos)
                                • 43 Proveedor de contenidos propio (075 puntos)
                                • 44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)
                                  • 5 Persistencia de datos en iOS Ficheros y SQLite
                                    • 51 Introduccioacuten
                                    • 52 Ficheros plist (Property Lists)
                                      • 521 Leyendo datos desde ficheros plist
                                      • 522 Escribiendo datos en ficheros plist
                                        • 53 SQLite
                                          • 531 Herramientas disponibles
                                          • 532 Lectura de datos
                                          • 533 Mostrando los datos en una tabla
                                              • 6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios
                                                • 61 Creando y leyendo un fichero plist (15 puntos)
                                                • 62 Implementar vista de detalle de la serie (05 puntos)
                                                • 63 Uso baacutesico de una base de datos SQLite (1 punto)
                                                  • 7 Persistencia de datos en iOS User Defaults y Core Data
                                                    • 71 Introduccioacuten
                                                    • 72 User Defaults
                                                    • 73 Core Data
                                                      • 731 Creando el proyecto
                                                      • 732 Definiendo el modelo de datos
                                                      • 733 Probando el modelo
                                                      • 734 Generando los modelos automaacuteticamente
                                                      • 735 Creando la vista de tabla
                                                      • 736 Migraciones de bases de datos con Core Data
                                                          • 8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios
                                                            • 81 Usando User Defaults (1 punto)
                                                            • 82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)
                                                            • 83 Migrando datos con Core Data (05 puntos)
Page 12: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de

meacutetodo son los siguientes

bull Un booleano opcional que permite indicar si el resultado deberiacutea contener tan soacutelovalores uacutenicos

bull El nombre de la tabla sobre la que realizar la consultabull Un array de cadenas que contenta un listado de las columnas que deben incluirse en el

resultadobull Una claacuteusula where que defina las filas que se deben obtener de la tabla Se puede

utilizar el comodiacuten el cual seraacute reemplazado por los valores que se pasen a traveacutesdel siguiente paraacutemetro

bull Un array de cadenas correspondientes a argumentos de seleccioacuten que reemplazaraacutenlos siacutembolos en la claacuteusula where

bull Una claacuteusula group by que define coacutemo se agruparaacuten las filas obtenidas comoresultado

bull Un filtro having que indique que grupos incluir en el resultado en el caso en el que sehaya hecho uso del paraacutemetro anterior

bull Una cadena que describa la ordenacioacuten que se llevaraacute a cabo sobre las filas obtenidascomo resultado

bull Una cadena opcional para definir un liacutemite en el nuacutemero de resultados a devolver

NotaComo se puede observar la mayoriacutea de estos paraacutemetros hacen referencia al lenguaje SQLTratar este tema queda fuera de los objetivos de este curso por lo que en nuestros ejemplos ledaremos en la mayoriacutea de las ocasiones un valor null a estos paraacutemetros

El siguiente coacutedigo muestra un ejemplo de consultas utilizando diversos paraacutemetros

Devolver los valores de las columnas uno y tres para todas las filas sin duplicadosString[] columnas = new String[] CLAVE_ID CLAVE_COL1 CLAVE_COL3

Cursor todasFilas = dbquery(true TABLA_BASE_DATOS columnas null nullnull null

null null)

Devolvemos los valores de todas las columnas para aquellas filas cuyovalor de la columna 3 sea igual al de una determinada variable Ordenamos las filasseguacuten el valor de la columna 5 Como queremos los valores de todas las columnas le damos al paraacutemetro correspondiente a las columnas a devolver el valor null En este caso no se ha hecho uso del primer paraacutemetro booleano que esoptativoString where = CLAVE_COL3 + = + valorString orden = CLAVE_COL5Cursor miResultado = dbquery(TABLA_BASE_DATOS null where null nullnull orden)

137 Extraer resultados de un cursor

Persistencia

12Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El primer paso para obtener resultados de un cursor seraacute desplazarnos a la posicioacuten apartir de la cual queremos obtener los datos usando cualquiera de los meacutetodosespecificados anteriormente (como por ejemplo moveToFirst o moveToPosition) Unavez hecho esto hacemos uso de meacutetodos get[TIPO] pasando como paraacutemetro el iacutendice dela columna Esto tendraacute como resultado la devolucioacuten del valor para dicha columna de lafila actualmente apuntada por el cursor

String valor = miResultadogetString(indiceColumna)

A continuacioacuten podemos ver un ejemplo maacutes completo en el que se va iterando a lo largode los resultados apuntados por un cursor extrayendo y sumando los valores de unacolumna de flotantes

int COLUMNA_VALORES_FLOTANTES = 2Cursor miResultado = dbquery(Tabla null null null null nullnull)float total = 0

Nos aseguramos de que se haya devuelto al menos una filaif (miResultadomoveToFirst())

Iteramos sobre el cursordo

float valor =miResultadogetFloat(COLUMNA_VALORES_FLOTANTES)

total += valor while (miResultadomoveToNext())

float media = total miResultadogetCount()

NotaDebido a que las columnas de las bases de datos en SQLite son debilmente tipadas podemosrealizar castings cuando sea necesario Por ejemplo los valores guardados como flotantes en labase de datos podriacutean leerse maacutes adelante como cadenas

138 Antildeadir actualizar y borrar filas

La clase SQLiteDatabase proporciona los meacutetodos insert delete y update queencapsulan las instrucciones SQL requeridas para llevar a cabo estas acciones Ademaacutesdisponemos de la posibilidad de utilizar el meacutetodo execSQL el cual nos permite ejecutarcualquier sentencia SQL vaacutelida en nuestras tablas de la base de datos en el caso en el quequeramos llevar a cabo estas (u otras) operaciones manualmente

NotaCada vez que modifiques los valores de la base de datos puedes actualizar un cursor que se puedaver afectado por medio del meacutetodo refreshQuery de la clase Cursor

Para insertar una nueva fila es necesario construir un objeto de la clase ContentValues

y usar su meacutetodo put para proporcionar valor a cada una de sus columnas Para insertar la

Persistencia

13Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

nueva fila pasaremos como paraacutemetro este objeto del tipo ContentValues al meacutetodoinsert de la instancia de la base de datos junto por supuesto con el nombre de la tablaA continuacioacuten se muestra un ejemplo

Crear la nueva filaContentValues nuevaFila = new ContentValues()

Asignamos valores a cada columnanuevaFilaput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas ]

Insertar la nueva fila en la tabladbinsert(TABLA_BASE_DATOS null nuevaFila)

NotaLos ficheros como imaacutegenes o ficheros de audio no se suelen almacenar dentro de tablas en unabase de datos En estos casos se suele optar por almacenar en la base de datos una cadena con laruta al fichero o una URI

La operacioacuten de actualizar una fila tambieacuten puede ser llevada a cabo por medio del usode la clase ContentValues En este caso deberemos llamar al meacutetodo update de lainstancia de la base de datos pasando como paraacutemetro el nombre de la tabla el objetoContentValues y una claacuteusula where que especifique la fila o filas a actualizar tal comose puede ver en el siguiente ejemplo

Creamos el objeto utilizado para definir el contenido a actualizarContentValues valoresActualizar = new ContentValues()

Asignamos valores a las columnas correspondientesvaloresActualizarput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas a actualizar ]

String where = CLAVE_ID + = + idFila

Actualizamos la fila con el iacutendice especificado en la instruccioacutenanteriordbupdate(TABLA_BASE_DATOS valoresActualizar where null)

Por uacuteltimo para borrar una fila simplemente llamaremos al meacutetodo delete de lainstancia de la base de datos especificando en este caso el nombre de la tabla que se veraacuteafectada por la operacioacuten y una claacuteusula where que utilizaremos para especificar las filasque queremos borrar Un ejemplo podriacutea ser el siguiente

dbdelete(TABLA_BASE_DATOS CLAVE_ID + = + idFila null)

Persistencia

14Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

2 Ejercicios - Persistencia en Android ficheros y SQLite

21 Uso de ficheros (05 puntos)

En este ejercicio vamos a crear una aplicacioacuten que muestre un listado de cadenas porpantalla Estas cadenas se almacenaraacuten en un fichero de texto privado para la aplicacioacutenPodremos antildeadir nuevas cadenas a partir de un cuadro de edicioacuten presente en la interfazPartiremos del proyecto Ficheros proporcionado en las plantillas de la aplicacioacuten Debesseguir los siguientes pasos

bull Antildeadir un manejador al botoacuten de la actividad principal para que cada vez que seapulsado se guarde en un fichero de texto El fichero se llamaraacute ficherotxt Al abrirel fichero para escritura se utilizaraacute el paraacutemetro ContextMODE_APPEND con lo quecada nueva cadena se antildeadiraacute al final del fichero en el caso en el que eacuteste ya existieraPara escribir en el fichero utilizaremos el meacutetodo writeBytes del objetoDataOutputStream correspondiente

bull Al pulsar el botoacuten tambieacuten se deberaacute borrar el contenido del elemento EditText (leasignamos a la vista una cadena vaciacutea con el meacutetodo setText)

bull Ejecutamos la aplicacioacuten e introducimos algunas liacuteneas en el fichero Vamos acomprobar ahora que todo ha funcionado correctamente Para ello accedemos alsistema de ficheros de nuestro dispositivo ejecutando el comando adb shell

dentro de la carpeta platform-tools de nuestra carpeta de instalacioacuten del SDK deAndroid

bull Comprueba que se ha creado el fichero ficherotxt en la carpetadatadataesuajtechandroidficherosfiles Examina su contenido ejecutando cat

ficherotxt Si algo no ha salido bien siempre podraacutes eliminar el fichero con rm

ficherotxtbull En la interfaz de la actividad se ha incluido una vista de tipo TextView debajo del

botoacuten Antildeade un manejador para dicha vista de tal forma que cada vez que se hagaclick sobre ella se muestre el contenido del fichero ficherotxt Para ello leeremos elfichero liacutenea a liacutenea antildeadiendo cada una al TextView por medio del meacutetodo append

Persistencia

15Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Interfaz de la aplicacioacuten Ficheros

22 Persistencia con ficheros (05 puntos)

Seguramente habraacutes observado que si abandonas la aplicacioacuten anterior (pulsando el botoacutenBACK del dispositivo) al volver a ejecutarla el contenido del TextView ha desaparecidoDebes volver a pulsar sobre la vista para que se vuelva a mostrar el contenido del ficheroHaz las modificaciones pertinentes para que esto no sea asiacute es decir para que cuandovuelva a mostrarse la actividad tras haber sido eliminada de la memoria se muestre elTextView tal cual se veiacutea antes de abandonar la aplicacioacuten

AvisoEn este ejercicio no se estaacute pidiendo que cargues el contenido del fichero en el TextView nadamaacutes arrancar la aplicacioacuten Puede darse el caso de que lo que esteacute mostrando el TextViewcuando la actividad sea eliminada de memoria no se corresponda con el contenido actualizado delfichero

NotaPara poder completar el ejercicio deberaacutes repasar los manejadores de evento de la primera sesioacutendel moacutedulo de Android relacionados con el ciclo de vida de ejecucioacuten de actividades

23 Base de datos SQLiteOpenHelper (05 puntos)

En las plantillas de la aplicacioacuten se incluye la aplicacioacuten BaseDatos En el proyecto de laaplicacioacuten se incluye el esqueleto de una clase MiAdaptadorBD que tendremos quecompletar Se trata de un patroacuten que nos va a permitir acceder a una base de datos de

Persistencia

16Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

usuarios dentro de la aplicacioacuten sin necesidad de hacer uso de coacutedigo SQL

La clase MiAdaptadorBD incluye a su vez otro patroacuten en este caso implementado comouna subclase de SQLiteOpenHelper Eacuteste nos obliga a definir queacute ocurre cuando la basede datos todaviacutea no existe y debe ser creada y queacute ocurre si ya existe pero debe seractualizada porque ha cambiado de versioacuten Asiacute el SQLiteOpenHelper queimplementemos en este caso MiOpenHelper nos devolveraacute siempre una base de datosseparaacutendonos de la loacutegica encargada de comprobar si la base de datos existe o no

En este primer ejercicio se pide hacer lo siguiente

bull Ejecutar la sentencia de creacioacuten de bases de datos (la tenemos declarada comoconstante de la clase) en el meacutetodo MiOpenHelperonCreate

bull Implementar tambieacuten el meacutetodo onUpgrade Idealmente eacuteste deberiacutea portar las tablasde la versioacuten antigua a la versioacuten nueva copiando todos los datos Nosotros vamos aeliminar directamente la tabla que tenemos con la sentencia SQL DROP TABLE IF

EXISTS + NOMBRE_TABLA y volveremos a crearlabull En el constructor de MiAdaptadorBD debemos obtener en el campo db la base de

datos utilizando MiOpenHelper

24 Base de datos insercioacuten y borrado (05 puntos)

Continuamos trabajando con el proyecto anterior En este ejercicio completaremos elcoacutedigo relacionado con las sentencias de insercioacuten y borrado Para realizar la insercioacutenvamos a hacer uso de un mecanismo que no hemos visto en la sesioacuten de teoriacutea y queconsiste en utilizar una sentencia de insercioacuten SQL precompilada que se completaraacute conlos valores concretos a insertar en la base de datos justo antes de realizar dicha insercioacutenLa ventaja de las sentencias compiladas es que evitan que se produzcan ataques deinyeccioacuten de coacutedigo

Como se puede observar se ha incluido un atributo a la clase que representa la insercioacutenSQL

private static final String INSERT = insert into + NOMBRE_TABLA +(+COLUMNAS[1]+) values ()

En esta sentencia no se indica ninguacuten valor concreto para la columna nombre En el lugaren el que deberiacutean aparecer los valores de dicho campo se ha escrito simplemente unsiacutembolo de interrogacioacuten Tambieacuten se ha antildeadido una instancia de la claseSQLiteStatement como parte de los atributos de la clase

private SQLiteStatement insertStatement

Realizamos los siguientes pasos

bull En el constructor de la clase MiAdaptadorBD llevamos a cabo la compilacioacuten de lasentencia

thisinsertStatement = thisdbcompileStatement(INSERT)

Persistencia

17Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Una vez hecho esto cada vez que deseemos insertar un nuevo usuario en la base dedatos deberemos dar valores concretos a la columna nombre y ejecutar la sentenciaPara ello debemos antildeadir el siguiente coacutedigo dentro del meacutetodo insert de la claseMiAdaptadorBD

thisinsertStatementbindString(1 nombreUsuario)return thisinsertStatementexecuteInsert()

bull Por uacuteltimo completamos el coacutedigo del meacutetodo deleteAll cuyo contenido es eliminara todos los usuarios de la base de datos Para que ademaacutes se devuelva el nuacutemero defilas afectadas podemos insertar la siguiente liacutenea en dicho meacutetodo

return dbdelete(NOMBRE_TABLA null null)

Una vez hechos todos estos cambios podremos utilizar la clase MiAdaptadorBD parahacer operaciones con la base de datos de usuarios de manera transparente

25 Base de datos probar nuestro adaptador (05 puntos)

Antildeade coacutedigo en la actividad Main para eliminar todos los usuarios de la base de datosantildeadir dos cualesquiera y listarlos por medio de la vista de tipo TextView que seencuentra en el layout de dicha actividad

Podemos comprobar mediante la liacutenea de comandos (comando adb shell) que la basede datos ha sido creada Para ello puede ser uacutetil hacer uso de los siguientes comandos

cd datadataesuajtechandroidbasedatosdatabasessqlite3 misusuariosdbsqlitegt schemasqlitegt tablessqlitegt select from usuarios

26 Base de datos cambios en la base de datos (05 puntos)

Ahora vamos a cambiar en la clase MiAdaptadorBD el nombre de la segunda columnaque en lugar de nombre se va a llamar nombres Ejecutamos la aplicacioacuten y comprobamosque sale con una excepcioacuten Comprueba por medio de LogCat cuaacutel ha sido el erroriquestCoacutemo lo podemos solucionar

NotaPista conforme hemos programado la clase MiAdaptadorBD y siguiendo el patroacuten de disentildeode SQLiteOpenHelper es posible arreglar el problema simplemente modificando el valor deun campo

Persistencia

18Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

3 Persistencia en Android proveedores de contenidos ySharedPreferences

31 Shared Preferences

Comenzamos esta sesioacuten hablando de una de las teacutecnicas maacutes simples de persistencia enAndroid junto al uso de ficheros Shared Preferences Se trata de un mecanismo ligeropara almacenar datos basado en pares clave-valor utilizado normalmente para guardarpreferencias u opciones de la aplicacioacuten Tambieacuten puede ser utilizado para almacenar elestado de la interfaz graacutefica para contemplar el caso de que nuestra actividad debafinalizar abruptamente su ejecucioacuten Otro posible uso es el de compartir informacioacutenentre componentes de una misma aplicacioacuten

NotaLos Shared Preferences nunca son compartidos entre diferentes aplicaciones

Este meacutetodo se basa en el uso de la clase SharedPreferences Es posible utilizarla paraguardar datos en muy diversos formatos booleanos cadenas flotantes y enteros

311 Guardar Shared Preferences

Para crear o modificar un Shared Preference llamamos al meacutetodogetSharedPreferences del contexto de la aplicacioacuten pasando como paraacutemetro elidentificador del conjunto de valores de preferencias con el que queremos trabajar Pararealizar la modificacioacuten del valor de un Shared Preference hacemos uso de la claseSharedPreferencesEditor Para obtener el objeto editor invocamos el meacutetodo edit

del objeto Shared Preferences que queremos modificar Los cambios se guardan medianteel meacutetodo commit Veamos un ejemplo

int modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences = getSharedPreferences(Mispreferencias modo)

Obtenemos un editor para modificar las preferenciasSharedPreferencesEditor editor = misSharedPreferencesedit()

Guardamos nuevos valores en el objeto SharedPreferenceseditorputBoolean(isTruetrue)editorputFloat(unFloat10)editorputInt(numeroEntero12)editorputLong(otroNumero2)editorputString(unaCadenavalor)

Guardamos los cambioseditorcommit()

Persistencia

19Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

312 Leer Shared Preferences

Para leer Shared Preferences utilizamos el meacutetodo getSharedPreferences tal como seha comentado anteriormente Pasamos como paraacutemetro el identificador del conjunto deShared Preferences al que deseamos acceder Una vez hecho esto ya podemos hacer usode meacutetodos de tipo get para acceder a preferencias individuales Hay diferentes meacutetodosget para diferentes tipos de datos Cada uno de ellos requiere como paraacutemetro la claveque identifica la preferencia cuyo valor deseamos obtener y un valor por defecto el cualse usaraacute en el caso concreto en el que no existiera la preferencia con la clave pasada comoprimer paraacutemetro Podemos ver un ejemplo a continuacioacuten

public static String MIS_PREFS = MIS_PREFS

public void cargarPreferences() Obtener las preferencias almacenadasint modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences =

getSharedPreferences(MIS_PREFS modo)

boolean isTrue = misSharedPreferencesgetBoolean(isTruefalse)float unFloat = misSharedPreferencesgetFloat(unFloat0)int numeroEntero = misSharedPreferencesgetInt(numeroEntero0)long otroNumero = misSharedPreferencesgetLong(otroNumero0)String unaCadena = misSharedPreferencesgetString(unaCadena)

313 Interfaces para Shared Preferences

Android proporciona una plataforma basada en XML para la creacioacuten de interfacesgraacuteficas para el manejo de preferencias Estas interfaces tendraacuten un aspecto similar al delas del resto de aplicaciones del sistema Al utilizarla nos estaremos asegurando de quenuestras actividades para el manejo de preferencias seraacuten consistentes con las del resto deaplicaciones del sistema De esta forma los usuarios estaraacuten familiarizados con el manejode esta parte de nuestra aplicacioacuten

La plataforma consiste en tres elementos

bull Layout de la actividad de preferencias se trata de un archivo XML que definiraacute lainterfaz graacutefica de la actividad que muestre los controles para modificar los valores delas preferencias Permite especificar las vistas a mostrar los posibles valores que sepueden introducir y a queacute clave de Shared Preferences se corresponde cada vista

bull Actividad de preferencias una subclase de PreferenceActivity que secorresponderaacute con la pantalla de preferencias de nuestra aplicacioacuten

bull Shared Preference Change Listener una implementacioacuten de la claseonSharedPreferenceChangeListener que actuaraacute en el caso en el que cambie elvalor de alguna Shared Preference

314 Definiendo una pantalla de preferencias con un layout en XML

Persistencia

20Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esta es sin duda la parte maacutes importante de una actividad de preferencias Se trata de unarchivo XML utilizado para definir varios aspectos de dicha actividad Al contrario queen el caso de los archivos de layout para interfaces graacuteficas que vimos en la sesioacutencorrespondiente del moacutedulo de Android estos archivos de layout se guardan en la carpetaresxml de los recursos de la aplicacioacuten

Aunque conceptualmente estos layouts son similares a los utilizados para definirinterfaces graacuteficas los layouts de actividades de preferencias utilizan un conjuntoespecializado de componentes especiacuteficos para este tipo de actividades Estaacuten pensadospara proporcionar interfaces con un aspecto similar al del resto de actividades depreferencias del sistema Estos controles se describen en maacutes detalle en la siguienteseccioacuten

Los layout de preferencias contendraacuten un elemento PreferenceScreen

ltxml version=10 encoding=utf-8gtltPreferenceScreen

xmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceScreengt

Se pueden antildeadir maacutes elementos de este tipo si se desea Al hacerlo eacutestos serepresentaraacuten como un elemento seleccionable en un listado que mostraraacute una nuevaventana de preferencias en caso de que sea seleccionado

Dentro de cada elemento PreferenceScreen podemos incluir tantos elementos de tipoPreferenceCategory y elementos especiacuteficos para antildeadir controles como se desee Loselementos PreferenceCategory son utilizados para dividir cada pantalla de preferenciasen subcategoriacuteas mediante una barra de tiacutetulo Su sintaxis es la siguiente

ltPreferenceCategoryandroidtitle=My Preference Categorygt

ltPreferenceCategorygt

Una vez dividida la pantalla en subcategoriacuteas tan soacutelo quedariacutea antildeadir los elementoscorrespondientes a los controles especiacuteficos que veremos en la siguiente seccioacuten Losatributos de los elementos XML para estos controles pueden variar aunque existe unconjunto de atributos comunes

bull androidkey la clave de Shared Preference que se usaraacute para guardar el valor delcontrol

bull androidtitle texto a mostrar para describir la preferenciabull androidsummary una descripcioacuten maacutes larga a mostrar debajo del texto definido con

el campo anteriorbull androiddefaultValue el valor por defecto que se mostraraacute inicialmente y tambieacuten

el que finalmente se guardaraacute si ninguacuten otro valor fue asignado a este campo

El siguiente listado muestra una pantalla de preferencias muy simple con una uacutenicacategoriacutea y un uacutenico control (un check box)

ltxml version=10 encoding=utf-8gt

Persistencia

21Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltPreferenceScreenxmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceCategory

androidtitle=Mi categoriacuteagtltCheckBoxPreference

androidkey=MI_CHECK_BOXandroidtitle=Preferencia Check Boxandroidsummary=Descripcioacuten de la preferencia

Check BoxandroiddefaultValue=true

gtltPreferenceCategorygt

ltPreferenceScreengt

Esta pantalla de preferencias se mostrariacutea de la siguiente forma

Ejemplo sencillo de ventana de preferencias

315 Controles nativos para preferencias

Android incluye diferentes controles que podemos antildeadir a nuestras ventanas depreferencias por medio de los elementos XML que se indican a continuacioacuten

bull CheckBoxPreference un check box estaacutendar que puede ser utilizado parapreferencias de tipo booleano

bull EditTextPreference permite al usuario introducir una cadena como valor para unapreferencia Al seleccionar el texto se mostraraacute un diaacutelogo con el que introducir elnuevo valor

bull ListPreference el equivalente a un Spinner Seleccionar este elemento de lainterfaz mostraraacute un diaacutelogo con las diferentes opciones que es posible seleccionar Seutilizan arrays para asociar valores y textos a las opciones de la lista

bull RingtonePreference un tipo especiacutefico de preferencia de tipo listado que permiteseleccionar entre diferentes tonos de teleacutefono Esta opcioacuten es particularmente uacutetil en

Persistencia

22Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

el caso en el que se esteacute creando una pantalla de preferencias para configurar lasopciones de notificacioacuten de una aplicacioacuten o componente de la misma

Tambieacuten es posible crear nuestros propios controles personalizados definiendo unasubclase de Preference (o cualquiera de sus subclases) Se puede encontrar maacutesinformacioacuten en la documentacioacuten de AndroidhttpdeveloperandroidcomreferenceandroidpreferencePreferencehtml

316 Actividades de preferencias

Para mostrar una pantalla de preferencias debemos definir una subclase dePreferenceActivity

public class MisPreferencias extends PreferenceActivity

La interfaz graacutefica de la interfaz se puede crear a partir del layout en el meacutetodo onCreatemediante una llamada a addPreferencesFromResource

Overridepublic void onCreate(Bundle savedInstaceState)

superonCreate(savedInstanceState)addPreferencesFromResource(Rxmlmilayout)

Como en el caso de cualquier otra actividad la actividad de preferencias que hayamosdefinido debe ser incluida en el Manifest de la aplicacioacuten

ltactivity androidname=MisPreferenciasandroidlabel=Mis preferenciasgt

ltactivitygt

Eso es todo lo que necesitamos para crear este tipo de actividades y mostrar una ventanacon las preferencias definidas en el layout Esta actividad puede ser iniciada comocualquier otra mediante un Intent ya sea a traveacutes de una llamada a startActivity obien a startActivityForResult

Intent i = new Intent(this MisPreferenciasclass)startActivityForResult(i MOSTRAR_PREFERENCIAS)

Los valores para las diferentes preferencias de la actividad son almacenadas en elcontexto de la aplicacioacuten Esto permite a cualquier componente de dicha aplicacioacutenincluyendo a cualquier actividad y servicio de la misma acceder a los valores tal como semuestra en el siguiente coacutedigo de ejemplo

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto) PENDIENTE hacer uso de meacutetodos get para obtener los valores

317 Shared Preference Change Listeners

Persistencia

23Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El uacuteltimo elemento que nos queda por examinar en esta plataforma de Shared Preferenceses la interfaz onSharedPreferenceChangeListener que es utilizada para invocar unevento cada vez que una preferencia es antildeadida eliminada o modificada Para registrarListeners usamos un coacutedigo como el que se muestra a continuacioacuten en el que la parte maacutesimportante es sin duda el meacutetodo onSharedPreferenceChanged dentro del cualdeberemos determinar queacute preferencia ha cambiado de estado a partir de sus paraacutemetrospara llevar a cabo las acciones oportunas

public class MiActividad extends Activity implementsOnSharedPreferenceChangeListener

Overridepublic void onCreate(Bundle SavedInstanceState)

Registramos este objetoOnSharedPreferenceChangeListener

Context contexto = getApplicationContext()SharedPreferences prefs =

PreferenceManagergetDefaultSharedPreferences(contexto)prefsregisterOnSharedPreferenceChangeListener(this)

public void onSharedPreferenceChanged(SharedPreferences prefsString clave)

PENDIENTE comprobar queacute clave concreta ha cambiado ysu nuevo valor

para modificar la interfaz de una actividad o elcomportamiento de

alguacuten componente

Otra forma maacutes sencilla de controlar los cambios en las preferencias es registrar unlistener en el constructor de la actividad principal o aquella que haga uso de los valores depreferencias de la forma

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto)

mListener = new OnSharedPreferenceChangeListener() public void onSharedPreferenceChanged(SharedPreferences

sharedPreferences String key)

if( keyequals(MICLAVE) )boolean valor =

sharedPreferencesgetBoolean(MICLAVE false)

prefsregisterOnSharedPreferenceChangeListener(mListener)

NotaEn este segundo ejemplo mListener es una variable miembro de la clase Para este caso esnecesario registrar el listener de esta forma porque sino el garbage collector lo eliminariacutea (debidoa particularidades en la programacioacuten de los listeners asociados a las SharedPreferences)

Persistencia

24Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

32 Proveedores de contenidos

Los proveedores de contenidos o ContentProvider proporcionan una interfaz parapublicar y consumir datos identificando la fuente de datos con una direccioacuten URI queempieza por content Son una forma maacutes estaacutendar que los adaptadores (como el quevimos en la sesioacuten anterior en la seccioacuten dedicada a SQLite) de desacoplar la capa deaplicacioacuten de la capa de datos

Podemos encontrar dos tipos principales de proveedores de contenidos en Android Losproveedores nativos son proveedores que el propio sistema nos proporciona para poderacceder a datos del dispostivo incluyendo audio viacutedeo imaacutegenes informacioacuten personaletc Por otra parte tambieacuten es posible que creemos nuestros propios proveedores con talde permitir a nuestra aplicacioacuten compartir datos con el resto del sistema En esta sesioacutenhablaremos de ambos tipos

321 Proveedores nativos

Android incluye una serie de proveedores de contenidos que nos pueden proporcionarinformacioacuten de diferentes tipos informacioacuten sobre nuestros contactos informacioacuten delcalendario e incluso ficheros multimedia Ejemplos de estos proveedores de contenidosnativos son el Browser CallLog ContactsContract MediaStore Settings y elUserDictionary Para poder hacer uso de estos proveedores de contenidos y acceder alos datos que nos proporcionan debemos antildeadir los permisos adecuados al ficheroAndroidManifestxml Por ejemplo para acceder al listiacuten telefoacutenico podemos antildeadir elpermiso correspondiente de la siguiente forma

ltuses-sdk androidminSdkVersion=8 gtltuses-permission androidname=androidpermissionREAD_CONTACTSgt

ltmanifestgt

Para obtener informacioacuten de un ContentProvider debemos hacer uso del meacutetodo query

de la clase ContentResolver El primero de los paraacutemetros de dicho meacutetodo seraacute la URIdel proveedor de contenidos al que deseamos acceder Este meacutetodo devuelve un cursorque nos permitiraacute iterar entre los resultados tal como se vio en la sesioacuten anterior en elapartado de cursores

ContentResolverquery(Uri uriString[] projectionString selectionString[] selectionArgsString sortOrder)

Por ejemplo la siguiente llamada nos proporcionaraacute un cursor que nos permitiraacute acceder atodos los contactos de nuestro teleacutefono moacutevil Para ello hemos hecho uso de la constante

Persistencia

25Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ContactsContractContactsCONTENT_URI que almacena la URI del proveedor decontenidos correspondiente Al resto de paraacutemetros se le ha asignado el valor null

ContentResolver cr = getContentResolver()Cursor cursor = crquery(ContactsContractContactsCONTENT_URI

null null null null)

NotaEn versiones anteriores a Android 20 las estructuras de datos de los contactos son diferentes y nose accede a esta URI

Una vez obtenido el cursor es posible mapear sus campos con alguacuten componente de lainterfaz graacutefica de la actividad De esta forma cualquier cambio que se produzca en elcursor se reflejaraacute automaacuteticamente en el componente graacutefico sin que sea necesario queprogramemos el coacutedigo que lo refresque Para esto tenemos que llamar al meacutetodosetNotificationUri del cursor con lo cual nos aseguraremos de que los listeners seraacutennotificados cuando se produzca alguacuten cambio en los datos referenciados por dicho cursor

cursorsetNotificationUri(crContactsContractContactsCONTENT_URI)

Para asignar un adaptador a una lista de la interfaz graacutefica de la actividad maacutesconcretamente una ListView utilizamos el meacutetodo setAdapter(Adapter)

ListView lv = (ListView)findViewById(RidListView01)SimpleCursorAdapter adapter = new SimpleCursorAdapter(

getApplicationContext()Rlayouttextviewlayoutcursornew String[]

ContactsContractContacts_IDContactsContractContactsDISPLAY_NAME

new int[]RidTextView1RidTextView2)

lvsetAdapter(adapter)

En este ejemplo los identificadores RidTextView1 y RidTextView2 se correspondencon views del layout que define cada fila de la ListView como se puede ver en elarchivo textviewlayoutxml que tendriacuteamos que crear

ltxml version=10 encoding=utf-8gt

ltLinearLayout xmlnsandroid=httpschemasandroidcomapkresandroidandroidorientation=horizontalandroidlayout_width=fill_parentandroidlayout_height=wrap_contentgt

ltTextView androidid=+idTextView1androidtextStyle=bold

Persistencia

26Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

androidems=2androidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltTextView androidid=+idTextView2androidtextStyle=boldandroidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltLinearLayoutgt

La lista en siacute (identificada por RidListView01 en el presente ejemplo) estariacutea en otroXML layout por ejemplo en mainxml

Se debe tener en cuenta que en el caso de este ejemplo si quisieacuteramos obtener losnuacutemeros de teleacutefono de cada uno de nuestros contactos tendriacuteamos que recorrer el cursorobtenido a partir del proveedor de contenidos y por cada persona en nuestra agenda crearun nuevo cursor que recorriera los teleacutefonos ya que una persona puede tener asignadosvarios teleacutefonos

Puedes acceder a un listado completo de los proveedores de contenidos nativos existentesen Android consultando su manual de desarrollo Concretamente leyendo la seccioacutendedicada al paquete androidprovider enhttpdeveloperandroidcomreferenceandroidproviderpackage-summaryhtml

322 Proveedores propios crear un nuevo proveedor de contenidos

Para acceder a nuestras fuentes de datos propias de manera estaacutendar nos interesaimplementar nuestros propios ContentProvider Gracias a que proporcionamos nuestrosdatos a partir de una URI quien use nuestro proveedor de contenidos no deberaacutepreocuparse de doacutende provienen los datos indicados por dicha URI podriacutean provenir deficheros locales en la tarjeta de memoria de un servidor en Internet o de una base dedatos SQLite Dada una URI nuestro proveedor de contenidos deberaacute proporcionar en suinterfaz los meacutetodos necesarios para realizar las operaciones baacutesicas en una base de datosinsercioacuten lectura actualizacioacuten y borrado

Para crear un nuevo proveedor de contenidos definimos una subclase deContentProvider Utilizaremos una sobrecarga del meacutetodo onCreate para crear einicializar la fuente de datos que queramos hacer puacuteblica a traveacutes de dicho proveedor Unesqueleto de subclase de ContentProvide podriacutea ser el siguiente

public class MiProveedor extends ContentProvider

Overridepublic boolean onCreate()

Inicializar la base de datosreturn true

Dentro de la clase deberemos crear un atributo puacuteblico y estaacutetico de nombreCONTENT_URI en el cual almacenaremos el URI completo del proveedor que estamos

Persistencia

27Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creando Este URI debe ser uacutenico por lo que una buena idea puede ser tomar como baseel nombre de paquete de nuestra aplicacioacuten La forma general de un URI es la siguiente

content[NOMBRE_PAQUETE]provider[NOMBRE_APLICACION][RUTA_DATOS]

Por ejemplo

contentesuajtechandroidprovidermiaplicacionelementos

En general (y por simplicidad) no es necesario antildeadir la cadena provider al nombre delpaquete por lo que podemos utilizar directamente el nombre del paquete seguido de laruta de datos de la forma

contentesuajtechandroidmiaplicacionelementos

Estos URIs se pueden presentar en dos formas La URI anterior representa una consultadirigida a obtener todos los valores de ese tipo (en este caso todos los elementos) Si a laURI anterior antildeadimos un sufijo [NUMERO_FILA] estamos representando una consultadestinada a obtener un uacutenico elemento (por ejemplo el quinto elemento en el siguienteejemplo)

contentesuajtechandroidprovidermiaplicacionelementos5

NotaSe considera una buena praacutectica de programacioacuten el proporcionar acceso a nuestro proveedor decontenidos utilizando cualquiera de estas dos formas

La forma maacutes simple de determinar cuaacutel de las dos formas anteriores de URI se utilizoacutepara hacer una peticioacuten a nuestro proveedor de contenidos es mediante un Uri MatcherCrearemos y configuramos un elemento Uri Matcher para analizar una URI y determinara cuaacutel de las dos formas se corresponde En el siguiente coacutedigo se muestra el coacutedigobaacutesico que deberemos emplear para implementar este patroacuten

public class MiProveedor extends ContentProvider

private static final String miURI =contentesuajtechandroidprovidermiaplicacionelementos

public static final Uri CONTENT_URI = Uriparse(miUri)

Creamos las constantes que nos van a permitir diferenciar entre los dos tipos distintos de peticiones a partir de la URIprivate static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

Inicializamos el objeto UriMatcher Una URI que termine en elementos se corresponderaacute con una consulta para todas las filas mientras que una URI con el sufijo elementos[FILA] representaraacute una uacutenica filastatic

uriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidprovidermiaplicacion

elementos TODAS_FILAS)

Persistencia

28Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

uriMatcheraddURI(esuajtechandroidprovidermiaplicacionelementos UNA_FILA)

Overridepublic boolean onCreate()

PENDIENTE inicializar la base de datosreturn true

Esta misma teacutecnica se puede emplear para proporcionar diferentes alternativas de URIpara diferentes subconjuntos de datos como por ejemplo diferentes tablas en una base dedatos a traveacutes del mismo proveedor de contenidos

323 Proveedores propios crear la interfaz de consultas

Para permitir realizar operaciones con los datos publicados a traveacutes de nuestro proveedorde contenidos deberemos implementar los meacutetodos delete insert update y query

para el borrado insercioacuten actualizacioacuten y realizacioacuten de consultas respectivamenteEstos meacutetodos son la interfaz estaacutendar utilizada con proveedores de contenidos de formaque para compartir datos entre aplicaciones no se tendraacuten interfaces distintos paradiferentes fuentes de datos

Lo maacutes habitual suele ser implementar un proveedor de contenidos como un mecanismopara permitir el acceso a una base de datos SQLite privada a una aplicacioacuten aunque atraveacutes de estos meacutetodos indicados se podriacutea en principio acceder a cualquier fuente dedatos

El siguiente coacutedigo extiende el ejemplo anterior (MiProveedor) y muestra el esqueleto deestos meacutetodos dentro de una subclase de ContentProvider Obseacutervese que se hace usodel objeto UriMatcher para determinar que tipo de consulta se estaacute realizando

Overridepublic Cursor query(Uri uri

String[] projectionString selectionString[] selectionArgsString sort)

Si se trata de una consulta para obtener una uacutenica filalimitamos

los resultados a obtener de la fuente de datos por medio de una claacuteusula whereswitch(UriMatchermatch(uri))

case UNA_FILA PENDIENTE modifica la sentencia SELECT

mediante una claacuteusula where obteniendo el identificador de fila

como numeroFila = urigetPathSegments()get(1))

return null

Override

Persistencia

29Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public Uri insert(Uri _uri ContentValues _valores) long idFila = [ Antildeadir un nuevo elemento ]

Devolvemos la URI del elemento recieacuten antildeadidoif (idFila gt 0)

return ContentUriswithAppendendId(CONTENT_URI idFila)

throw new SQLException(No se pudo antildeadir un nuevo elemento a +_uri)

Overridepublic int delete(Uri uri String where String[] whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

Overridepublic int update(Uri uri ContentValues valores String where String[]whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

324 Proveedores propios tipo MIME

El uacuteltimo paso para crear un proveedor de contenidos es definir el tipo MIME queidentifica el tipo de datos que devuelve el proveedor Debemos sobrecargar el meacutetodogetType para que devuelva una cadena que identifique nuestro tipo de datos de manerauacutenica Se deberiacutean definir dos tipos posibles uno para el caso de una uacutenica fila y otro parael caso de devolver todos los resultados Se debe utilizar una sintaxis similar a la delsiguiente ejemplo

bull Un uacutenico elementovndesuajtechandroidmiaplicacioncursoritemmiproveedorcontent

bull Todos los elementosvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

Como se puede observar la cadena comienza siempre por vnd A continuacioacutentendriacuteamos el nombre del paquete de nuestra aplicacioacuten Lo siguiente es cursor ya quenuestro proveedor de contenidos estaacute devolviendo datos de tipo Cursor Justo antes de labarra pondremos item en el caso del tipo MIME para un uacutenico elemento y dir para elcaso del tipo MIME para todos los elementos Finalmente tras la barra pondremos elnombre de nuestra clase (en minuacutesculas) seguido de content En el siguiente coacutedigo deejemplo se muestra coacutemo sobrecargar getType seguacuten la URI

Persistencia

30Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Overridepublic String getType(Uri _uri)

switch (uriMatchermatch(_uri)) case TODAS_FILAS

returnvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

case UNA_FILAreturn

vndesuajtechandroidmiaplicacioncursoritemmiproveedorcontentdefault

throw new IllegalArgumentException(URI nosoportada + _uri)

325 Proveedores propios registrar el proveedor

No debemos olvidar incorporar nuestro proveedor de contenidos al Manifest de laaplicacioacuten una vez que lo hemos completado Esto se haraacute por medio del elementoprovider cuyo atributo authorities podremos utilizar para especificar la URI base talcomo se puede ver en el siguiente ejemplo

ltprovider androidname=MiProveedorandroidauthorities=esuajtechandroidmiaplicaciongt

326 Content Resolvers

Aunque en la seccioacuten de proveedores nativos ya se ha dado alguacuten ejemplo de coacutemo haceruso de un Content Resolver para realizar una consulta ahora que hemos podido observaren detalle la estructura de un proveedor de contenidos propio es un buen momento paraver en detalle coacutemo realizar cada una de las operaciones que eacutestos permiten

El contexto de una aplicacioacuten incluye siempre una instancia de la claseContentResolver a la cual se puede acceder mediante el meacutetodo getContentResolver

ContentResolver cr = getContentResolver()

Esta clase incorpora diversos meacutetodos para realizar consultas a proveedores decontenidos Cada uno de estos meacutetodos acepta como paraacutemetro una URI que indica conqueacute proveedor de contenidos se desea interactuar

Las consultas realizadas sobre un proveedor de contenidos recuerdan a las consultasrealizadas sobre bases de datos Los resultados de las consultas se devuelven comoobjetos de la clase Cursor Se pueden extraer valores del cursor de la misma forma en laque se explicoacute en el apartado de bases de datos de la sesioacuten anterior El meacutetodo query dela clase ContentResolver acepta los siguientes paraacutemetros

bull La URI del proveedor del contenidos al que se desea realizar la consultabull Una proyeccioacuten que enumera las columnas que se quieren incluir en los resultados

obtenidosbull Una claacuteusula where que define las filas a obtener Se pueden utilizar comodines que

Persistencia

31Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

se reemplazaraacuten por valores incluidos en el siguiente paraacutemetrobull Una matriz de cadenas que se pueden utilizar para reemplazar los comodines en la

claacuteusula where anteriorbull Una cadena que describa la ordenacioacuten de los elementos devueltos

El siguiente coacutedigo muestra un ejemplo

ContentResolver cr = getContentResolver() Devolver todas las filasCursor todasFilas = crquery(MiProveedorCONTENT_URI null null nullnull) Devolver todas columnas de las filas para las que la columna 3 tiene un valor determinado ordenadas seguacuten el valor de la columna 5String where = COL3 + = + valorString orden = COL5Cursor algunasFilas = crquery(MiProveedorCONTENT_URI null where nullorden)

327 Otras operaciones con Content Resolvers

Para realizar otro tipo de operaciones con los datos de un proveedor de contenidosharemos uso de los meacutetodos delete update e insert de un objeto ContentResolver

Con respecto a la insercioacuten la clase ContentResolver proporciona en su interfaz dosmeacutetodos diferentes insert y bulkInsert Ambos aceptan como paraacutemetro la URI delproveedor de contenidos pero mientras que el primer meacutetodo tan solo toma comoparaacutemetro un objeto de la clase ContentValues (ya vistos en la seccioacuten de bases dedatos) el segundo toma como paraacutemetro una matriz de elementos de este tipo El meacutetodoinsert devolveraacute la URI del elemento recieacuten antildeadido mientras que bulkInsert

devolveraacute el nuacutemero de filas correctamente insertadas

Obtener el Content ResolverContentResolver cr = getContentResolver()

Crear una nueva fila de valores a insertarContentValues nuevosValores = new ContentValues()

Asignar valores a cada columnanuevosValoresput(NOMBRE_COLUMNA nuevoValor)[ Repetir para cada columna ]

Uri miFilaUri = crinsert(MiProveedorCONTENT_URI nuevosValores)

Insertamos ahora una matriz de nuevas filasContentValues[] arrayValores = new ContentValues[5] PENDIENTE rellenar el array con los valores correspondientesint count = crbulkInsert(MiProveedorCONTENT_URI arrayValores)

Para el caso del borrado hacemos uso del meacutetodo delete del Content Resolver pasandocomo paraacutemetro la URI de la fila que deseamos eliminar de la fuente de datos Otraposibilidad es incluir una claacuteusula where para eliminar muacuteltiples filas Ambas teacutecnicas semuestran en el siguiente ejemplo

ContentResolver cr = getContentResolver()

Persistencia

32Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Eliminar una fila especiacuteficacrdelete(miFilaUri null null) Eliminar las primeras cinco filasString where = _id lt 5crdelete(MiProveedorCONTENT_URI where null)

Una actualizacioacuten se lleva a cabo mediante el meacutetodo update del objeto ContentResolver Este meacutetodo toma como paraacutemetro la URI del proveedor de contenidosobjetivo un objeto de la clase ContentValues que empareja nombres de columna convalores actualizados y una claacuteusula where que indica queacute filas deben ser actualizadas Elresultado de la actualizacioacuten seraacute que en cada fila en la que se cumpla la condicioacuten de laclaacuteusula where se cambiaraacuten los valores de las columnas especificados por el objeto de laclase ContentValues tambieacuten se devolveraacute el nuacutemero de filas correctamenteactualizadas A continuacioacuten se muestra un ejemplo

ContentValues nuevosValores = new ContentValues()

Utilizamos el objeto ContentValues para especificar en queacute columnas queremos que se produzca un cambio y cuaacutel debe ser el nuevo valor de dichas columnasnuevosValuesput(NOMBRE_COLUMNA nuevoValor)

Aplicamos los cambios a las primeras cinco filasString where = _id lt 5

getContentResolver()update(MiProveedorCONTENT_URI nuevosValores wherenull)

Persistencia

33Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

4 Ejercicios - Persistencia en Android proveedores de contenidos ySharedPreferences

41 Compartir datos entre actividades con Shared Preferences (075 puntos)

Descarga de las plantillas el proyecto Dni Dicho proyecto estaacute compuesto de dosactividades Formulario y Resumen El objetivo del ejercicio es conseguir que al pulsar elbotoacuten Siguiente en la actividad Formulario se muestre en la actividad Resumen cuaacutelesfueron los datos introducidos

En este ejercicio vamos a hacer uso de SharedPreferences para pasar los datos de unaactividad a la siguiente Al pulsar en Siguiente en Formulario deberemos guardar el valorde todos los campos mediante esta plataforma Al mostrarse la actividad Resumendeberemos leer estos datos tambieacuten a partir de SharedPreferences para mostrarlos porpantalla

Por uacuteltimo crea un meacutetodo que valide el DNI (debe tratarse de una secuencia de ochodiacutegitos entre 0 y 9 y una letra al final) Si se pulsa Siguiente y el DNI no tiene el formatocorrecto no se deberaacute pasar a la actividad Resumen En lugar de esto se mostraraacute unToast en pantalla con un mensaje de error

42 Actividad de preferencias (075 puntos)

En este ejercicio seguimos trabajando con el proyecto del ejercicio anterior Vamos aantildeadir un menuacute de opciones que permita escoger en queacute momento se comprobaraacute lavalidez del DNI Para ello antildeade en primer lugar un menuacute a la actividad Formulario conuna uacutenica opcioacuten cuyo nombre seraacute Opciones

Al seleccionar esta opcioacuten se deberaacute mostrar una actividad de preferencias cuyo aspectoseraacute el siguiente

Persistencia

34Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Preferencias de la aplicacioacuten Dni

El significado de las opciones seraacute el siguiente

bull En el campo con esta opcioacuten seleccionada al pulsar sobre el campo de DNI en elformulario deberaacute mostrarse un Toast indicando si la sintaxis del DNI es correcta ono

bull Al pulsar si se desactiva esta opcioacuten (que deberaacute estar activada por defecto) no seharaacute la comprobacioacuten del DNI al pulsar el botoacuten Siguiente por lo que siempre seraacuteposible pasar de la actividad Formulario a la actividad Resumen sea cual sea elformato del DNI introducido

bull El uacuteltimo check box se encontraraacute siempre deshabilitado iquestQueacute atributo debemosutilizar para conseguir esto

Crea dos variables booleanas llamadas enElCampo y alPulsar Su valor dependeraacute delestado de los checkboxes anteriores y se actualizaraacute por medio del eventoonSharedPreferenceChange Utiliza el valor de estas variables en el coacutedigo de tuactividad para que esta tenga el comportamiento indicado

43 Proveedor de contenidos propio (075 puntos)

Vamos a implementar otra forma de acceder a la base de datos de usuarios de laaplicacioacuten BaseDatos desarrollada durante los ejercicios de la sesioacuten anterior siguiendoesta vez el patroacuten de disentildeo ContentProvider de Android Seguiremos trabajando puescon dicho proyecto

bull Creamos una nueva clase llamada UsuariosProvider que herede deContentProvider Esto nos obligaraacute a sobrecargar una serie de meacutetodos abstractosAntes de implementar la query vamos a configurar el provider

bull Antildeadimos algunos campos tiacutepicos de los content provider

Persistencia

35Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public static final Uri CONTENT_URI =Uriparse(contentesuajtechandroidbasedatosusuarios)

private static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

staticuriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

TODAS_FILAS)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

UNA_FILA)

bull Vamos a acceder a la misma base de datos de usuarios utilizada en los ejercicios de lasesioacuten anterior pero no vamos a hacerlo a traveacutes del adaptador que tuvimos queimplementar sino que vamos a copiar de eacutel el coacutedigo que nos haga falta Copia loscampos que definen el nombre de la base de datos de la tabla de las columnas laversioacuten asiacute como la referencia al contexto y a la base de datos La sentenciacompilada del insert ya no va a hacer falta Inicializa los valores necesarios en elconstructor Para inicializar la referencia a la base de datos vamos a utilizar una vezmaacutes MiOpenHelper Podemos copiarlo del adaptador que ya implementamos en losejercicios de la sesioacuten anterior

bull Implementa de forma apropiada el getType para devolver un tipo MIME diferenteseguacuten si se trata de una URI de una fila o de todas las filas Para ello ayuacutedate deluriMatcher

bull Implementa el meacutetodo query Simplemente se trata de devolver el cursor queobtenemos al hacer una consulta a la base de datos SQLite Algunos de los paraacutemetrosque le pasaremos los recibimos como paraacutemetros del meacutetodo del provider Los que notengamos iraacuten con valor null

bull Aunque no tenemos todos los meacutetodos del UsuariosProvider implementadospodemos probarlo Para ello debemos registarlo en el AndroidManifestxml

ltprovider androidname=UsuariosProvider

androidauthorities=esuajtechandroidbasedatosgtltapplicationgt

ltmanifestgt

bull En la clase Main se antildeadioacute codigo para insertar una serie de valores en la base dedatos y mostrarlos en el campo de texto Manteniendo este coacutedigo vamos a antildeadir alcampo de texto el resultado obtenido con la consulta del UsuariosProvider paracomprobar que tanto el adaptador de la base de datos como el proveedor nosdevuelven el mismo resultado

44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)

Porque es la forma estaacutendar que establece Android de acceder a contenidos Ademaacutes elproveedor de contenidos nos permitiraacute notificar al ContentResolver de los cambiosocurridos Asiacute componentes en la pantalla podraacuten refrescarse de forma automaacutetica

Persistencia

36Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Utiliza el proyecto ProveedorContenidos de las plantillas Implementa la insercioacuten enel proveedor de contenidos Prueacutebala insertando algunos usuarios de ejemplo en laclase Main Implementa tambieacuten el OnClickListener del botoacuten que inserta nuevosusuarios El nombre del nuevo usuario iraacute indicado en el EditText

bull Comprueba que la insercioacuten funciona y que gracias a la siguiente liacutenea y al estarusando un proveedor de contenidos la lista se actualiza automaacuteticamente cuandoocurre alguacuten cambio sin necesidad de pedir expliacutecitamente la actualizacioacuten al pulsarel botoacuten

cursorsetNotificationUri(cr UsuariosProviderCONTENT_URI)

NotaEsto no va a funcionar si se notifica que se ha producido un cambio al insertar o borrar unelemento Para ello usamosgetContext()getContentResolver()notifyChange(UsuariosProviderCONTENT_URI null)

bull Implementa el meacutetodo delete del proveedor de contenidos Prueacutebalo en la claseMain Termina de implementar el onCreateContextMenuListener que se ejecutaraacutecada vez que se haga una pulsacioacuten larga sobre alguna entrada de la lista Compruebaque funciona (eliminando el usuario correspondiente y no otro)

Persistencia

37Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

5 Persistencia de datos en iOS Ficheros y SQLite

51 Introduccioacuten

La persistencia de datos en iOS se realiza en una pequentildea memoria flash que tienenuestro dispositivo la cual es equivalente a un disco duro de tamantildeo limitado Los datosque ahiacute se almacenen se conservaraacuten aunque el dispositivo se apague Por motivos deseguridad el SO de iOS no permite que las aplicaciones accedan directamente a lamemoria interna pero a cambio existe una API completa de acceso a la porcioacuten dememoria que le corresponde a la app que desarrollemos

En una aplicacioacuten iOS encontraremos baacutesicamente 4 modos de almacenamiento deinformacioacuten de forma persistente

bull Ficheros de propiedades (plist)bull Bases de datos embebidas SQLitebull Datos de usuario (User Defaults)bull Core Data

En esta primera sesioacuten veremos coacutemo usar los ficheros de texto (Property Lists) y elalmacenamiento en base de datos de tipo SQLite

52 Ficheros plist (Property Lists)

El almacenamiento de datos en ficheros de propiedades es uno de los maacutes usados en eldesarrollo de las aplicaciones para iOS Se usa principalmente para guardar datos deconfiguracioacuten de la aplicacioacuten u otra informacioacuten poco compleja y acceder a ella deforma sencilla Si estamos desarrollando un juego podemos usarlos para la definicioacuten delos niveles almacenar las puntuaciones del jugador etc

521 Leyendo datos desde ficheros plist

Los ficheros plist (Property Lists) tienen un formato XML aunque XCode nosproporciona una interfaz que nos permite acceder a ellos de forma muy sencilla Paraentender el funcionamiento de este tipo de ficheros vamos a realizar un ejemplo en el queprimero crearemos un fichero plist lo completaremos con datos de ejemplo y despueacutes loleeremos para mostrarlo por pantalla

Un ejemplo de un fichero plist podriacutea ser el siguiente

Persistencia

38Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Fichero plist

Antes de nada creamos un proyecto nuevo en XCode de tipo Window-based

Application al que llamaremos sesion03-ejemplo1 Para crear un fichero plistdebemos hacer click derecho sobre el directorio Supporting Files de nuestro proyectoy seleccionamos iOS gt Resource gt Property List Lo guardaremos con el nombreconfigUsuario Seguidamente ya podemos acceder al fichero recieacuten creado haciendoclick sobre el en este momento XCode nos mostraraacute un pequentildeo editor totalmente vacioen el que iremos rellenando con datos Podemos verlo tambieacuten en formato XML sihacemos click derecho sobre el y seleccionamos Open As gt Preview

Para empezar a rellenar el fichero que acabamos de crear hacemos click derecho dentrode su contenido (ahora vacio) y seleccionamos Add row Entonces nos apareceraacute unafila vaciacutea Dentro del campo Key escribimos la clave un nombre descriptivo quedeberemos utilizar maacutes adelante para acceder a los datos En nuestro caso vamos aescribir config como clave Dentro de la columna Type seleccionamosDictionary Veremos que aparece una flecha en el lado izquierdo que indica quepueden haber subniveles dentro de nuestro diccionario

Ahora antildeadimos un nuevo item que cuelgue del diccionario que acabamos de crear paraello hacemos click sobre la flecha de la izquierda esta se giraraacute hacia abajo entonceshacemos click ahora sobre el siacutembolo + (maacutes) Nos apareceraacute una nueva liacutenea que cuelgadel diccionario

En esta nueva liacutenea dentro del campo key escribimos nombre el campo type lodejamos como de tipo String ya que va a ser una cadena de texto y en la columna deValue escribimos nuestro nombre por ejemplo Javi

Ahora vamos a antildeadir otro campo nuevo en nuestra configuracioacuten para ello hacemosclick sobre el siacutembolo + y nos apareceraacute una nueva liacutenea justo al mismo nivel que laanterior y colgando del diccionario Dentro del campo de Key escribimos ciudad yen Value escribimos Alicante El campo de Type lo dejamos como String

Persistencia

39Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora dentro de nuestro fichero plist vamos a indicar los dispositivos que dispone elusuario para ello creamos una nueva linea dentro del diccionario y le pondremos comoclave dispositivos Seleccionamos el tipo Array y seguimos los mismos pasosque al crear el diccionario para ello hacemos click sobre la flecha de la izquierda ydespueacutes sobre el siacutembolo + (maacutes) Ahora nos apareceraacute una nueva fila con clave Item 0Seleccionamos el tipo String y en el campo Value iPhone

Ahora repetimos el paso 3 veces indicando como values iPad Android y

Blackberry

Una vez seguidos todos estos pasos tendremos nuestro fichero de propiedades listo Debede quedar como se muestra a continuacioacuten

Fichero plist terminado

Este fichero se almacenaraacute dentro del directorio de bundle cuando compilemos laaplicacioacuten En el caso de que queramos escribir en el debermos almacenar el ficherodentro del directorio de Documents del binario

Ahora desde nuestro coacutedigo vamos a acceder a eacutel y a mostrarlo por pantalla para elloescribimos el siguiente coacutedigo dentro del meacutetodo didFinishLaunchingWithOptions dela clase delegada

Cargamos el fichero PLISTNSString mainBundlePath = [[NSBundle mainBundle] bundlePath]NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]NSDictionary diccionario = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionario delraiz

NSString nombre = [dicConfig objectForKeynombre]

Persistencia

40Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

[selfwindow makeKeyAndVisible]return YES

Si todo ha ido correctamente deberemos obtener la siguiente salida por la consola

Lista de dispositivos

522 Escribiendo datos en ficheros plist

Partiendo del ejemplo anterior vamos a escribir ahora en el fichero para ello primerodebemos comprobar que el fichero plist se encuentre dentro del directorio de Documents

del dispositivo en el caso de que no lo esteacute (por ejemplo si es la primera vez que arrancala aplicacioacuten) debemos copiarlo a ese directorio Una vez que tenemos el fichero dentrodel directorio de documentos ya podemos escribir en el

Para realizar todo el proceso de comprobacioacuten de la existencia del fichero dentro deldirectorio de documentos copiarlo en ese directorio y leer los datos debemos de sustituirel coacutedigo que hay dentro del meacutetodo didFinishLaunchingWithOptions de la clasedelegada por este otro

AtencioacutenPara que funcione la escritura en un fichero plist este debe de estar dentro del directorio deDocuments de nuestro dispositivo iOS no en el boundle

NSDictionary diccionarioRaiz

Ruta del directorio de documentos de nuestro dispositivoNSArray paths =

NSSearchPathForDirectoriesInDomains(NSDocumentDirectoryNSUserDomainMask YES)

if ([paths count] gt 0)

Persistencia

41Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

Primero comprobamos si existe el fichero en eldirectorio de documentos

BOOL fileExists = [[NSFileManager defaultManager]fileExistsAtPathdocumentsFilename]if (fileExists)

NSLog(Fichero encontrado en directorio de documentosOK

documentsFilename)

Si existe ya cargamos los datos desde aquidirectamente

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

else

NSLog(No se encuentra el fichero configUsuarioplisten

el directorio de documentos-gtLo creamos)

Si no existe primero cargamos el fichero PLISTdesde el Boundle

NSString mainBundlePath = [[NSBundle mainBundle]bundlePath]

NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Y despues escribimos los datos cargados (eldiccionario completo)

a un fichero nuevo de la carpeta de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

NSLog(plist de configUsuario creado)

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionariodel raiz

NSString nombre = [dicConfig objectForKeynombre]NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

Persistencia

42Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Por ultimo liberamos la memoria del diccionario raiz[diccionarioRaiz release]

[selfwindow makeKeyAndVisible]return YES

Para comprobar el funcionamiento arrancamos la aplicacioacuten y veremos que la primera vezse crearaacute el fichero dentro de la carpeta de Documents de nuestro dispositivo y en lassiguientes ya no se crearaacute y simplemente lo leeraacute desde la carpeta de Documents

Ahora que ya tenemos la gestioacuten de ficheros baacutesica completada vamos a escribir sobre elalguacuten dato Para ello simplemente escribimos el siguiente fragmento de coacutedigo justo antesde la liacutenea [selfwindow makeKeyAndVisible]

Cambiamos el nombre y lo guardamos en el fichero Ruta del directorio de documentos de nuestro dispositivo

if ([paths count] gt 0)

Cargamos el fichero desde el directorio dedocumentos del dispositivo

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cambiamos el valor del nombre en el diccionario[dicConfig setValueOtro nombre forKeynombre]

Escribimos todo el diccionario de nuevo al fichero del directorio de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

Por ultimo liberamos el diccionario de lamemoria

[diccionarioRaiz release]

Lo que hemos hecho en el coacutedigo anterior es cargar todo el diccionario de nuevo desde eldirectorio de documentos modificar los datos que queremos cambiar o incluso antildeadir ydespueacutes guardar el diccionario en el fichero

Volvemos a arrancar la aplicacioacuten y veremos que ha cambiado el campo de nombre

Nota

Persistencia

43Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

La lectura y escritura de datos en un fichero plist se debe de hacer de una soacutela vez nunca porpartes Siempre se debe de cargar o escribir un diccionario NSDictionary como elementoprincipal del fichero

53 SQLite

SQLite es una base de datos relacional ligera con la que se puede trabajar de formasencilla mediante sentencias SQL La base de datos se almacenaraacute en un fichero dentrode nuestra aplicacioacuten Este meacutetodo de persistencia puede ser el adecuado cuando los datosa almacenar esteacuten en un formato de tablas filas y columnas y se necesite que los datossean accesibles de forma raacutepida

531 Herramientas disponibles

Para explicar el funcionamiento de las librerias de SQLite de Cocoa Touch vamos a seguirun ejemplo completo Comenzamos creando la base de datos para ello podemos utilizaruno de los frontends que facilitan la gestioacuten del SQLite uno bastante sencillo yrecomendado es el SQLite Database Browser y otro es fmdb

Nosotros utilizaremos el primero (SQLite Database Browser) Abrimos la aplicacioacuten yhacemos click sobre el botoacuten de crear nueva base de datos que llamaremospersonasDBsqlite Seguidamente creamos las tablas para simplificar en nuestroejemplo crearemos una soacutela tabla llamada personas Esta tabla tendraacute las siguientescolumnas id (NUMERIC) nombre (TEXT) apellidos (TEXT) dni (TEXT) edad

(NUMERIC) y localidad (TEXT)

Persistencia

44Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Estructura de la Base de Datos

Una vez creada la tabla pasamos a insertar datos dentro de ella para ello abrimos lapestantildea de Browse Data y hacemos click en New Record Cuando tengamos al menos 3registros metidos dentro de la tabla ya podemos guardar la base de datos Hacemos clicksobre guardar le ponemos el nombre que queramos y elegimos cualquier directorio delMAC

Datos de la Base de Datos

532 Lectura de datos

Una vez que tenemos la base de datos SQLite creada ya podemos empezar a escribir elcoacutedigo para leer y mostrar los datos dentro de nuestra aplicacioacuten

Primero empezamos creando un proyecto nuevo en XCode usando la plantilla basada envistas View-based application y lo llamaremos sesion03-ejemplo2 Ahora antildeadimosel framework de sqlite a nuestro proyecto para ello hacemos click sobre el raiz delproyecto y seleccionamos la pestantildea Build Phases Desplegamos el listado de Link

Binary With Libraries y hacemos click sobre el botoacuten (+) El el listadoseleccionamos el framework libsqlite3dylib y hacemos click en Add

Persistencia

45Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Antildeadiendo el framework libsqlite3dylib

Ahora antildeadimos a nuestro proyecto la base de datos que hemos creado anteriormentepara ello arrastramos el fichero personasDBsqlite a nuestro directorio Supporting

Files Asegurate de seleccionar la opcioacuten de Copy items to destination groups

folder (if needed) para que se copie el fichero dentro de la carpeta del proyecto

Para acelerar el proceso de lectura de la base de datos y ahorrar memoria vamos a cargarprimero todos los datos en objetos para ello vamos a crear una clase que se encargue dealmacenar los datos Hacemos click derecho sobre el raiz del proyecto New file gtObjective-C class seleccionamos NSObject como clase principal y le damos a NextEscribimos como nombre de la clase Persona y hacemos click en save

Personah

imports iniciales

interface Persona NSObject int _uIdNSString _nombreNSString _apellidos

Persistencia

46Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSInteger _edadNSString _localidad

property (nonatomic assign) int uIdproperty (nonatomic copy) NSString nombreproperty (nonatomic copy) NSString apellidosproperty (nonatomic) NSInteger edadproperty (nonatomic copy) NSString localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidos edad(NSInteger)edadlocalidad(NSString )localidad

end

Personam

import Personah

implementation Persona

synthesize uId = _uIdsynthesize nombre = _nombresynthesize apellidos = _apellidossynthesize edad = _edadsynthesize localidad = _localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidosedad(NSInteger)edad localidad(NSString )localidad

if ((self = [super init])) selfuId = uIdselfnombre = nombreselfapellidos = apellidosselfedad = edadselflocalidad = localidad

return self

- (void) dealloc selfnombre = nilselfapellidos = nilselflocalidad = nil[super dealloc]

end

Una vez creada la clase Persona vamos a crear la clase encargada de manejar la basede datos sqlite3 Otra opcioacuten seriacutea cargar todos los datos directamente desde la clasedelegada al arrancar la aplicacioacuten pero no es recomendable ya que si en alguacuten momentotenemos que cambiar de motor de base de datos lo tendremos maacutes complicado

Persistencia

47Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creamos entonces una clase dentro de nuestro proyecto llamada BDPersistencia queherede de NSObject y escribimos el siguiente coacutedigo

BDPersistenciah

imports iniciales

interface BDPersistencia NSObject sqlite3 _database

+ (BDPersistencia)database- (NSArray )arrayPersonas

end

BDPersistenciam

import BDPersistenciahimport Personah

implementation BDPersistencia

static BDPersistencia _database

+ (BDPersistencia)database if (_database == nil)

_database = [[BDPersistencia alloc] init]return _database

- (id)init if ((self = [super init]))

NSString sqLiteDb = [[NSBundle mainBundle]pathForResourcepersonasDB

ofTypesqlite]

if (sqlite3_open([sqLiteDb UTF8String]amp_database) = SQLITE_OK)

NSLog(Fallo al abrir la BD)

return self

- (void)dealloc sqlite3_close(_database)[super dealloc]

- (NSArray )arrayPersonas

NSMutableArray arraySalida = [[[NSMutableArray alloc]init]

autorelease]

Persistencia

48Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString query = SELECT id nombre apellidoslocalidad edad

FROM personas ORDER BY apellidos DESC

sqlite3_stmt statementif (sqlite3_prepare_v2(_database [query UTF8String]

-1ampstatement nil)

== SQLITE_OK) while (sqlite3_step(statement) == SQLITE_ROW)

int uniqueId = sqlite3_column_int(statement0)

char nombreChar = (char )sqlite3_column_text(statement 1)

char apellidosChar = (char )sqlite3_column_text(statement 2)

char localidadChar = (char )sqlite3_column_text(statement 3)

int edad = sqlite3_column_int(statement 4)

NSString nombre = [[NSString alloc]initWithUTF8StringnombreChar]NSString apellidos = [[NSString alloc]initWithUTF8StringapellidosChar]NSString localidad = [[NSString alloc]initWithUTF8StringlocalidadChar]Persona persona = [[Persona alloc]initWithUiduniqueId nombrenombreapellidosapellidos edadedad

localidadlocalidad]

[arraySalida addObjectpersona][nombre release][apellidos release][localidad release][persona release]

sqlite3_finalize(statement)

return arraySalida

end

Con esto ya podemos leer de nuestra base de datos las personas Para ver que funcionatodo a la perfeccioacuten escribimos el siguiente coacutedigo dentro de la clase delegada en elmeacutetodo applicationDidFinishLaunching Antes debemos incluir en la parte superiorlas clases creadas

import BDPersistenciahimport Personah

NSArray personas = [BDPersistenciadatabase]arrayPersonas

for (Persona persona in personas) NSLog(d d personauId

personanombrepersonaapellidos personalocalidad personaedad)

Persistencia

49Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Si ejecutamos el proyecto obtendremos la siguiente salida por consola

Listado de personas

533 Mostrando los datos en una tabla

Una vez que tenemos el coacutedigo para obtener todas las filas de la tabla de personas de labase de datos queda lo maacutes faacutecil mostrarlas dentro de una vista de la aplicacioacuten En estecaso utilizaremos la vista de tabla UITableView

Comenzamos creando una nueva vista en nuestro proyecto para ello hacemos clickderecho sobre el raiz y seleccionamos New File gt UIVIewController SubclassAsegurate de marcar Subclass of UITableViewController y la opcoacuten de With

XIB for user interface Hacemos click en Next y guardamos el fichero comoPersonasViewController por ejemplo

Ahora abrimos el fichero PersonasViewControllerh y antildeadimos un NSArray queseraacute el que almacene las personas de la base de datos

imports iniciales

interface PersonasViewController UITableViewController

NSArray _listadoPersonas

property (nonatomic retain) NSArray listadoPersonas

end

Ahora abrimos el fichero PersonasViewControllerm antildeadimos el syntetizeimportamos los ficheros de gestioacuten de la base de datos la clase Persona y todo el coacutedigobaacutesico para la gestioacuten de la vista de la tabla

import PersonasViewControllerhimport Personahimport BDPersistenciah

implementation PersonasViewController

synthesize listadoPersonas = _listadoPersonas

Persistencia

50Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

- (id)initWithStyle(UITableViewStyle)style

self = [super initWithStylestyle]if (self)

Custom initializationreturn self

- (void)dealloc

[super dealloc]

selflistadoPersonas = nil

- (void)didReceiveMemoryWarning

Releases the view if it doesnt have a superview[super didReceiveMemoryWarning]

Release any cached data images etc that arent inuse

pragma mark - View lifecycle

- (void)viewDidLoad

[super viewDidLoad]

selftitle = Listado de personas

selflistadoPersonas = [BDPersistenciadatabase]arrayPersonas

- (void)viewDidUnload

[super viewDidUnload] Release any retained subviews of the main view eg selfmyOutlet = nil

- (void)viewWillAppear(BOOL)animated

[super viewWillAppearanimated]

- (void)viewDidAppear(BOOL)animated

[super viewDidAppearanimated]

- (void)viewWillDisappear(BOOL)animated

[super viewWillDisappearanimated]

- (void)viewDidDisappear(BOOL)animated

[super viewDidDisappearanimated]

- (BOOL)shouldAutorotateToInterfaceOrientation

Persistencia

51Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

(UIInterfaceOrientation)interfaceOrientation

Return YES for supported orientationsreturn (interfaceOrientation ==

UIInterfaceOrientationPortrait)

pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView(UITableView)tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection

(NSInteger)section

Return the number of rows in the sectionreturn [selflistadoPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath

(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]if (cell == nil)

cell = [[[UITableViewCell alloc]initWithStyleUITableViewCellStyleSubtitle

reuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona persona = (Persona )[selflistadoPersonas

objectAtIndexindexPathrow]celltextLabeltext = [NSString stringWithFormat

(d) personanombrepersonaapellidos personaedad]celldetailTextLabeltext = personalocalidad

return cell

pragma mark - Table view delegate

- (void)tableView(UITableView )tableViewdidSelectRowAtIndexPath

(NSIndexPath )indexPath

Navigation logic may go here Create and pushanother view controller

end

Persistencia

52Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora soacutelo nos queda incorporar la vista que acabamos de crear dentro de nuestraaplicacioacuten Para ello vamos a llamarla desde el Delegate y a meterla dentro de unNavigation Controller Primero debemos de antildeadir un Outlet en la clasedelegada hacia un Navigation Controller

imports iniciales

interface sesion03_ejemplo1AppDelegate NSObjectltUIApplicationDelegategt

UIWindow _windowUINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet

UINavigationController navController

end

Ahora hacemos click sobre la vista MainWindowxib y arrastramos un Navigation

Controller desde el listado de la columna de la derecha hacia la columna de laizquierda donde pone Objects Nos apareceraacute el Navigation Controller dibujadoen pantalla Ahora desplegamos el Navigation Controller y hacemos click sobre elView Controller que hay dentro Nos vamos a la pestantildea de Identity Inspector

de la columna de la derecha y escribimos dentro de Class el nombre de la vista dellistado de personas PersonasViewController

Finalmente hacemos click sobre el objeto Delegate vamos a la pestantildea de Show

Connections Inspector y arrastramos desde navController hasta el objetoNavigation Controller de la columna de la izquierda Ya tenemos los controladoresy las vistas conectadas

Una vez hecho esto nos queda indicar dentro de nuestro coacutedigo que la aplicacioacuten arranquecon el Navigation Controller para ello escribimos lo siguiente al final del meacutetododidFinishLaunchingWithOptions de la implementacioacuten de la clase delegada

selfwindowrootViewController = selfnavController[selfwindow makeKeyAndVisible]return YES

Ya estaacute listo todo y preparado para compilar Si ejecutamos el proyecto veremos que nosaparece el listado de personas en la vista de tabla

Persistencia

53Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios

61 Creando y leyendo un fichero plist (15 puntos)

En este primer ejercicio vamos a crear nuestra primera aplicacioacuten usando el meacutetodo depersistencia de ficheros La aplicacioacuten mostraraacute en una vista de tabla TableView unlistado de series en la que si seleccionamos una veremos su informacioacuten maacutes importanteen otra vista Comenzaremos creando un fichero de propiedades (plist) siguiendo unaestructura determinada y posteriormente mostraremos en una vista de tabla todos susdatos iexclComenzamos

1) Crear un proyecto usando la plantilla Master-Detail application Lo llamaremosejercicio-plist-ios como identificador de empresa escribiremos esuajtech y como prefijode clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremos UseAutomatic Reference Counting para olvidarnos de la gestioacuten de memoria El resto deopciones las dejaremos sin marcar

2) Crear un archivo de propiedades (plist) con el nombre de series

3) Editar el archivo plist creando la siguiente estructura de datos (5 series miacutenimo)

Plist series

4) Rellenar el archivo plist con datos de series reales

5) Escribir el coacutedigo necesario en UAMasterViewControllerm que recorra el archivoplist que acabamos de crear y muestre por consola los titulos de las series

6) Mostrar en la tabla que hay implementada los tiacutetulos de la serie Para ello deberemosde completar todos los meacutetodos del protocolo del TableView

7) Ejecutar la aplicacioacuten y comprobar que funciona correctamente

Persistencia

54Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

62 Implementar vista de detalle de la serie (05 puntos)

Dentro del proyecto anterior (no hace falta crear uno nuevo) vamos a mostrar toda lainformacioacuten adicional de cada una de las series en una vista de detalle Cuandoseleccionemos una fila de la tabla nos deberaacute aparecer una nueva vista con el resto deinformacioacuten (antildeo director principal sinopsis y duracioacuten)

63 Uso baacutesico de una base de datos SQLite (1 punto)

En este ejercicio vamos a crear la misma aplicacioacuten que en el ejercicio anterior perousando el meacutetodo de persistencia de SQLite Para completar el ejercicio deberemos seguirlos siguientes pasos

1) Crear un proyecto usando la plantilla Master-Detail application lo llamaremosejercicio-sqlite-ios como identificador de empresa escribiremos esuajtech y comoprefijo de clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremosUse Automatic Reference Counting para olvidarnos de la gestioacuten de memoria El restode opciones las dejaremos sin marcar

2) Nos descargamos e instalamos el programa SQLite Database Browser desde estadireccioacuten

3) Disentildear la estructura de la base de datos usando SQLite Database Browser tal y comose muestra en la imagen siguiente

Sqlite series

4) Insertar al menos 5 series en la base de datos usando el programa SQLite DatabaseBrowser

5) Cargar todos los datos de la base de datos desde el meacutetododidFinishLaunchingWithOptions de la clase UAAppDelegate Mostrar por consola losdatos cargados

6) Mostrar los datos cargados en el paso anterior en la vista de tabla UITableView quehay creada en la clase UAMasterViewController

Persistencia

55Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7) Arrancar la aplicacioacuten y comprobar que se muestran las series en la tabla de formacorrecta

Persistencia

56Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7 Persistencia de datos en iOS User Defaults y Core Data

71 Introduccioacuten

En esta sesioacuten veremos como utilizar dos nuevos meacutetodos de persistencia en iOS ambosmuy interesantes y usados muy frecuentemente

bull User Defaults Muy uacutetil para almacenar datos simples y de uso repetido dentro denuestras aplicaciones iOS

bull Core Data Meacutetodo avanzado de almacenamiento de objetos enteros dentro de lamemoria del dispositivo iOS Muy uacutetil para almacenar datos complejos conrelaciones etc

72 User Defaults

Este meacutetodo es muy uacutetil cuando queremos almacenar pequentildeas cantidades de datos comopuntuaciones informacioacuten de usuario el estado de nuestra aplicacioacuten configuracionesetc

Este meacutetodo es el maacutes raacutepido de usar ya que no requiere de una base de datos ni ficherosni ninguna otra capa intermedia Los datos se almacenan directamente en la memoria deldispositivo dentro de un objeto de la clase NSUserDefaults

Para aprender a usar este meacutetodo de almacenamiento de datos vamos a crear unaaplicacioacuten muy simple que lea un nombre y una edad de pantalla y las guarde en unobjeto NSUserDefaults Para ello seguimos los siguientes pasos

bull 1) Comenzamos creando un nuevo proyecto llamado sesion04-ejemplo1 dentrode XCode

bull 2) Abrimos la vista sesion04_ejemplo1ViewController y arrastramos dos TextField un botoacuten y dos labels quedando la vista de la siguiente manera

Persistencia

57Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista de la aplicacioacuten

bull 3) Abrimos el fichero sesion04_ejemplo1ViewControllerh y definimos el Outletdel evento onclick del botoacuten El fichero quedaraacute de la siguiente manera

imports iniciales

interface sesion04_ejemplo1ViewController UIViewController UITextField tfNombreUITextField tfEdad

property(nonatomic retain) IBOutlet UITextField tfNombreproperty(nonatomic retain) IBOutlet UITextField tfEdad

-(IBAction) guardaDatos

end

bull 4) Ahora implementamos el coacutedigo para el meacutetodo del botoacuten dentro desesion04_ejemplo1ViewControllerm

synthesize tfEdadsynthesize tfNombre

-(IBAction) guardaDatos NSLog(Guardamos los datos)

Persistencia

58Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creamos el objeto NSUserDefaultsNSUserDefaults datos = [NSUserDefaults standardUserDefaults]

guardamos el nombre (NSString)[datos setObjecttfNombretext forKeynombre]

guardamos la edad (Integer)[datos setInteger[tfEdadtext integerValue] forKeyedad]

almacenamos los datos en la memoria[datos synchronize]

bull 5) Tenemos que enlazar el botoacuten con el evento que hemos programado en el pasoanterior para ello abrimos la vista sesion04_ejemplo1ViewControllerxib y conla tecla ctlr apretada arrastramos desde el botoacuten hasta Files Owner (columna de laizquierda parte superior) y seleccionamos el meacutetodo que hemos creado(guardaDatos) De esta forma ya tenemos conectado el botoacuten

bull 6) Ahora hacemos lo mismo con los Text Field Tenemos que conectarlos alcontrolador para ello seleccionamos el objeto Files Owner y abrimos la pestantildeade conexiones (la que estaacute maacutes a la derecha en la columna de la derecha) Hacemosclick en tfEdad y arrastramos hasta el Text Field de edad Hacemos lo mismo para eltext field de nombre Ya tenemos todos los elementos de la vista conectados

Conexioacuten de Outlets

bull 7) Arrancamos la aplicacioacuten y la probamos

Persistencia

59Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Aplicacioacuten

bull 8) Ahora vamos a hacer que la aplicacioacuten cargue los datos que se han guardado alarrancar la vista para ello simplemente tenemos que escribir el siguiente coacutedigo en elmeacutetodo (void)viewDidLoad de la clase sesion04_ejemplo1ViewControllerm

- (void)viewDidLoad

[super viewDidLoad]

NSUserDefaults datos = [NSUserDefaults standardUserDefaults]

obtenemos un NSStringNSString nombre = [datos stringForKeynombre]

obtenemos un NSIntegerNSInteger edad = [datos integerForKeyedad]

if (nombre=nil ampamp edad=0)NSLog(Nombre cargado edad dnombreedad)

else

NSLog(Sin datos)

bull 9) Ya podemos arrancar de nuevo la aplicacioacuten y veremos que si escribimos cualquier

Persistencia

60Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

texto en los Text Fields estos se almacenaraacuten dentro del objeto NSUserDefaults Alapagar la aplicacioacuten y volver a arrancarla estos datos se cargaraacuten (apareceraacuten enconsola)

Consola

NotaLos datos permaneceraacuten en nuestro dispositivo hasta que se elimine la aplicacioacuten en ese casotodos los datos guardados en NSUserDefaults se borraraacuten

bull 10) Probar eliminar la aplicacioacuten del dispositivosimulador y volver arrancarComprobamos que los datos se han borrado

Consola

73 Core Data

De todas las formas de persistencia de datos que existen en iOS Core Data es la maacutesapropiada para usar en caso de manejar datos no triviales con relaciones algo maacutescomplejas entre ellos El uso de Core Data dentro de nuestra aplicacioacuten optimiza almaacuteximo el consumo de memoria mejora el tiempo de respuesta y reduce la cantidad decoacutedigo redundante a escribir

Core Data utiliza de fondo el meacutetodo de almacenamiento SQLite visto en la sesioacuten 3 deeste moacutedulo por tanto para explicar el funcionamiento de este meacutetodo de persistenciavamos a utilizar el mismo modelo de datos que usamos en esa sesioacuten Persona -gtDetallePersona

El el siguiente ejemplo haremos la misma aplicacioacuten que hicimos con SQLite perousando este nuevo meacutetodo de persistencia

731 Creando el proyecto

Para empezar tenemos que crearnos un nuevo proyecto en XCode Para ello abrimosXCode y seleccionamos crear una nueva aplicacioacuten basada en ventanas (Window-basedapplication) Hacemos click en Next escribimos como nombre del proyectosesion04-ejemplo2 y seleccionamos Use Core Data

Persistencia

61Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creando un proyecto nuevo

Con esto ya tenemos la estructura baacutesica para empezar a programar Antes de nadahechamos un vistazo raacutepido a toda la estructura de directorios creada y vemos que dentrodel directorio principal tenemos un archivo que se llamasesion04_ejemplo2xcdatamodeld Al hacer click sobre el archivo se nos abre uneditor especial que en principio estaacute vacio y que maacutes adelante utilizaremos para disentildearnuestro modelo de datos

Ahora hechamos un vistazo a la clase delegada sesion04_ejemplo2AppDelegatemvemos que hay unos cuantos meacutetodos nuevos que serviraacuten para configurar el Core DataVamos a explicar los maacutes importantes

bull - (NSManagedObjectModel )managedObjectModel NSManagedObjectModel esla clase que contiene la definicioacuten de cada uno de los objetos o entidades quealmacenamos en la base de datos Normalmente este meacutetodo no lo utilizaremos ya quenosotros vamos a utilizar el editor que hemos visto antes con el que podremos crearnuevas entidades crear sus atributos y relaciones

bull - (NSPersistentStoreCoordinator )persistentStoreCoordinator Aquiacute esdonde configuramos los nombres y las ubicaciones de las bases de datos que se usaraacutenpara almacenar los objetos Cuando un managed object necesite guardar algo pasaraacutepor este meacutetodo

bull - (NSManagedObjectContext )managedObjectContext Esta claseNSManagedObjectContext seraacute la maacutes usada con diferencia de las tres y por tanto la

Persistencia

62Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

maacutes importante La utilizaremos baacutesicamente para obtener objetos insertarlos oborrarlos

732 Definiendo el modelo de datos

En el ejemplo que hicimos en la sesioacuten anterior de SQLite creamos dos tablas una paralos datos baacutesicos de la persona y otra para el detalle Ahora haremos lo mismo perousando Core Data la diferencia principal es que mientras que con SQLite obteniamos soacutelolos datos que necesitaacutebamos de la base de datos aquiacute obtenemos el objeto completo

Para definir nuestro modelo de datos basado en dos objetos (Persona y DetallePersona)abrimos el editor visual haciendo click sobre el ficherosesion04_ejemplo2xcdatamodeld Empezaremos creando una nueva entidad para ellohacemos click sobre el botoacuten Add Entity (+) que se encuentra en la parte inferior dela pantalla A la nueva entidad le pondremos como nombre Persona

Dentro de la seccioacuten de Attributes hacemos click sobre (+) y creamos un nuevoatributo para nuestra entidad le llamamos nombre y le asignamos como tipo StringRealizamos este mismo paso 2 veces maacutes para antildeadir apellidos y edad este uacutetlimode tipo Integer 16

Atributos Persona

Ahora creamos la entidad de DetallePersona de la misma manera que la anterior perocreando los atributos localidad pais direccion cp y telefono Todasde tipo String

Atributos DetallePersona

Finalmente nos queda relacionar de alguacuten modo las dos entidades para ello antildeadimos una

Persistencia

63Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

relacioacuten dentro de la entidad de Persona hacia DetallePersona simplementehaciendo click sobre el botoacuten (+) de la seccioacuten de Relationships A la relacioacuten leponemos como nombre detalle seleccionamos como destino la entidadDetallePersona y seraacute No inverse Este tipo de relacioacuten es uno a uno Por dentroCore Data realizaraacute crearaacute una clave ajena en la tabla de persona que apunte aDetallePersona pero eso a nosotros no nos interesa

Vista general modelo de datos (1)

Apple recomienda que siempre que hagamos una relacioacuten de una entidad a otra hagamostambieacuten la relacioacuten inversa por lo tanto hacemos justo lo anterior pero al reveacutes (deDetallePersona a Persona) con la diferencia que en la columna de Inverse

seleccionamos datosPersona

Ya tenemos las entidades y las relaciones creadas Si hacemos click en el botoacuten deEditor Style en la parte de abajo a la derecha del editor podemos ver de una formamaacutes clara la estructura que hemos disentildeado

Persistencia

64Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista general modelo de datos (2)

733 Probando el modelo

Una vez que tenemos el modelo creado pasamos a probarlo para comprobar que estaacute todocorrecto para ello abrimos la clase delegada (sesion04_ejemplo2AppDelegatem) yantildeadimos el siguiente fragmento de coacutedigo arriba del meacutetodoapplicationDidFinishLaunching

NSManagedObjectContext context = [self managedObjectContext]NSManagedObject persona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

[persona setValueJuan forKeynombre][persona setValueMartinez forKeyapellidos][persona setValue[NSNumber numberWithInt28] forKeyedad]

NSManagedObject detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersonainManagedObjectContextcontext]

[detallePersona setValueCalle Falsa 123 forKeydireccion][detallePersona setValueAlicante forKeylocalidad][detallePersona setValueEspantildea forKeypais][detallePersona setValue965656565 forKeytelefono]

[detallePersona setValuepersona forKeydatosPersona][persona setValuedetallePersona forKeydetalle]

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Persistencia

65Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Con este coacutedigo hemos creado un objeto Persona enlazado con otro objetoDetallePersona iexclComo puedes ver no hemos necesitado nada de coacutedigo SQLPrimero creamos un objeto del tipo NSManagedObjectContext que almacena el conextode Core Data Despueacutes creamos el objeto Persona en siacute con NSManagedObject A esteobjeto le pondremos los valores para cada una de las propiedades definidas en el modeloHacemos lo mismo con DetallePersona Finalmente relacionamos el objetoPersona con el objeto DetallePersona

Compilamos el proyecto comprobamos que todo funciona bien y no salen errores

Para comprobar que los datos se han almacenado correctamente en nuestro dispositivo losmostramos en la consola de la siguiente manera

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

intValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(------------------)

[fetchRequest release]

El coacutedigo de arriba lo escribiremos dentro del meacutetododidFinishLaunchingWithOptions de la clase delegada Lo que hace este coacutedigo esrecorrer todos los objetos almacenados en memoria y mostrarlos por consola Siarrancamos la aplicacioacuten veremos que se muestran todos los datos de los objetosalmacenados Cada vez que arranquemos la aplicacioacuten se mostraraacuten maacutes objetos ya quetambieacuten los creamos

Para listar los objetos usamos la clase NSFetchRequest Esta clase es equivalente aejecutar una sentencia Select de SQL Al objeto NSFetchRequest le asignamos laentidad que queremos listar (SELECT FROM ) en nuestro caso seraacute PersonaEl listado de los objetos se obtiene mediante el meacutetodo executeFetchRequest de laclase NSManagedObjectContext que hemos definido en el bloque anterior de coacutedigoEste meacutetodo devolveraacute un NSArray con todos los objetos NSManagedObject

Persistencia

66Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Salida de consola

NotaSi queremos ver queacute sentencias SQL estaacute ejecutando Core Data por debajo tenemos que activarel flag de debug para ello seleccionamos la opcioacuten Product gt Edit Scheme Enla columna de la izquierda seleccionamos el esquema que estamos utilizando para Debug y en laparte de la derecha en el cuadro de Arguments Passed on Launch antildeadimos 2paraacutemetros -comappleCoreDataSQLDebug y 1

Ahora arrancamos de nuevo la aplicacioacuten y veremos que en la consola apareceraacuten lassentencias SQL utilizadas por Core Data

()

INSERT INTO ZDETALLEPERSONA(Z_PK Z_ENT Z_OPT ZDATOSPERSONA ZTELEFONOZLOCALIDAD ZDIRECCION ZPAIS)VALUES( )CoreData sql SELECT 0 t0Z_PK t0Z_OPT t0ZTELEFONO t0ZLOCALIDADt0ZDIRECCION t0ZPAIS t0ZDATOSPERSONAFROM ZDETALLEPERSONA t0 WHERE t0Z_PK =

()

734 Generando los modelos automaacuteticamente

Generar los modelos a mano directamente desde el editor no es lo maacutes recomendado yaque podemos cometer errores de escritura errores de tipo etc La mejor manera dehacerlo es creando un archivo para cada entidad Esto se puede hacer desde 0 peroXCode tiene un generador de clases que es muy faacutecil de usar y ahorremos bastantetiempo iexclVamos a usarlo

Hacemos click sobre el raiz de nuestro proyecto y seleccionamos New File En lacolumna de la izquierda seleccionamos iOS gt Core Data y en el cuadro de laderecha NSManagedObject subclass Click en next En la siguiente pantallaseleccionamos nuestro modelo de datos sesion04_ejemplo2 y click en next Ahoranos aseguramos de seleccionar las dos entidades que hemos creado anteriormentePersona y DetallePersona click en next Seleccionamos el raiz de nuestroproyecto para guardar los ficheros que se generen y aceptamos

Persistencia

67Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ventana de seleccioacuten de entidad

Ahora veremos que xsource ha creado automaacuteticamente 4 ficheros nuevos dentro denuestro proyecto Personamh y DetallePersonamh con todos los atributosespecificados Estas nuevas clases que heredan de NSManagedObject seraacuten las queutilicemos a partir de ahora

Cambiamos el coacutedigo de la clase delegada por el siguiente

NSManagedObjectContext context = [self managedObjectContext]Persona datosPersona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

datosPersonanombre = JuandatosPersonaapellidos = MartinezdatosPersonaedad = [NSNumber numberWithInt28]

DetallePersona detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersona

inManagedObjectContextcontext]detallePersonadireccion = Calle Falsa 123

Persistencia

68Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

detallePersonalocalidad = AlicantedetallePersonapais = EspantildeadetallePersonatelefono = 965656565

detallePersonadatosPersona = datosPersonadatosPersonadetalle = detallePersona

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Listamos los datosNSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

integerValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(-----------------)[fetchRequest release]

Ejecutamos el proyecto y comprobamos que en la consola aparece lo mismo que antespero ahora tenemos el coacutedigo bastante maacutes claro

735 Creando la vista de tabla

Ahora vamos a mostrar los objetos que antes hemos listado por consola en una vista detabla UITableView Para ello creamos un UITableViewController

Click en New file gt UIViewController subclass En la siguiente pantallanos aseguramos de seleccionar Subclass of UITableViewController de esta formanos apareceraacuten ya todos los meacutetodos de la tabla creados por defecto Guardamos elarchivo con el nombre que queramos por ejemplo PersonasViewController

Abrimos ahora PersonasViewControllerh y antildeadimos 2 atributos a la clase

bull Un NSArray que se seraacute el listado de personas (objetos de la clase Persona)bull Una referencia a NSManagedObjectContext

El fichero PersonasViewControllerh quedaraacute de la siguiente manera

Persistencia

69Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

imports iniciales

interface PersonasViewController UITableViewController NSArray _listaPersonasNSManagedObjectContext _context

property (nonatomic retain) NSArray listaPersonasproperty (nonatomic retain) NSManagedObjectContext context

end

Ahora en el m importamos en el encabezado las clases Personah yDetallePersonah y en el meacutetodo viewDidLoad creamos el array listaPersonas contodos los objetos Persona de forma similar a cuando lo hicimos en el apartado anterior

import PersonasViewControllerh

import Personahimport DetallePersonah

implementation PersonasViewController

synthesize listaPersonas = _listaPersonassynthesize context = _context

En viewDidLoad

- (void)viewDidLoad

[super viewDidLoad]

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContext_context]

[fetchRequest setEntityentity]NSError errorselflistaPersonas = [_context executeFetchRequestfetchRequest

erroramperror]selftitle = Listado Personas[fetchRequest release]

Ahora completamos el resto de meacutetodos heredados de UITableViewController de lamisma forma que hicimos en sesiones anteriores

- (NSInteger)numberOfSectionsInTableView(UITableView )tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection(NSInteger)section

Persistencia

70Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Return the number of rows in the sectionreturn [_listaPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]

if (cell == nil) cell = [[[UITableViewCell alloc]

initWithStyleUITableViewCellStyleSubtitlereuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona p = (Persona )[_listaPersonas objectAtIndexindexPathrow]celltextLabeltext = pnombrecelldetailTextLabeltext = [NSString stringWithFormatd antildeos

[pedad intValue]]

return cell

Ahora para poder mostrar la tabla dentro de nuestra aplicacioacuten debemos asignarla dentrode la clase delegada para ello abrimos la vista MainWindowxib y arrastramos unacontroladora de navegacioacuten Navigation Controller a la lista de objetos de laizquierda La desplegamos y dentro de View Controller en la pestantildea de la derechade Identity Inspector escribimos el nombre de nuestra clase que tiene la vista de latabla PersonasViewController

Pantalla del disentildeador

Ahora abrimos el fichero sesion04_ejemplo2AppDelegateh y creamos un

Persistencia

71Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

UINavigationController dentro de la declaracioacuten de variables dejando el ficherocomo sigue

imports iniciales

interface sesion04_ejemplo2AppDelegate NSObject ltUIApplicationDelegategt

UINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet UINavigationControllernavController

property (nonatomic retain readonly) NSManagedObjectContextmanagedObjectContextproperty (nonatomic retain readonly) NSManagedObjectModelmanagedObjectModelproperty (nonatomic retain readonly) NSPersistentStoreCoordinatorpersistentStoreCoordinator

- (void)saveContext- (NSURL )applicationDocumentsDirectory

end

Dentro del m importamos la clase PersonasViewControllerh y completamos elsynthetize

synthesize navController = _navController

Y por uacuteltimo justo antes de la llamada al meacutetodo makeKeyAndVisible escribimos elsiguiente coacutedigo para asignar el contexto de la clase delegada alPersonasViewController

PersonasViewController root = (PersonasViewController ) [_navControllertopViewController]

rootcontext = [self managedObjectContext][selfwindow addSubview_navControllerview]

Ahora compilamos y ejecutamos nuestro proyecto y si todo ha ido bien deberiacutea de salirla tabla con los datos de las personas

Persistencia

72Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Listado de personas

736 Migraciones de bases de datos con Core Data

Una vez que tenemos disentildeada nuestra base de datos inicial iquestqueacute pasariacutea si queremosmodificarla para antildeadirle un campo maacutes a una tabla iquestQueacute haraacute Core Data para realizar lamigracioacuten de la versioacuten anterior a la actual

Siguiendo con el ejemplo anterior si despueacutes de ejecutar la aplicacioacuten en nuestrodispositivo o simulador modificamos la estructura de la base de datos antildeadiendo unatributo nuevo en la entidad DetallePersona por ejemplo codigoPostal con el tipoInteger 16 veremos que al volver a arrancar la aplicacioacuten nos apareceraacute un error entiempo de ejecucioacuten similar al siguiente

Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 Theoperationcouldnrsquot be completed(Cocoa error 134100) UserInfo=0x6b6c680 metadata=ltCFBasicHash 0x6b68380[0x1337b38]gttype = immutable dict count = 7entries =gt

2 ltCFString 0x6b70320 [0x1337b38]gtcontents =NSStoreModelVersionIdentifiers

=ltCFArray 0x6b706e0 [0x1337b38]gttype = immutable count = 1

values = (0 ltCFString 0x1332cd8 [0x1337b38]gtcontents =

)4 ltCFString 0x6b70350 [0x1337b38]gtcontents =

NSPersistenceFrameworkVersion=

Persistencia

73Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltCFNumber 0x6b6f2c0 [0x1337b38]gtvalue = +386 type =kCFNumberSInt64Type

6 ltCFString 0x6b70380 [0x1337b38]gtcontents =NSStoreModelVersionHashes =

ltCFBasicHash 0x6b707e0 [0x1337b38]gttype = immutable dict count =2entries =gt

1 ltCFString 0x6b70700 [0x1337b38]gtcontents = Persona =ltCFData 0x6b70740

[0x1337b38]gtlength = 32 capacity = 32 bytes =0x1c50e5a379ff0ddc3cda7b82a3934c5e 75649ac3c919beea2 ltCFString 0x6b70720 [0x1337b38]gtcontents = DetallePersona

=ltCFData 0x6b70790[0x1337b38]gtlength = 32 capacity = 32 bytes =0xb7ba2eb498f9a1acdd6630d56f6cd12c e8963dd3e488ba95

7 ltCFString 0x10e3aa8 [0x1337b38]gtcontents = NSStoreUUID =ltCFString 0x6b70510[0x1337b38]gtcontents = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E8 ltCFString 0x10e3948 [0x1337b38]gtcontents = NSStoreType =ltCFString 0x10e3958[0x1337b38]gtcontents = SQLite9 ltCFString 0x6b703b0 [0x1337b38]gtcontents =

_NSAutoVacuumLevel = ltCFString0x6b70830 [0x1337b38]gtcontents = 210 ltFString 0x6b703d0 [0x1337b38]gtcontents =

NSStoreModelVersionHashesVersion=ltCFNumber 0x6b61950 [0x1337b38]gtvalue = +3 type =

kCFNumberSInt32Type reason=The model used to open the store is incompatible with the oneused tocreate the store

metadata = NSPersistenceFrameworkVersion = 386NSStoreModelVersionHashes =

DetallePersona = ltb7ba2eb4 98f9a1ac dd6630d5 6f6cd12c f890007746b16f00

e8963dd3 e488ba95gtPersona = lt1c50e5a3 79ff0ddc 3cda7b82 a3934c5e 5cb4ee4b

4ac98838 75649ac3c919beeagt

NSStoreModelVersionHashesVersion = 3NSStoreModelVersionIdentifiers = (

)NSStoreType = SQLiteNSStoreUUID = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E_NSAutoVacuumLevel = 2

reason = The model used to open the store is incompatible with the

one usedto create the store

Este error es muy comuacuten cuando estamos desarrollando aplicaciones con Core DataBaacutesicamente nos dice que el modelo de base de datos que estaacute intentando cargar dememoria es distinto al que se indica en la aplicacioacuten esto es porque no hemos indicadoninguacuten meacutetodo de migracioacuten Realmente existen dos formas de solucionar este error

Persistencia

74Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Por un lado y soacutelo cuando estemos desarrollando y no tengamos ninguna versioacutende nuestra aplicacioacuten lanzada en la App Store podremos eliminar la base de datosdel dispositivo simulador simplemente borrando la aplicacioacuten de el Tambieacutenpodemos ejecutar el siguiente coacutedigo una soacutela vez [[NSFileManagerdefaultManager] removeItemAtURLstoreURL errornil]

bull En el caso de que estemos realizando una migracioacuten sobre una versioacuten ya publicadaen la App Store el meacutetodo anterior no nos serviriacutea ya que obligariacuteamos al usuario aborrar su aplicacioacuten del dispositivo con lo que todos los datos que tuviera seperderiacutean Esto no es viable por lo que necesitaremos usar otro meacutetodo para realizarla migracioacuten y solucionar el error de una forma maacutes limpia este meacutetodo es el quevamos a comentar a continuacioacuten

XCode soporta Versioning para Core Data esto es que podemos crear versiones de labase de datos de una forma sencilla si seguimos una serie de pasos

1) Antildeadimos las siguientes opciones en forma de NSDictionary a nuestro coacutedigo delpersistentStoreCoordinator

NSDictionary options = [NSDictionary dictionaryWithObjectsAndKeys[NSNumber numberWithBoolYES]NSMigratePersistentStoresAutomaticallyOption[NSNumber numberWithBoolYES]NSInferMappingModelAutomaticallyOptionnil]

Y modificamos el meacutetodo addPersistentStoreWithType pasaacutendole como paraacutemetro eldiccionario options

if ([__persistentStoreCoordinatoraddPersistentStoreWithTypeNSSQLiteStoreTypeconfigurationnil URLstoreURL optionsoptions erroramperror])

Con el coacutedigo anterior indicamos al persistentStoreCoordinator que la migracioacuten enel caso de que se deba de realizar sea de forma automaacutetica

AtencioacutenDeberemos de tener en cuenta que este tipo de migracioacuten que estamos estudiando en este puntosoacutelo es vaacutelida para cambios simples en Core Data lo que Apple llama como lightweightmigration Este tipo de migracioacuten admite una serie de cambios que se indican dentro de ladocumentacioacuten de Apple para cambios maacutes complejos deberemos realizar migracionespersonalizadas y mucho maacutes complejas El documento en donde Apple explica el tipo demigraciones que existen para Core Data y su forma de implementarlas lo podemos consultardesde esta web

2) Ahora deberemos de crear la versioacuten del esquema de Core Data dentro de XCode para

Persistencia

75Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ello seleccionamos el fichero del esquema sesion04_ejemplo2xcdatamodeld yseleccionamos en el menuacute principal Editor gt Add Model Version A la nueva versioacuten lallamaremos sesion04_ejemplo_2 (versioacuten 2) Una vez que hayamos pulsado sobreFinish se nos habraacute creado la nueva versioacuten del esquema dentro de la principal y estaraacutemarcada con un tick la versioacuten anterior

3) Una vez que tenemos creada la nueva versioacuten deberemos modificarla antildeadiendole elnuevo atributo Seleccionamos la versioacuten recieacuten creadasesion04_ejemplo_2xcdatamodel y antildeadimos a la entidad DetallePersona elatributo codigoPostal Atencioacuten Si este atributo lo hemos antildeadido anteriormentedeberemos de borrarlo de la versioacuten anterior y antildeadirlo en la segunda versioacuten

4) Ahora nos queda indicar a Core Data que queremos usar la versioacuten 2 de la base dedatos para nuestra aplicacioacuten Para ello seleccionamos el esquema principal (el raiz)sesion04_ejemplo2xcdatamodeld y en la ventana de la derecha en la pestantildea de FileInspector seleccionamos como Versioacuten actual la versioacuten 2 esto lo hacemos dentro delapartado de Versioned Core Data Model Automaacuteticamente veremos que el siacutembolo detick cambia a la versioacuten 2

5) Ahora ya podemos volver a compilar y veremos que no nos aparece el error y funcionatodo seguacuten lo esperado

Magical RecordExisten varias librerias de coacutedigo libre que facilitan enormemente el uso de Core Data ennuestras aplicaciones iOS Una de ellas es Magical Record Mediante esta libreriacutea podremos usarCore Data sin tener que preocuparnos de todo el proceso de configuracioacuten en las controladorasde la configuracioacuten de migraciones etc Su instalacioacuten en un proyecto de XCode es muy faacutecil yaltamente recomendable ya que proporciona una API muy clara y sencilla de usar

Persistencia

76Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios

81 Usando User Defaults (1 punto)

En este ejercicio vamos a usar las variables de usuario para almacenar y mostrar los datosdel dispositivo que estamos usando y las veces que hemos arrancado la aplicacioacuten

Crear un proyecto nuevo usando la plantilla Single View Application con el nombreejercicio-userdefaults-ios Se debe de ejecutar en iPhone e iPad (Universal) Comoidentificador de empresa escribiremos esuajtech y como prefijo de clase UA Debe detener el ARC activado (desmarcar el resto de opciones)

Ayuda meacutetodos de UIDevicePara obtener los datos del dispositivo usaremos el singleton UIDevice estos son algunos de losmeacutetodos que podemos usar [[UIDevice currentDevice] name] [[UIDevicecurrentDevice] systemName] [[UIDevice currentDevice]systemVersion] [[UIDevice currentDevice] model] [[UIDevicecurrentDevice] localizedModel]

82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)

En este ejercicio disentildearemos un modelo de datos de dos tablas relacionadas entre ellasantildeadiremos datos por coacutedigo y los mostraremos en una vista de tabla La base de datosque vamos a implementar constaraacute de dos tablas por un lado una que contendraacute los tiacutetulosde series y otra que tendraacute los detalles A la primera la llamaremos Serie y la segundaDetalleSerie Para realizar correctamente el ejercicio deberemos seguir los siguientespasos

1) Crear un proyecto nuevo usando la plantilla Master-Detail Application con elnombre ejercicio-coredata-ios Se debe de ejecutar en iPhone como identificador deempresa escribiremos esuajtech y como prefijo de clase UA Debe de tener el ARCactivado y la opcioacuten de Use Core Data (desmarcar el resto de opciones)

2) Disentildear el siguiente modelo de datos

Persistencia

77Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esquema modelo de datos

3) Revisar el fichero UAAppDelegatem y comprobar que tenemos todos los meacutetodosnecesarios para que funcione Core Data en nuestra aplicacioacuten

4) Modificar el fichero UAMasterViewControllerm para que se muestren los tiacutetulos delas series en la tabla

5) Probar la aplicacioacuten antildeadiendo unos tiacutetulos de prueba

83 Migrando datos con Core Data (05 puntos)

Una vez que hemos disentildeado la Base de Datos con Core Data vamos a modificarlaantildeadiendo un atributo maacutes a la tabla de DetalleSerie valoracion

a) iquestQueacute pasa si cambiamos el modelo de datos y arrancamos la aplicacioacuten iquestQueacute errornos da y por queacute

b) iquestCoacutemo podemos evitar este tipo de errores cuando estemos desarrollando iquestY siestamos en produccioacuten con la aplicacioacuten ya a la venta

c) Crea una nueva versioacuten del modelo

d) Modifica el coacutedigo necesario dentro de la clase UAAppDelegate para evitar este tipo deerrores cuando estemos en un entorno de produccioacuten

e) Vuelve a arrancar la aplicacioacuten y comprueba que funciona bien

Persistencia

78Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Persistencia

79Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

  • 1 Persistencia en Android ficheros y SQLite
    • 11 Introduccioacuten
    • 12 Manejo de ficheros tradicionales en Android
      • 121 Apertura de ficheros
      • 122 Ficheros como recursos
      • 123 Operar con ficheros
      • 124 Almacenar datos en la tarjeta de memoria
        • 13 Base de datos SQLite
          • 131 Content Values
          • 132 Cursores
          • 133 Trabajar con bases de datos SQLite
          • 134 La clase SQLiteOpenHelper
          • 135 Crear una base de datos sin SQLiteHelper
          • 136 Realizar una consulta
          • 137 Extraer resultados de un cursor
          • 138 Antildeadir actualizar y borrar filas
              • 2 Ejercicios - Persistencia en Android ficheros y SQLite
                • 21 Uso de ficheros (05 puntos)
                • 22 Persistencia con ficheros (05 puntos)
                • 23 Base de datos SQLiteOpenHelper (05 puntos)
                • 24 Base de datos insercioacuten y borrado (05 puntos)
                • 25 Base de datos probar nuestro adaptador (05 puntos)
                • 26 Base de datos cambios en la base de datos (05 puntos)
                  • 3 Persistencia en Android proveedores de contenidos y SharedPreferences
                    • 31 Shared Preferences
                      • 311 Guardar Shared Preferences
                      • 312 Leer Shared Preferences
                      • 313 Interfaces para Shared Preferences
                      • 314 Definiendo una pantalla de preferencias con un layout en XML
                      • 315 Controles nativos para preferencias
                      • 316 Actividades de preferencias
                      • 317 Shared Preference Change Listeners
                        • 32 Proveedores de contenidos
                          • 321 Proveedores nativos
                          • 322 Proveedores propios crear un nuevo proveedor de contenidos
                          • 323 Proveedores propios crear la interfaz de consultas
                          • 324 Proveedores propios tipo MIME
                          • 325 Proveedores propios registrar el proveedor
                          • 326 Content Resolvers
                          • 327 Otras operaciones con Content Resolvers
                              • 4 Ejercicios - Persistencia en Android proveedores de contenidos y SharedPreferences
                                • 41 Compartir datos entre actividades con Shared Preferences (075 puntos)
                                • 42 Actividad de preferencias (075 puntos)
                                • 43 Proveedor de contenidos propio (075 puntos)
                                • 44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)
                                  • 5 Persistencia de datos en iOS Ficheros y SQLite
                                    • 51 Introduccioacuten
                                    • 52 Ficheros plist (Property Lists)
                                      • 521 Leyendo datos desde ficheros plist
                                      • 522 Escribiendo datos en ficheros plist
                                        • 53 SQLite
                                          • 531 Herramientas disponibles
                                          • 532 Lectura de datos
                                          • 533 Mostrando los datos en una tabla
                                              • 6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios
                                                • 61 Creando y leyendo un fichero plist (15 puntos)
                                                • 62 Implementar vista de detalle de la serie (05 puntos)
                                                • 63 Uso baacutesico de una base de datos SQLite (1 punto)
                                                  • 7 Persistencia de datos en iOS User Defaults y Core Data
                                                    • 71 Introduccioacuten
                                                    • 72 User Defaults
                                                    • 73 Core Data
                                                      • 731 Creando el proyecto
                                                      • 732 Definiendo el modelo de datos
                                                      • 733 Probando el modelo
                                                      • 734 Generando los modelos automaacuteticamente
                                                      • 735 Creando la vista de tabla
                                                      • 736 Migraciones de bases de datos con Core Data
                                                          • 8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios
                                                            • 81 Usando User Defaults (1 punto)
                                                            • 82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)
                                                            • 83 Migrando datos con Core Data (05 puntos)
Page 13: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de

El primer paso para obtener resultados de un cursor seraacute desplazarnos a la posicioacuten apartir de la cual queremos obtener los datos usando cualquiera de los meacutetodosespecificados anteriormente (como por ejemplo moveToFirst o moveToPosition) Unavez hecho esto hacemos uso de meacutetodos get[TIPO] pasando como paraacutemetro el iacutendice dela columna Esto tendraacute como resultado la devolucioacuten del valor para dicha columna de lafila actualmente apuntada por el cursor

String valor = miResultadogetString(indiceColumna)

A continuacioacuten podemos ver un ejemplo maacutes completo en el que se va iterando a lo largode los resultados apuntados por un cursor extrayendo y sumando los valores de unacolumna de flotantes

int COLUMNA_VALORES_FLOTANTES = 2Cursor miResultado = dbquery(Tabla null null null null nullnull)float total = 0

Nos aseguramos de que se haya devuelto al menos una filaif (miResultadomoveToFirst())

Iteramos sobre el cursordo

float valor =miResultadogetFloat(COLUMNA_VALORES_FLOTANTES)

total += valor while (miResultadomoveToNext())

float media = total miResultadogetCount()

NotaDebido a que las columnas de las bases de datos en SQLite son debilmente tipadas podemosrealizar castings cuando sea necesario Por ejemplo los valores guardados como flotantes en labase de datos podriacutean leerse maacutes adelante como cadenas

138 Antildeadir actualizar y borrar filas

La clase SQLiteDatabase proporciona los meacutetodos insert delete y update queencapsulan las instrucciones SQL requeridas para llevar a cabo estas acciones Ademaacutesdisponemos de la posibilidad de utilizar el meacutetodo execSQL el cual nos permite ejecutarcualquier sentencia SQL vaacutelida en nuestras tablas de la base de datos en el caso en el quequeramos llevar a cabo estas (u otras) operaciones manualmente

NotaCada vez que modifiques los valores de la base de datos puedes actualizar un cursor que se puedaver afectado por medio del meacutetodo refreshQuery de la clase Cursor

Para insertar una nueva fila es necesario construir un objeto de la clase ContentValues

y usar su meacutetodo put para proporcionar valor a cada una de sus columnas Para insertar la

Persistencia

13Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

nueva fila pasaremos como paraacutemetro este objeto del tipo ContentValues al meacutetodoinsert de la instancia de la base de datos junto por supuesto con el nombre de la tablaA continuacioacuten se muestra un ejemplo

Crear la nueva filaContentValues nuevaFila = new ContentValues()

Asignamos valores a cada columnanuevaFilaput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas ]

Insertar la nueva fila en la tabladbinsert(TABLA_BASE_DATOS null nuevaFila)

NotaLos ficheros como imaacutegenes o ficheros de audio no se suelen almacenar dentro de tablas en unabase de datos En estos casos se suele optar por almacenar en la base de datos una cadena con laruta al fichero o una URI

La operacioacuten de actualizar una fila tambieacuten puede ser llevada a cabo por medio del usode la clase ContentValues En este caso deberemos llamar al meacutetodo update de lainstancia de la base de datos pasando como paraacutemetro el nombre de la tabla el objetoContentValues y una claacuteusula where que especifique la fila o filas a actualizar tal comose puede ver en el siguiente ejemplo

Creamos el objeto utilizado para definir el contenido a actualizarContentValues valoresActualizar = new ContentValues()

Asignamos valores a las columnas correspondientesvaloresActualizarput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas a actualizar ]

String where = CLAVE_ID + = + idFila

Actualizamos la fila con el iacutendice especificado en la instruccioacutenanteriordbupdate(TABLA_BASE_DATOS valoresActualizar where null)

Por uacuteltimo para borrar una fila simplemente llamaremos al meacutetodo delete de lainstancia de la base de datos especificando en este caso el nombre de la tabla que se veraacuteafectada por la operacioacuten y una claacuteusula where que utilizaremos para especificar las filasque queremos borrar Un ejemplo podriacutea ser el siguiente

dbdelete(TABLA_BASE_DATOS CLAVE_ID + = + idFila null)

Persistencia

14Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

2 Ejercicios - Persistencia en Android ficheros y SQLite

21 Uso de ficheros (05 puntos)

En este ejercicio vamos a crear una aplicacioacuten que muestre un listado de cadenas porpantalla Estas cadenas se almacenaraacuten en un fichero de texto privado para la aplicacioacutenPodremos antildeadir nuevas cadenas a partir de un cuadro de edicioacuten presente en la interfazPartiremos del proyecto Ficheros proporcionado en las plantillas de la aplicacioacuten Debesseguir los siguientes pasos

bull Antildeadir un manejador al botoacuten de la actividad principal para que cada vez que seapulsado se guarde en un fichero de texto El fichero se llamaraacute ficherotxt Al abrirel fichero para escritura se utilizaraacute el paraacutemetro ContextMODE_APPEND con lo quecada nueva cadena se antildeadiraacute al final del fichero en el caso en el que eacuteste ya existieraPara escribir en el fichero utilizaremos el meacutetodo writeBytes del objetoDataOutputStream correspondiente

bull Al pulsar el botoacuten tambieacuten se deberaacute borrar el contenido del elemento EditText (leasignamos a la vista una cadena vaciacutea con el meacutetodo setText)

bull Ejecutamos la aplicacioacuten e introducimos algunas liacuteneas en el fichero Vamos acomprobar ahora que todo ha funcionado correctamente Para ello accedemos alsistema de ficheros de nuestro dispositivo ejecutando el comando adb shell

dentro de la carpeta platform-tools de nuestra carpeta de instalacioacuten del SDK deAndroid

bull Comprueba que se ha creado el fichero ficherotxt en la carpetadatadataesuajtechandroidficherosfiles Examina su contenido ejecutando cat

ficherotxt Si algo no ha salido bien siempre podraacutes eliminar el fichero con rm

ficherotxtbull En la interfaz de la actividad se ha incluido una vista de tipo TextView debajo del

botoacuten Antildeade un manejador para dicha vista de tal forma que cada vez que se hagaclick sobre ella se muestre el contenido del fichero ficherotxt Para ello leeremos elfichero liacutenea a liacutenea antildeadiendo cada una al TextView por medio del meacutetodo append

Persistencia

15Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Interfaz de la aplicacioacuten Ficheros

22 Persistencia con ficheros (05 puntos)

Seguramente habraacutes observado que si abandonas la aplicacioacuten anterior (pulsando el botoacutenBACK del dispositivo) al volver a ejecutarla el contenido del TextView ha desaparecidoDebes volver a pulsar sobre la vista para que se vuelva a mostrar el contenido del ficheroHaz las modificaciones pertinentes para que esto no sea asiacute es decir para que cuandovuelva a mostrarse la actividad tras haber sido eliminada de la memoria se muestre elTextView tal cual se veiacutea antes de abandonar la aplicacioacuten

AvisoEn este ejercicio no se estaacute pidiendo que cargues el contenido del fichero en el TextView nadamaacutes arrancar la aplicacioacuten Puede darse el caso de que lo que esteacute mostrando el TextViewcuando la actividad sea eliminada de memoria no se corresponda con el contenido actualizado delfichero

NotaPara poder completar el ejercicio deberaacutes repasar los manejadores de evento de la primera sesioacutendel moacutedulo de Android relacionados con el ciclo de vida de ejecucioacuten de actividades

23 Base de datos SQLiteOpenHelper (05 puntos)

En las plantillas de la aplicacioacuten se incluye la aplicacioacuten BaseDatos En el proyecto de laaplicacioacuten se incluye el esqueleto de una clase MiAdaptadorBD que tendremos quecompletar Se trata de un patroacuten que nos va a permitir acceder a una base de datos de

Persistencia

16Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

usuarios dentro de la aplicacioacuten sin necesidad de hacer uso de coacutedigo SQL

La clase MiAdaptadorBD incluye a su vez otro patroacuten en este caso implementado comouna subclase de SQLiteOpenHelper Eacuteste nos obliga a definir queacute ocurre cuando la basede datos todaviacutea no existe y debe ser creada y queacute ocurre si ya existe pero debe seractualizada porque ha cambiado de versioacuten Asiacute el SQLiteOpenHelper queimplementemos en este caso MiOpenHelper nos devolveraacute siempre una base de datosseparaacutendonos de la loacutegica encargada de comprobar si la base de datos existe o no

En este primer ejercicio se pide hacer lo siguiente

bull Ejecutar la sentencia de creacioacuten de bases de datos (la tenemos declarada comoconstante de la clase) en el meacutetodo MiOpenHelperonCreate

bull Implementar tambieacuten el meacutetodo onUpgrade Idealmente eacuteste deberiacutea portar las tablasde la versioacuten antigua a la versioacuten nueva copiando todos los datos Nosotros vamos aeliminar directamente la tabla que tenemos con la sentencia SQL DROP TABLE IF

EXISTS + NOMBRE_TABLA y volveremos a crearlabull En el constructor de MiAdaptadorBD debemos obtener en el campo db la base de

datos utilizando MiOpenHelper

24 Base de datos insercioacuten y borrado (05 puntos)

Continuamos trabajando con el proyecto anterior En este ejercicio completaremos elcoacutedigo relacionado con las sentencias de insercioacuten y borrado Para realizar la insercioacutenvamos a hacer uso de un mecanismo que no hemos visto en la sesioacuten de teoriacutea y queconsiste en utilizar una sentencia de insercioacuten SQL precompilada que se completaraacute conlos valores concretos a insertar en la base de datos justo antes de realizar dicha insercioacutenLa ventaja de las sentencias compiladas es que evitan que se produzcan ataques deinyeccioacuten de coacutedigo

Como se puede observar se ha incluido un atributo a la clase que representa la insercioacutenSQL

private static final String INSERT = insert into + NOMBRE_TABLA +(+COLUMNAS[1]+) values ()

En esta sentencia no se indica ninguacuten valor concreto para la columna nombre En el lugaren el que deberiacutean aparecer los valores de dicho campo se ha escrito simplemente unsiacutembolo de interrogacioacuten Tambieacuten se ha antildeadido una instancia de la claseSQLiteStatement como parte de los atributos de la clase

private SQLiteStatement insertStatement

Realizamos los siguientes pasos

bull En el constructor de la clase MiAdaptadorBD llevamos a cabo la compilacioacuten de lasentencia

thisinsertStatement = thisdbcompileStatement(INSERT)

Persistencia

17Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Una vez hecho esto cada vez que deseemos insertar un nuevo usuario en la base dedatos deberemos dar valores concretos a la columna nombre y ejecutar la sentenciaPara ello debemos antildeadir el siguiente coacutedigo dentro del meacutetodo insert de la claseMiAdaptadorBD

thisinsertStatementbindString(1 nombreUsuario)return thisinsertStatementexecuteInsert()

bull Por uacuteltimo completamos el coacutedigo del meacutetodo deleteAll cuyo contenido es eliminara todos los usuarios de la base de datos Para que ademaacutes se devuelva el nuacutemero defilas afectadas podemos insertar la siguiente liacutenea en dicho meacutetodo

return dbdelete(NOMBRE_TABLA null null)

Una vez hechos todos estos cambios podremos utilizar la clase MiAdaptadorBD parahacer operaciones con la base de datos de usuarios de manera transparente

25 Base de datos probar nuestro adaptador (05 puntos)

Antildeade coacutedigo en la actividad Main para eliminar todos los usuarios de la base de datosantildeadir dos cualesquiera y listarlos por medio de la vista de tipo TextView que seencuentra en el layout de dicha actividad

Podemos comprobar mediante la liacutenea de comandos (comando adb shell) que la basede datos ha sido creada Para ello puede ser uacutetil hacer uso de los siguientes comandos

cd datadataesuajtechandroidbasedatosdatabasessqlite3 misusuariosdbsqlitegt schemasqlitegt tablessqlitegt select from usuarios

26 Base de datos cambios en la base de datos (05 puntos)

Ahora vamos a cambiar en la clase MiAdaptadorBD el nombre de la segunda columnaque en lugar de nombre se va a llamar nombres Ejecutamos la aplicacioacuten y comprobamosque sale con una excepcioacuten Comprueba por medio de LogCat cuaacutel ha sido el erroriquestCoacutemo lo podemos solucionar

NotaPista conforme hemos programado la clase MiAdaptadorBD y siguiendo el patroacuten de disentildeode SQLiteOpenHelper es posible arreglar el problema simplemente modificando el valor deun campo

Persistencia

18Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

3 Persistencia en Android proveedores de contenidos ySharedPreferences

31 Shared Preferences

Comenzamos esta sesioacuten hablando de una de las teacutecnicas maacutes simples de persistencia enAndroid junto al uso de ficheros Shared Preferences Se trata de un mecanismo ligeropara almacenar datos basado en pares clave-valor utilizado normalmente para guardarpreferencias u opciones de la aplicacioacuten Tambieacuten puede ser utilizado para almacenar elestado de la interfaz graacutefica para contemplar el caso de que nuestra actividad debafinalizar abruptamente su ejecucioacuten Otro posible uso es el de compartir informacioacutenentre componentes de una misma aplicacioacuten

NotaLos Shared Preferences nunca son compartidos entre diferentes aplicaciones

Este meacutetodo se basa en el uso de la clase SharedPreferences Es posible utilizarla paraguardar datos en muy diversos formatos booleanos cadenas flotantes y enteros

311 Guardar Shared Preferences

Para crear o modificar un Shared Preference llamamos al meacutetodogetSharedPreferences del contexto de la aplicacioacuten pasando como paraacutemetro elidentificador del conjunto de valores de preferencias con el que queremos trabajar Pararealizar la modificacioacuten del valor de un Shared Preference hacemos uso de la claseSharedPreferencesEditor Para obtener el objeto editor invocamos el meacutetodo edit

del objeto Shared Preferences que queremos modificar Los cambios se guardan medianteel meacutetodo commit Veamos un ejemplo

int modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences = getSharedPreferences(Mispreferencias modo)

Obtenemos un editor para modificar las preferenciasSharedPreferencesEditor editor = misSharedPreferencesedit()

Guardamos nuevos valores en el objeto SharedPreferenceseditorputBoolean(isTruetrue)editorputFloat(unFloat10)editorputInt(numeroEntero12)editorputLong(otroNumero2)editorputString(unaCadenavalor)

Guardamos los cambioseditorcommit()

Persistencia

19Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

312 Leer Shared Preferences

Para leer Shared Preferences utilizamos el meacutetodo getSharedPreferences tal como seha comentado anteriormente Pasamos como paraacutemetro el identificador del conjunto deShared Preferences al que deseamos acceder Una vez hecho esto ya podemos hacer usode meacutetodos de tipo get para acceder a preferencias individuales Hay diferentes meacutetodosget para diferentes tipos de datos Cada uno de ellos requiere como paraacutemetro la claveque identifica la preferencia cuyo valor deseamos obtener y un valor por defecto el cualse usaraacute en el caso concreto en el que no existiera la preferencia con la clave pasada comoprimer paraacutemetro Podemos ver un ejemplo a continuacioacuten

public static String MIS_PREFS = MIS_PREFS

public void cargarPreferences() Obtener las preferencias almacenadasint modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences =

getSharedPreferences(MIS_PREFS modo)

boolean isTrue = misSharedPreferencesgetBoolean(isTruefalse)float unFloat = misSharedPreferencesgetFloat(unFloat0)int numeroEntero = misSharedPreferencesgetInt(numeroEntero0)long otroNumero = misSharedPreferencesgetLong(otroNumero0)String unaCadena = misSharedPreferencesgetString(unaCadena)

313 Interfaces para Shared Preferences

Android proporciona una plataforma basada en XML para la creacioacuten de interfacesgraacuteficas para el manejo de preferencias Estas interfaces tendraacuten un aspecto similar al delas del resto de aplicaciones del sistema Al utilizarla nos estaremos asegurando de quenuestras actividades para el manejo de preferencias seraacuten consistentes con las del resto deaplicaciones del sistema De esta forma los usuarios estaraacuten familiarizados con el manejode esta parte de nuestra aplicacioacuten

La plataforma consiste en tres elementos

bull Layout de la actividad de preferencias se trata de un archivo XML que definiraacute lainterfaz graacutefica de la actividad que muestre los controles para modificar los valores delas preferencias Permite especificar las vistas a mostrar los posibles valores que sepueden introducir y a queacute clave de Shared Preferences se corresponde cada vista

bull Actividad de preferencias una subclase de PreferenceActivity que secorresponderaacute con la pantalla de preferencias de nuestra aplicacioacuten

bull Shared Preference Change Listener una implementacioacuten de la claseonSharedPreferenceChangeListener que actuaraacute en el caso en el que cambie elvalor de alguna Shared Preference

314 Definiendo una pantalla de preferencias con un layout en XML

Persistencia

20Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esta es sin duda la parte maacutes importante de una actividad de preferencias Se trata de unarchivo XML utilizado para definir varios aspectos de dicha actividad Al contrario queen el caso de los archivos de layout para interfaces graacuteficas que vimos en la sesioacutencorrespondiente del moacutedulo de Android estos archivos de layout se guardan en la carpetaresxml de los recursos de la aplicacioacuten

Aunque conceptualmente estos layouts son similares a los utilizados para definirinterfaces graacuteficas los layouts de actividades de preferencias utilizan un conjuntoespecializado de componentes especiacuteficos para este tipo de actividades Estaacuten pensadospara proporcionar interfaces con un aspecto similar al del resto de actividades depreferencias del sistema Estos controles se describen en maacutes detalle en la siguienteseccioacuten

Los layout de preferencias contendraacuten un elemento PreferenceScreen

ltxml version=10 encoding=utf-8gtltPreferenceScreen

xmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceScreengt

Se pueden antildeadir maacutes elementos de este tipo si se desea Al hacerlo eacutestos serepresentaraacuten como un elemento seleccionable en un listado que mostraraacute una nuevaventana de preferencias en caso de que sea seleccionado

Dentro de cada elemento PreferenceScreen podemos incluir tantos elementos de tipoPreferenceCategory y elementos especiacuteficos para antildeadir controles como se desee Loselementos PreferenceCategory son utilizados para dividir cada pantalla de preferenciasen subcategoriacuteas mediante una barra de tiacutetulo Su sintaxis es la siguiente

ltPreferenceCategoryandroidtitle=My Preference Categorygt

ltPreferenceCategorygt

Una vez dividida la pantalla en subcategoriacuteas tan soacutelo quedariacutea antildeadir los elementoscorrespondientes a los controles especiacuteficos que veremos en la siguiente seccioacuten Losatributos de los elementos XML para estos controles pueden variar aunque existe unconjunto de atributos comunes

bull androidkey la clave de Shared Preference que se usaraacute para guardar el valor delcontrol

bull androidtitle texto a mostrar para describir la preferenciabull androidsummary una descripcioacuten maacutes larga a mostrar debajo del texto definido con

el campo anteriorbull androiddefaultValue el valor por defecto que se mostraraacute inicialmente y tambieacuten

el que finalmente se guardaraacute si ninguacuten otro valor fue asignado a este campo

El siguiente listado muestra una pantalla de preferencias muy simple con una uacutenicacategoriacutea y un uacutenico control (un check box)

ltxml version=10 encoding=utf-8gt

Persistencia

21Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltPreferenceScreenxmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceCategory

androidtitle=Mi categoriacuteagtltCheckBoxPreference

androidkey=MI_CHECK_BOXandroidtitle=Preferencia Check Boxandroidsummary=Descripcioacuten de la preferencia

Check BoxandroiddefaultValue=true

gtltPreferenceCategorygt

ltPreferenceScreengt

Esta pantalla de preferencias se mostrariacutea de la siguiente forma

Ejemplo sencillo de ventana de preferencias

315 Controles nativos para preferencias

Android incluye diferentes controles que podemos antildeadir a nuestras ventanas depreferencias por medio de los elementos XML que se indican a continuacioacuten

bull CheckBoxPreference un check box estaacutendar que puede ser utilizado parapreferencias de tipo booleano

bull EditTextPreference permite al usuario introducir una cadena como valor para unapreferencia Al seleccionar el texto se mostraraacute un diaacutelogo con el que introducir elnuevo valor

bull ListPreference el equivalente a un Spinner Seleccionar este elemento de lainterfaz mostraraacute un diaacutelogo con las diferentes opciones que es posible seleccionar Seutilizan arrays para asociar valores y textos a las opciones de la lista

bull RingtonePreference un tipo especiacutefico de preferencia de tipo listado que permiteseleccionar entre diferentes tonos de teleacutefono Esta opcioacuten es particularmente uacutetil en

Persistencia

22Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

el caso en el que se esteacute creando una pantalla de preferencias para configurar lasopciones de notificacioacuten de una aplicacioacuten o componente de la misma

Tambieacuten es posible crear nuestros propios controles personalizados definiendo unasubclase de Preference (o cualquiera de sus subclases) Se puede encontrar maacutesinformacioacuten en la documentacioacuten de AndroidhttpdeveloperandroidcomreferenceandroidpreferencePreferencehtml

316 Actividades de preferencias

Para mostrar una pantalla de preferencias debemos definir una subclase dePreferenceActivity

public class MisPreferencias extends PreferenceActivity

La interfaz graacutefica de la interfaz se puede crear a partir del layout en el meacutetodo onCreatemediante una llamada a addPreferencesFromResource

Overridepublic void onCreate(Bundle savedInstaceState)

superonCreate(savedInstanceState)addPreferencesFromResource(Rxmlmilayout)

Como en el caso de cualquier otra actividad la actividad de preferencias que hayamosdefinido debe ser incluida en el Manifest de la aplicacioacuten

ltactivity androidname=MisPreferenciasandroidlabel=Mis preferenciasgt

ltactivitygt

Eso es todo lo que necesitamos para crear este tipo de actividades y mostrar una ventanacon las preferencias definidas en el layout Esta actividad puede ser iniciada comocualquier otra mediante un Intent ya sea a traveacutes de una llamada a startActivity obien a startActivityForResult

Intent i = new Intent(this MisPreferenciasclass)startActivityForResult(i MOSTRAR_PREFERENCIAS)

Los valores para las diferentes preferencias de la actividad son almacenadas en elcontexto de la aplicacioacuten Esto permite a cualquier componente de dicha aplicacioacutenincluyendo a cualquier actividad y servicio de la misma acceder a los valores tal como semuestra en el siguiente coacutedigo de ejemplo

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto) PENDIENTE hacer uso de meacutetodos get para obtener los valores

317 Shared Preference Change Listeners

Persistencia

23Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El uacuteltimo elemento que nos queda por examinar en esta plataforma de Shared Preferenceses la interfaz onSharedPreferenceChangeListener que es utilizada para invocar unevento cada vez que una preferencia es antildeadida eliminada o modificada Para registrarListeners usamos un coacutedigo como el que se muestra a continuacioacuten en el que la parte maacutesimportante es sin duda el meacutetodo onSharedPreferenceChanged dentro del cualdeberemos determinar queacute preferencia ha cambiado de estado a partir de sus paraacutemetrospara llevar a cabo las acciones oportunas

public class MiActividad extends Activity implementsOnSharedPreferenceChangeListener

Overridepublic void onCreate(Bundle SavedInstanceState)

Registramos este objetoOnSharedPreferenceChangeListener

Context contexto = getApplicationContext()SharedPreferences prefs =

PreferenceManagergetDefaultSharedPreferences(contexto)prefsregisterOnSharedPreferenceChangeListener(this)

public void onSharedPreferenceChanged(SharedPreferences prefsString clave)

PENDIENTE comprobar queacute clave concreta ha cambiado ysu nuevo valor

para modificar la interfaz de una actividad o elcomportamiento de

alguacuten componente

Otra forma maacutes sencilla de controlar los cambios en las preferencias es registrar unlistener en el constructor de la actividad principal o aquella que haga uso de los valores depreferencias de la forma

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto)

mListener = new OnSharedPreferenceChangeListener() public void onSharedPreferenceChanged(SharedPreferences

sharedPreferences String key)

if( keyequals(MICLAVE) )boolean valor =

sharedPreferencesgetBoolean(MICLAVE false)

prefsregisterOnSharedPreferenceChangeListener(mListener)

NotaEn este segundo ejemplo mListener es una variable miembro de la clase Para este caso esnecesario registrar el listener de esta forma porque sino el garbage collector lo eliminariacutea (debidoa particularidades en la programacioacuten de los listeners asociados a las SharedPreferences)

Persistencia

24Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

32 Proveedores de contenidos

Los proveedores de contenidos o ContentProvider proporcionan una interfaz parapublicar y consumir datos identificando la fuente de datos con una direccioacuten URI queempieza por content Son una forma maacutes estaacutendar que los adaptadores (como el quevimos en la sesioacuten anterior en la seccioacuten dedicada a SQLite) de desacoplar la capa deaplicacioacuten de la capa de datos

Podemos encontrar dos tipos principales de proveedores de contenidos en Android Losproveedores nativos son proveedores que el propio sistema nos proporciona para poderacceder a datos del dispostivo incluyendo audio viacutedeo imaacutegenes informacioacuten personaletc Por otra parte tambieacuten es posible que creemos nuestros propios proveedores con talde permitir a nuestra aplicacioacuten compartir datos con el resto del sistema En esta sesioacutenhablaremos de ambos tipos

321 Proveedores nativos

Android incluye una serie de proveedores de contenidos que nos pueden proporcionarinformacioacuten de diferentes tipos informacioacuten sobre nuestros contactos informacioacuten delcalendario e incluso ficheros multimedia Ejemplos de estos proveedores de contenidosnativos son el Browser CallLog ContactsContract MediaStore Settings y elUserDictionary Para poder hacer uso de estos proveedores de contenidos y acceder alos datos que nos proporcionan debemos antildeadir los permisos adecuados al ficheroAndroidManifestxml Por ejemplo para acceder al listiacuten telefoacutenico podemos antildeadir elpermiso correspondiente de la siguiente forma

ltuses-sdk androidminSdkVersion=8 gtltuses-permission androidname=androidpermissionREAD_CONTACTSgt

ltmanifestgt

Para obtener informacioacuten de un ContentProvider debemos hacer uso del meacutetodo query

de la clase ContentResolver El primero de los paraacutemetros de dicho meacutetodo seraacute la URIdel proveedor de contenidos al que deseamos acceder Este meacutetodo devuelve un cursorque nos permitiraacute iterar entre los resultados tal como se vio en la sesioacuten anterior en elapartado de cursores

ContentResolverquery(Uri uriString[] projectionString selectionString[] selectionArgsString sortOrder)

Por ejemplo la siguiente llamada nos proporcionaraacute un cursor que nos permitiraacute acceder atodos los contactos de nuestro teleacutefono moacutevil Para ello hemos hecho uso de la constante

Persistencia

25Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ContactsContractContactsCONTENT_URI que almacena la URI del proveedor decontenidos correspondiente Al resto de paraacutemetros se le ha asignado el valor null

ContentResolver cr = getContentResolver()Cursor cursor = crquery(ContactsContractContactsCONTENT_URI

null null null null)

NotaEn versiones anteriores a Android 20 las estructuras de datos de los contactos son diferentes y nose accede a esta URI

Una vez obtenido el cursor es posible mapear sus campos con alguacuten componente de lainterfaz graacutefica de la actividad De esta forma cualquier cambio que se produzca en elcursor se reflejaraacute automaacuteticamente en el componente graacutefico sin que sea necesario queprogramemos el coacutedigo que lo refresque Para esto tenemos que llamar al meacutetodosetNotificationUri del cursor con lo cual nos aseguraremos de que los listeners seraacutennotificados cuando se produzca alguacuten cambio en los datos referenciados por dicho cursor

cursorsetNotificationUri(crContactsContractContactsCONTENT_URI)

Para asignar un adaptador a una lista de la interfaz graacutefica de la actividad maacutesconcretamente una ListView utilizamos el meacutetodo setAdapter(Adapter)

ListView lv = (ListView)findViewById(RidListView01)SimpleCursorAdapter adapter = new SimpleCursorAdapter(

getApplicationContext()Rlayouttextviewlayoutcursornew String[]

ContactsContractContacts_IDContactsContractContactsDISPLAY_NAME

new int[]RidTextView1RidTextView2)

lvsetAdapter(adapter)

En este ejemplo los identificadores RidTextView1 y RidTextView2 se correspondencon views del layout que define cada fila de la ListView como se puede ver en elarchivo textviewlayoutxml que tendriacuteamos que crear

ltxml version=10 encoding=utf-8gt

ltLinearLayout xmlnsandroid=httpschemasandroidcomapkresandroidandroidorientation=horizontalandroidlayout_width=fill_parentandroidlayout_height=wrap_contentgt

ltTextView androidid=+idTextView1androidtextStyle=bold

Persistencia

26Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

androidems=2androidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltTextView androidid=+idTextView2androidtextStyle=boldandroidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltLinearLayoutgt

La lista en siacute (identificada por RidListView01 en el presente ejemplo) estariacutea en otroXML layout por ejemplo en mainxml

Se debe tener en cuenta que en el caso de este ejemplo si quisieacuteramos obtener losnuacutemeros de teleacutefono de cada uno de nuestros contactos tendriacuteamos que recorrer el cursorobtenido a partir del proveedor de contenidos y por cada persona en nuestra agenda crearun nuevo cursor que recorriera los teleacutefonos ya que una persona puede tener asignadosvarios teleacutefonos

Puedes acceder a un listado completo de los proveedores de contenidos nativos existentesen Android consultando su manual de desarrollo Concretamente leyendo la seccioacutendedicada al paquete androidprovider enhttpdeveloperandroidcomreferenceandroidproviderpackage-summaryhtml

322 Proveedores propios crear un nuevo proveedor de contenidos

Para acceder a nuestras fuentes de datos propias de manera estaacutendar nos interesaimplementar nuestros propios ContentProvider Gracias a que proporcionamos nuestrosdatos a partir de una URI quien use nuestro proveedor de contenidos no deberaacutepreocuparse de doacutende provienen los datos indicados por dicha URI podriacutean provenir deficheros locales en la tarjeta de memoria de un servidor en Internet o de una base dedatos SQLite Dada una URI nuestro proveedor de contenidos deberaacute proporcionar en suinterfaz los meacutetodos necesarios para realizar las operaciones baacutesicas en una base de datosinsercioacuten lectura actualizacioacuten y borrado

Para crear un nuevo proveedor de contenidos definimos una subclase deContentProvider Utilizaremos una sobrecarga del meacutetodo onCreate para crear einicializar la fuente de datos que queramos hacer puacuteblica a traveacutes de dicho proveedor Unesqueleto de subclase de ContentProvide podriacutea ser el siguiente

public class MiProveedor extends ContentProvider

Overridepublic boolean onCreate()

Inicializar la base de datosreturn true

Dentro de la clase deberemos crear un atributo puacuteblico y estaacutetico de nombreCONTENT_URI en el cual almacenaremos el URI completo del proveedor que estamos

Persistencia

27Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creando Este URI debe ser uacutenico por lo que una buena idea puede ser tomar como baseel nombre de paquete de nuestra aplicacioacuten La forma general de un URI es la siguiente

content[NOMBRE_PAQUETE]provider[NOMBRE_APLICACION][RUTA_DATOS]

Por ejemplo

contentesuajtechandroidprovidermiaplicacionelementos

En general (y por simplicidad) no es necesario antildeadir la cadena provider al nombre delpaquete por lo que podemos utilizar directamente el nombre del paquete seguido de laruta de datos de la forma

contentesuajtechandroidmiaplicacionelementos

Estos URIs se pueden presentar en dos formas La URI anterior representa una consultadirigida a obtener todos los valores de ese tipo (en este caso todos los elementos) Si a laURI anterior antildeadimos un sufijo [NUMERO_FILA] estamos representando una consultadestinada a obtener un uacutenico elemento (por ejemplo el quinto elemento en el siguienteejemplo)

contentesuajtechandroidprovidermiaplicacionelementos5

NotaSe considera una buena praacutectica de programacioacuten el proporcionar acceso a nuestro proveedor decontenidos utilizando cualquiera de estas dos formas

La forma maacutes simple de determinar cuaacutel de las dos formas anteriores de URI se utilizoacutepara hacer una peticioacuten a nuestro proveedor de contenidos es mediante un Uri MatcherCrearemos y configuramos un elemento Uri Matcher para analizar una URI y determinara cuaacutel de las dos formas se corresponde En el siguiente coacutedigo se muestra el coacutedigobaacutesico que deberemos emplear para implementar este patroacuten

public class MiProveedor extends ContentProvider

private static final String miURI =contentesuajtechandroidprovidermiaplicacionelementos

public static final Uri CONTENT_URI = Uriparse(miUri)

Creamos las constantes que nos van a permitir diferenciar entre los dos tipos distintos de peticiones a partir de la URIprivate static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

Inicializamos el objeto UriMatcher Una URI que termine en elementos se corresponderaacute con una consulta para todas las filas mientras que una URI con el sufijo elementos[FILA] representaraacute una uacutenica filastatic

uriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidprovidermiaplicacion

elementos TODAS_FILAS)

Persistencia

28Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

uriMatcheraddURI(esuajtechandroidprovidermiaplicacionelementos UNA_FILA)

Overridepublic boolean onCreate()

PENDIENTE inicializar la base de datosreturn true

Esta misma teacutecnica se puede emplear para proporcionar diferentes alternativas de URIpara diferentes subconjuntos de datos como por ejemplo diferentes tablas en una base dedatos a traveacutes del mismo proveedor de contenidos

323 Proveedores propios crear la interfaz de consultas

Para permitir realizar operaciones con los datos publicados a traveacutes de nuestro proveedorde contenidos deberemos implementar los meacutetodos delete insert update y query

para el borrado insercioacuten actualizacioacuten y realizacioacuten de consultas respectivamenteEstos meacutetodos son la interfaz estaacutendar utilizada con proveedores de contenidos de formaque para compartir datos entre aplicaciones no se tendraacuten interfaces distintos paradiferentes fuentes de datos

Lo maacutes habitual suele ser implementar un proveedor de contenidos como un mecanismopara permitir el acceso a una base de datos SQLite privada a una aplicacioacuten aunque atraveacutes de estos meacutetodos indicados se podriacutea en principio acceder a cualquier fuente dedatos

El siguiente coacutedigo extiende el ejemplo anterior (MiProveedor) y muestra el esqueleto deestos meacutetodos dentro de una subclase de ContentProvider Obseacutervese que se hace usodel objeto UriMatcher para determinar que tipo de consulta se estaacute realizando

Overridepublic Cursor query(Uri uri

String[] projectionString selectionString[] selectionArgsString sort)

Si se trata de una consulta para obtener una uacutenica filalimitamos

los resultados a obtener de la fuente de datos por medio de una claacuteusula whereswitch(UriMatchermatch(uri))

case UNA_FILA PENDIENTE modifica la sentencia SELECT

mediante una claacuteusula where obteniendo el identificador de fila

como numeroFila = urigetPathSegments()get(1))

return null

Override

Persistencia

29Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public Uri insert(Uri _uri ContentValues _valores) long idFila = [ Antildeadir un nuevo elemento ]

Devolvemos la URI del elemento recieacuten antildeadidoif (idFila gt 0)

return ContentUriswithAppendendId(CONTENT_URI idFila)

throw new SQLException(No se pudo antildeadir un nuevo elemento a +_uri)

Overridepublic int delete(Uri uri String where String[] whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

Overridepublic int update(Uri uri ContentValues valores String where String[]whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

324 Proveedores propios tipo MIME

El uacuteltimo paso para crear un proveedor de contenidos es definir el tipo MIME queidentifica el tipo de datos que devuelve el proveedor Debemos sobrecargar el meacutetodogetType para que devuelva una cadena que identifique nuestro tipo de datos de manerauacutenica Se deberiacutean definir dos tipos posibles uno para el caso de una uacutenica fila y otro parael caso de devolver todos los resultados Se debe utilizar una sintaxis similar a la delsiguiente ejemplo

bull Un uacutenico elementovndesuajtechandroidmiaplicacioncursoritemmiproveedorcontent

bull Todos los elementosvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

Como se puede observar la cadena comienza siempre por vnd A continuacioacutentendriacuteamos el nombre del paquete de nuestra aplicacioacuten Lo siguiente es cursor ya quenuestro proveedor de contenidos estaacute devolviendo datos de tipo Cursor Justo antes de labarra pondremos item en el caso del tipo MIME para un uacutenico elemento y dir para elcaso del tipo MIME para todos los elementos Finalmente tras la barra pondremos elnombre de nuestra clase (en minuacutesculas) seguido de content En el siguiente coacutedigo deejemplo se muestra coacutemo sobrecargar getType seguacuten la URI

Persistencia

30Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Overridepublic String getType(Uri _uri)

switch (uriMatchermatch(_uri)) case TODAS_FILAS

returnvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

case UNA_FILAreturn

vndesuajtechandroidmiaplicacioncursoritemmiproveedorcontentdefault

throw new IllegalArgumentException(URI nosoportada + _uri)

325 Proveedores propios registrar el proveedor

No debemos olvidar incorporar nuestro proveedor de contenidos al Manifest de laaplicacioacuten una vez que lo hemos completado Esto se haraacute por medio del elementoprovider cuyo atributo authorities podremos utilizar para especificar la URI base talcomo se puede ver en el siguiente ejemplo

ltprovider androidname=MiProveedorandroidauthorities=esuajtechandroidmiaplicaciongt

326 Content Resolvers

Aunque en la seccioacuten de proveedores nativos ya se ha dado alguacuten ejemplo de coacutemo haceruso de un Content Resolver para realizar una consulta ahora que hemos podido observaren detalle la estructura de un proveedor de contenidos propio es un buen momento paraver en detalle coacutemo realizar cada una de las operaciones que eacutestos permiten

El contexto de una aplicacioacuten incluye siempre una instancia de la claseContentResolver a la cual se puede acceder mediante el meacutetodo getContentResolver

ContentResolver cr = getContentResolver()

Esta clase incorpora diversos meacutetodos para realizar consultas a proveedores decontenidos Cada uno de estos meacutetodos acepta como paraacutemetro una URI que indica conqueacute proveedor de contenidos se desea interactuar

Las consultas realizadas sobre un proveedor de contenidos recuerdan a las consultasrealizadas sobre bases de datos Los resultados de las consultas se devuelven comoobjetos de la clase Cursor Se pueden extraer valores del cursor de la misma forma en laque se explicoacute en el apartado de bases de datos de la sesioacuten anterior El meacutetodo query dela clase ContentResolver acepta los siguientes paraacutemetros

bull La URI del proveedor del contenidos al que se desea realizar la consultabull Una proyeccioacuten que enumera las columnas que se quieren incluir en los resultados

obtenidosbull Una claacuteusula where que define las filas a obtener Se pueden utilizar comodines que

Persistencia

31Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

se reemplazaraacuten por valores incluidos en el siguiente paraacutemetrobull Una matriz de cadenas que se pueden utilizar para reemplazar los comodines en la

claacuteusula where anteriorbull Una cadena que describa la ordenacioacuten de los elementos devueltos

El siguiente coacutedigo muestra un ejemplo

ContentResolver cr = getContentResolver() Devolver todas las filasCursor todasFilas = crquery(MiProveedorCONTENT_URI null null nullnull) Devolver todas columnas de las filas para las que la columna 3 tiene un valor determinado ordenadas seguacuten el valor de la columna 5String where = COL3 + = + valorString orden = COL5Cursor algunasFilas = crquery(MiProveedorCONTENT_URI null where nullorden)

327 Otras operaciones con Content Resolvers

Para realizar otro tipo de operaciones con los datos de un proveedor de contenidosharemos uso de los meacutetodos delete update e insert de un objeto ContentResolver

Con respecto a la insercioacuten la clase ContentResolver proporciona en su interfaz dosmeacutetodos diferentes insert y bulkInsert Ambos aceptan como paraacutemetro la URI delproveedor de contenidos pero mientras que el primer meacutetodo tan solo toma comoparaacutemetro un objeto de la clase ContentValues (ya vistos en la seccioacuten de bases dedatos) el segundo toma como paraacutemetro una matriz de elementos de este tipo El meacutetodoinsert devolveraacute la URI del elemento recieacuten antildeadido mientras que bulkInsert

devolveraacute el nuacutemero de filas correctamente insertadas

Obtener el Content ResolverContentResolver cr = getContentResolver()

Crear una nueva fila de valores a insertarContentValues nuevosValores = new ContentValues()

Asignar valores a cada columnanuevosValoresput(NOMBRE_COLUMNA nuevoValor)[ Repetir para cada columna ]

Uri miFilaUri = crinsert(MiProveedorCONTENT_URI nuevosValores)

Insertamos ahora una matriz de nuevas filasContentValues[] arrayValores = new ContentValues[5] PENDIENTE rellenar el array con los valores correspondientesint count = crbulkInsert(MiProveedorCONTENT_URI arrayValores)

Para el caso del borrado hacemos uso del meacutetodo delete del Content Resolver pasandocomo paraacutemetro la URI de la fila que deseamos eliminar de la fuente de datos Otraposibilidad es incluir una claacuteusula where para eliminar muacuteltiples filas Ambas teacutecnicas semuestran en el siguiente ejemplo

ContentResolver cr = getContentResolver()

Persistencia

32Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Eliminar una fila especiacuteficacrdelete(miFilaUri null null) Eliminar las primeras cinco filasString where = _id lt 5crdelete(MiProveedorCONTENT_URI where null)

Una actualizacioacuten se lleva a cabo mediante el meacutetodo update del objeto ContentResolver Este meacutetodo toma como paraacutemetro la URI del proveedor de contenidosobjetivo un objeto de la clase ContentValues que empareja nombres de columna convalores actualizados y una claacuteusula where que indica queacute filas deben ser actualizadas Elresultado de la actualizacioacuten seraacute que en cada fila en la que se cumpla la condicioacuten de laclaacuteusula where se cambiaraacuten los valores de las columnas especificados por el objeto de laclase ContentValues tambieacuten se devolveraacute el nuacutemero de filas correctamenteactualizadas A continuacioacuten se muestra un ejemplo

ContentValues nuevosValores = new ContentValues()

Utilizamos el objeto ContentValues para especificar en queacute columnas queremos que se produzca un cambio y cuaacutel debe ser el nuevo valor de dichas columnasnuevosValuesput(NOMBRE_COLUMNA nuevoValor)

Aplicamos los cambios a las primeras cinco filasString where = _id lt 5

getContentResolver()update(MiProveedorCONTENT_URI nuevosValores wherenull)

Persistencia

33Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

4 Ejercicios - Persistencia en Android proveedores de contenidos ySharedPreferences

41 Compartir datos entre actividades con Shared Preferences (075 puntos)

Descarga de las plantillas el proyecto Dni Dicho proyecto estaacute compuesto de dosactividades Formulario y Resumen El objetivo del ejercicio es conseguir que al pulsar elbotoacuten Siguiente en la actividad Formulario se muestre en la actividad Resumen cuaacutelesfueron los datos introducidos

En este ejercicio vamos a hacer uso de SharedPreferences para pasar los datos de unaactividad a la siguiente Al pulsar en Siguiente en Formulario deberemos guardar el valorde todos los campos mediante esta plataforma Al mostrarse la actividad Resumendeberemos leer estos datos tambieacuten a partir de SharedPreferences para mostrarlos porpantalla

Por uacuteltimo crea un meacutetodo que valide el DNI (debe tratarse de una secuencia de ochodiacutegitos entre 0 y 9 y una letra al final) Si se pulsa Siguiente y el DNI no tiene el formatocorrecto no se deberaacute pasar a la actividad Resumen En lugar de esto se mostraraacute unToast en pantalla con un mensaje de error

42 Actividad de preferencias (075 puntos)

En este ejercicio seguimos trabajando con el proyecto del ejercicio anterior Vamos aantildeadir un menuacute de opciones que permita escoger en queacute momento se comprobaraacute lavalidez del DNI Para ello antildeade en primer lugar un menuacute a la actividad Formulario conuna uacutenica opcioacuten cuyo nombre seraacute Opciones

Al seleccionar esta opcioacuten se deberaacute mostrar una actividad de preferencias cuyo aspectoseraacute el siguiente

Persistencia

34Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Preferencias de la aplicacioacuten Dni

El significado de las opciones seraacute el siguiente

bull En el campo con esta opcioacuten seleccionada al pulsar sobre el campo de DNI en elformulario deberaacute mostrarse un Toast indicando si la sintaxis del DNI es correcta ono

bull Al pulsar si se desactiva esta opcioacuten (que deberaacute estar activada por defecto) no seharaacute la comprobacioacuten del DNI al pulsar el botoacuten Siguiente por lo que siempre seraacuteposible pasar de la actividad Formulario a la actividad Resumen sea cual sea elformato del DNI introducido

bull El uacuteltimo check box se encontraraacute siempre deshabilitado iquestQueacute atributo debemosutilizar para conseguir esto

Crea dos variables booleanas llamadas enElCampo y alPulsar Su valor dependeraacute delestado de los checkboxes anteriores y se actualizaraacute por medio del eventoonSharedPreferenceChange Utiliza el valor de estas variables en el coacutedigo de tuactividad para que esta tenga el comportamiento indicado

43 Proveedor de contenidos propio (075 puntos)

Vamos a implementar otra forma de acceder a la base de datos de usuarios de laaplicacioacuten BaseDatos desarrollada durante los ejercicios de la sesioacuten anterior siguiendoesta vez el patroacuten de disentildeo ContentProvider de Android Seguiremos trabajando puescon dicho proyecto

bull Creamos una nueva clase llamada UsuariosProvider que herede deContentProvider Esto nos obligaraacute a sobrecargar una serie de meacutetodos abstractosAntes de implementar la query vamos a configurar el provider

bull Antildeadimos algunos campos tiacutepicos de los content provider

Persistencia

35Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public static final Uri CONTENT_URI =Uriparse(contentesuajtechandroidbasedatosusuarios)

private static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

staticuriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

TODAS_FILAS)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

UNA_FILA)

bull Vamos a acceder a la misma base de datos de usuarios utilizada en los ejercicios de lasesioacuten anterior pero no vamos a hacerlo a traveacutes del adaptador que tuvimos queimplementar sino que vamos a copiar de eacutel el coacutedigo que nos haga falta Copia loscampos que definen el nombre de la base de datos de la tabla de las columnas laversioacuten asiacute como la referencia al contexto y a la base de datos La sentenciacompilada del insert ya no va a hacer falta Inicializa los valores necesarios en elconstructor Para inicializar la referencia a la base de datos vamos a utilizar una vezmaacutes MiOpenHelper Podemos copiarlo del adaptador que ya implementamos en losejercicios de la sesioacuten anterior

bull Implementa de forma apropiada el getType para devolver un tipo MIME diferenteseguacuten si se trata de una URI de una fila o de todas las filas Para ello ayuacutedate deluriMatcher

bull Implementa el meacutetodo query Simplemente se trata de devolver el cursor queobtenemos al hacer una consulta a la base de datos SQLite Algunos de los paraacutemetrosque le pasaremos los recibimos como paraacutemetros del meacutetodo del provider Los que notengamos iraacuten con valor null

bull Aunque no tenemos todos los meacutetodos del UsuariosProvider implementadospodemos probarlo Para ello debemos registarlo en el AndroidManifestxml

ltprovider androidname=UsuariosProvider

androidauthorities=esuajtechandroidbasedatosgtltapplicationgt

ltmanifestgt

bull En la clase Main se antildeadioacute codigo para insertar una serie de valores en la base dedatos y mostrarlos en el campo de texto Manteniendo este coacutedigo vamos a antildeadir alcampo de texto el resultado obtenido con la consulta del UsuariosProvider paracomprobar que tanto el adaptador de la base de datos como el proveedor nosdevuelven el mismo resultado

44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)

Porque es la forma estaacutendar que establece Android de acceder a contenidos Ademaacutes elproveedor de contenidos nos permitiraacute notificar al ContentResolver de los cambiosocurridos Asiacute componentes en la pantalla podraacuten refrescarse de forma automaacutetica

Persistencia

36Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Utiliza el proyecto ProveedorContenidos de las plantillas Implementa la insercioacuten enel proveedor de contenidos Prueacutebala insertando algunos usuarios de ejemplo en laclase Main Implementa tambieacuten el OnClickListener del botoacuten que inserta nuevosusuarios El nombre del nuevo usuario iraacute indicado en el EditText

bull Comprueba que la insercioacuten funciona y que gracias a la siguiente liacutenea y al estarusando un proveedor de contenidos la lista se actualiza automaacuteticamente cuandoocurre alguacuten cambio sin necesidad de pedir expliacutecitamente la actualizacioacuten al pulsarel botoacuten

cursorsetNotificationUri(cr UsuariosProviderCONTENT_URI)

NotaEsto no va a funcionar si se notifica que se ha producido un cambio al insertar o borrar unelemento Para ello usamosgetContext()getContentResolver()notifyChange(UsuariosProviderCONTENT_URI null)

bull Implementa el meacutetodo delete del proveedor de contenidos Prueacutebalo en la claseMain Termina de implementar el onCreateContextMenuListener que se ejecutaraacutecada vez que se haga una pulsacioacuten larga sobre alguna entrada de la lista Compruebaque funciona (eliminando el usuario correspondiente y no otro)

Persistencia

37Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

5 Persistencia de datos en iOS Ficheros y SQLite

51 Introduccioacuten

La persistencia de datos en iOS se realiza en una pequentildea memoria flash que tienenuestro dispositivo la cual es equivalente a un disco duro de tamantildeo limitado Los datosque ahiacute se almacenen se conservaraacuten aunque el dispositivo se apague Por motivos deseguridad el SO de iOS no permite que las aplicaciones accedan directamente a lamemoria interna pero a cambio existe una API completa de acceso a la porcioacuten dememoria que le corresponde a la app que desarrollemos

En una aplicacioacuten iOS encontraremos baacutesicamente 4 modos de almacenamiento deinformacioacuten de forma persistente

bull Ficheros de propiedades (plist)bull Bases de datos embebidas SQLitebull Datos de usuario (User Defaults)bull Core Data

En esta primera sesioacuten veremos coacutemo usar los ficheros de texto (Property Lists) y elalmacenamiento en base de datos de tipo SQLite

52 Ficheros plist (Property Lists)

El almacenamiento de datos en ficheros de propiedades es uno de los maacutes usados en eldesarrollo de las aplicaciones para iOS Se usa principalmente para guardar datos deconfiguracioacuten de la aplicacioacuten u otra informacioacuten poco compleja y acceder a ella deforma sencilla Si estamos desarrollando un juego podemos usarlos para la definicioacuten delos niveles almacenar las puntuaciones del jugador etc

521 Leyendo datos desde ficheros plist

Los ficheros plist (Property Lists) tienen un formato XML aunque XCode nosproporciona una interfaz que nos permite acceder a ellos de forma muy sencilla Paraentender el funcionamiento de este tipo de ficheros vamos a realizar un ejemplo en el queprimero crearemos un fichero plist lo completaremos con datos de ejemplo y despueacutes loleeremos para mostrarlo por pantalla

Un ejemplo de un fichero plist podriacutea ser el siguiente

Persistencia

38Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Fichero plist

Antes de nada creamos un proyecto nuevo en XCode de tipo Window-based

Application al que llamaremos sesion03-ejemplo1 Para crear un fichero plistdebemos hacer click derecho sobre el directorio Supporting Files de nuestro proyectoy seleccionamos iOS gt Resource gt Property List Lo guardaremos con el nombreconfigUsuario Seguidamente ya podemos acceder al fichero recieacuten creado haciendoclick sobre el en este momento XCode nos mostraraacute un pequentildeo editor totalmente vacioen el que iremos rellenando con datos Podemos verlo tambieacuten en formato XML sihacemos click derecho sobre el y seleccionamos Open As gt Preview

Para empezar a rellenar el fichero que acabamos de crear hacemos click derecho dentrode su contenido (ahora vacio) y seleccionamos Add row Entonces nos apareceraacute unafila vaciacutea Dentro del campo Key escribimos la clave un nombre descriptivo quedeberemos utilizar maacutes adelante para acceder a los datos En nuestro caso vamos aescribir config como clave Dentro de la columna Type seleccionamosDictionary Veremos que aparece una flecha en el lado izquierdo que indica quepueden haber subniveles dentro de nuestro diccionario

Ahora antildeadimos un nuevo item que cuelgue del diccionario que acabamos de crear paraello hacemos click sobre la flecha de la izquierda esta se giraraacute hacia abajo entonceshacemos click ahora sobre el siacutembolo + (maacutes) Nos apareceraacute una nueva liacutenea que cuelgadel diccionario

En esta nueva liacutenea dentro del campo key escribimos nombre el campo type lodejamos como de tipo String ya que va a ser una cadena de texto y en la columna deValue escribimos nuestro nombre por ejemplo Javi

Ahora vamos a antildeadir otro campo nuevo en nuestra configuracioacuten para ello hacemosclick sobre el siacutembolo + y nos apareceraacute una nueva liacutenea justo al mismo nivel que laanterior y colgando del diccionario Dentro del campo de Key escribimos ciudad yen Value escribimos Alicante El campo de Type lo dejamos como String

Persistencia

39Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora dentro de nuestro fichero plist vamos a indicar los dispositivos que dispone elusuario para ello creamos una nueva linea dentro del diccionario y le pondremos comoclave dispositivos Seleccionamos el tipo Array y seguimos los mismos pasosque al crear el diccionario para ello hacemos click sobre la flecha de la izquierda ydespueacutes sobre el siacutembolo + (maacutes) Ahora nos apareceraacute una nueva fila con clave Item 0Seleccionamos el tipo String y en el campo Value iPhone

Ahora repetimos el paso 3 veces indicando como values iPad Android y

Blackberry

Una vez seguidos todos estos pasos tendremos nuestro fichero de propiedades listo Debede quedar como se muestra a continuacioacuten

Fichero plist terminado

Este fichero se almacenaraacute dentro del directorio de bundle cuando compilemos laaplicacioacuten En el caso de que queramos escribir en el debermos almacenar el ficherodentro del directorio de Documents del binario

Ahora desde nuestro coacutedigo vamos a acceder a eacutel y a mostrarlo por pantalla para elloescribimos el siguiente coacutedigo dentro del meacutetodo didFinishLaunchingWithOptions dela clase delegada

Cargamos el fichero PLISTNSString mainBundlePath = [[NSBundle mainBundle] bundlePath]NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]NSDictionary diccionario = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionario delraiz

NSString nombre = [dicConfig objectForKeynombre]

Persistencia

40Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

[selfwindow makeKeyAndVisible]return YES

Si todo ha ido correctamente deberemos obtener la siguiente salida por la consola

Lista de dispositivos

522 Escribiendo datos en ficheros plist

Partiendo del ejemplo anterior vamos a escribir ahora en el fichero para ello primerodebemos comprobar que el fichero plist se encuentre dentro del directorio de Documents

del dispositivo en el caso de que no lo esteacute (por ejemplo si es la primera vez que arrancala aplicacioacuten) debemos copiarlo a ese directorio Una vez que tenemos el fichero dentrodel directorio de documentos ya podemos escribir en el

Para realizar todo el proceso de comprobacioacuten de la existencia del fichero dentro deldirectorio de documentos copiarlo en ese directorio y leer los datos debemos de sustituirel coacutedigo que hay dentro del meacutetodo didFinishLaunchingWithOptions de la clasedelegada por este otro

AtencioacutenPara que funcione la escritura en un fichero plist este debe de estar dentro del directorio deDocuments de nuestro dispositivo iOS no en el boundle

NSDictionary diccionarioRaiz

Ruta del directorio de documentos de nuestro dispositivoNSArray paths =

NSSearchPathForDirectoriesInDomains(NSDocumentDirectoryNSUserDomainMask YES)

if ([paths count] gt 0)

Persistencia

41Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

Primero comprobamos si existe el fichero en eldirectorio de documentos

BOOL fileExists = [[NSFileManager defaultManager]fileExistsAtPathdocumentsFilename]if (fileExists)

NSLog(Fichero encontrado en directorio de documentosOK

documentsFilename)

Si existe ya cargamos los datos desde aquidirectamente

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

else

NSLog(No se encuentra el fichero configUsuarioplisten

el directorio de documentos-gtLo creamos)

Si no existe primero cargamos el fichero PLISTdesde el Boundle

NSString mainBundlePath = [[NSBundle mainBundle]bundlePath]

NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Y despues escribimos los datos cargados (eldiccionario completo)

a un fichero nuevo de la carpeta de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

NSLog(plist de configUsuario creado)

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionariodel raiz

NSString nombre = [dicConfig objectForKeynombre]NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

Persistencia

42Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Por ultimo liberamos la memoria del diccionario raiz[diccionarioRaiz release]

[selfwindow makeKeyAndVisible]return YES

Para comprobar el funcionamiento arrancamos la aplicacioacuten y veremos que la primera vezse crearaacute el fichero dentro de la carpeta de Documents de nuestro dispositivo y en lassiguientes ya no se crearaacute y simplemente lo leeraacute desde la carpeta de Documents

Ahora que ya tenemos la gestioacuten de ficheros baacutesica completada vamos a escribir sobre elalguacuten dato Para ello simplemente escribimos el siguiente fragmento de coacutedigo justo antesde la liacutenea [selfwindow makeKeyAndVisible]

Cambiamos el nombre y lo guardamos en el fichero Ruta del directorio de documentos de nuestro dispositivo

if ([paths count] gt 0)

Cargamos el fichero desde el directorio dedocumentos del dispositivo

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cambiamos el valor del nombre en el diccionario[dicConfig setValueOtro nombre forKeynombre]

Escribimos todo el diccionario de nuevo al fichero del directorio de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

Por ultimo liberamos el diccionario de lamemoria

[diccionarioRaiz release]

Lo que hemos hecho en el coacutedigo anterior es cargar todo el diccionario de nuevo desde eldirectorio de documentos modificar los datos que queremos cambiar o incluso antildeadir ydespueacutes guardar el diccionario en el fichero

Volvemos a arrancar la aplicacioacuten y veremos que ha cambiado el campo de nombre

Nota

Persistencia

43Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

La lectura y escritura de datos en un fichero plist se debe de hacer de una soacutela vez nunca porpartes Siempre se debe de cargar o escribir un diccionario NSDictionary como elementoprincipal del fichero

53 SQLite

SQLite es una base de datos relacional ligera con la que se puede trabajar de formasencilla mediante sentencias SQL La base de datos se almacenaraacute en un fichero dentrode nuestra aplicacioacuten Este meacutetodo de persistencia puede ser el adecuado cuando los datosa almacenar esteacuten en un formato de tablas filas y columnas y se necesite que los datossean accesibles de forma raacutepida

531 Herramientas disponibles

Para explicar el funcionamiento de las librerias de SQLite de Cocoa Touch vamos a seguirun ejemplo completo Comenzamos creando la base de datos para ello podemos utilizaruno de los frontends que facilitan la gestioacuten del SQLite uno bastante sencillo yrecomendado es el SQLite Database Browser y otro es fmdb

Nosotros utilizaremos el primero (SQLite Database Browser) Abrimos la aplicacioacuten yhacemos click sobre el botoacuten de crear nueva base de datos que llamaremospersonasDBsqlite Seguidamente creamos las tablas para simplificar en nuestroejemplo crearemos una soacutela tabla llamada personas Esta tabla tendraacute las siguientescolumnas id (NUMERIC) nombre (TEXT) apellidos (TEXT) dni (TEXT) edad

(NUMERIC) y localidad (TEXT)

Persistencia

44Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Estructura de la Base de Datos

Una vez creada la tabla pasamos a insertar datos dentro de ella para ello abrimos lapestantildea de Browse Data y hacemos click en New Record Cuando tengamos al menos 3registros metidos dentro de la tabla ya podemos guardar la base de datos Hacemos clicksobre guardar le ponemos el nombre que queramos y elegimos cualquier directorio delMAC

Datos de la Base de Datos

532 Lectura de datos

Una vez que tenemos la base de datos SQLite creada ya podemos empezar a escribir elcoacutedigo para leer y mostrar los datos dentro de nuestra aplicacioacuten

Primero empezamos creando un proyecto nuevo en XCode usando la plantilla basada envistas View-based application y lo llamaremos sesion03-ejemplo2 Ahora antildeadimosel framework de sqlite a nuestro proyecto para ello hacemos click sobre el raiz delproyecto y seleccionamos la pestantildea Build Phases Desplegamos el listado de Link

Binary With Libraries y hacemos click sobre el botoacuten (+) El el listadoseleccionamos el framework libsqlite3dylib y hacemos click en Add

Persistencia

45Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Antildeadiendo el framework libsqlite3dylib

Ahora antildeadimos a nuestro proyecto la base de datos que hemos creado anteriormentepara ello arrastramos el fichero personasDBsqlite a nuestro directorio Supporting

Files Asegurate de seleccionar la opcioacuten de Copy items to destination groups

folder (if needed) para que se copie el fichero dentro de la carpeta del proyecto

Para acelerar el proceso de lectura de la base de datos y ahorrar memoria vamos a cargarprimero todos los datos en objetos para ello vamos a crear una clase que se encargue dealmacenar los datos Hacemos click derecho sobre el raiz del proyecto New file gtObjective-C class seleccionamos NSObject como clase principal y le damos a NextEscribimos como nombre de la clase Persona y hacemos click en save

Personah

imports iniciales

interface Persona NSObject int _uIdNSString _nombreNSString _apellidos

Persistencia

46Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSInteger _edadNSString _localidad

property (nonatomic assign) int uIdproperty (nonatomic copy) NSString nombreproperty (nonatomic copy) NSString apellidosproperty (nonatomic) NSInteger edadproperty (nonatomic copy) NSString localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidos edad(NSInteger)edadlocalidad(NSString )localidad

end

Personam

import Personah

implementation Persona

synthesize uId = _uIdsynthesize nombre = _nombresynthesize apellidos = _apellidossynthesize edad = _edadsynthesize localidad = _localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidosedad(NSInteger)edad localidad(NSString )localidad

if ((self = [super init])) selfuId = uIdselfnombre = nombreselfapellidos = apellidosselfedad = edadselflocalidad = localidad

return self

- (void) dealloc selfnombre = nilselfapellidos = nilselflocalidad = nil[super dealloc]

end

Una vez creada la clase Persona vamos a crear la clase encargada de manejar la basede datos sqlite3 Otra opcioacuten seriacutea cargar todos los datos directamente desde la clasedelegada al arrancar la aplicacioacuten pero no es recomendable ya que si en alguacuten momentotenemos que cambiar de motor de base de datos lo tendremos maacutes complicado

Persistencia

47Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creamos entonces una clase dentro de nuestro proyecto llamada BDPersistencia queherede de NSObject y escribimos el siguiente coacutedigo

BDPersistenciah

imports iniciales

interface BDPersistencia NSObject sqlite3 _database

+ (BDPersistencia)database- (NSArray )arrayPersonas

end

BDPersistenciam

import BDPersistenciahimport Personah

implementation BDPersistencia

static BDPersistencia _database

+ (BDPersistencia)database if (_database == nil)

_database = [[BDPersistencia alloc] init]return _database

- (id)init if ((self = [super init]))

NSString sqLiteDb = [[NSBundle mainBundle]pathForResourcepersonasDB

ofTypesqlite]

if (sqlite3_open([sqLiteDb UTF8String]amp_database) = SQLITE_OK)

NSLog(Fallo al abrir la BD)

return self

- (void)dealloc sqlite3_close(_database)[super dealloc]

- (NSArray )arrayPersonas

NSMutableArray arraySalida = [[[NSMutableArray alloc]init]

autorelease]

Persistencia

48Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString query = SELECT id nombre apellidoslocalidad edad

FROM personas ORDER BY apellidos DESC

sqlite3_stmt statementif (sqlite3_prepare_v2(_database [query UTF8String]

-1ampstatement nil)

== SQLITE_OK) while (sqlite3_step(statement) == SQLITE_ROW)

int uniqueId = sqlite3_column_int(statement0)

char nombreChar = (char )sqlite3_column_text(statement 1)

char apellidosChar = (char )sqlite3_column_text(statement 2)

char localidadChar = (char )sqlite3_column_text(statement 3)

int edad = sqlite3_column_int(statement 4)

NSString nombre = [[NSString alloc]initWithUTF8StringnombreChar]NSString apellidos = [[NSString alloc]initWithUTF8StringapellidosChar]NSString localidad = [[NSString alloc]initWithUTF8StringlocalidadChar]Persona persona = [[Persona alloc]initWithUiduniqueId nombrenombreapellidosapellidos edadedad

localidadlocalidad]

[arraySalida addObjectpersona][nombre release][apellidos release][localidad release][persona release]

sqlite3_finalize(statement)

return arraySalida

end

Con esto ya podemos leer de nuestra base de datos las personas Para ver que funcionatodo a la perfeccioacuten escribimos el siguiente coacutedigo dentro de la clase delegada en elmeacutetodo applicationDidFinishLaunching Antes debemos incluir en la parte superiorlas clases creadas

import BDPersistenciahimport Personah

NSArray personas = [BDPersistenciadatabase]arrayPersonas

for (Persona persona in personas) NSLog(d d personauId

personanombrepersonaapellidos personalocalidad personaedad)

Persistencia

49Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Si ejecutamos el proyecto obtendremos la siguiente salida por consola

Listado de personas

533 Mostrando los datos en una tabla

Una vez que tenemos el coacutedigo para obtener todas las filas de la tabla de personas de labase de datos queda lo maacutes faacutecil mostrarlas dentro de una vista de la aplicacioacuten En estecaso utilizaremos la vista de tabla UITableView

Comenzamos creando una nueva vista en nuestro proyecto para ello hacemos clickderecho sobre el raiz y seleccionamos New File gt UIVIewController SubclassAsegurate de marcar Subclass of UITableViewController y la opcoacuten de With

XIB for user interface Hacemos click en Next y guardamos el fichero comoPersonasViewController por ejemplo

Ahora abrimos el fichero PersonasViewControllerh y antildeadimos un NSArray queseraacute el que almacene las personas de la base de datos

imports iniciales

interface PersonasViewController UITableViewController

NSArray _listadoPersonas

property (nonatomic retain) NSArray listadoPersonas

end

Ahora abrimos el fichero PersonasViewControllerm antildeadimos el syntetizeimportamos los ficheros de gestioacuten de la base de datos la clase Persona y todo el coacutedigobaacutesico para la gestioacuten de la vista de la tabla

import PersonasViewControllerhimport Personahimport BDPersistenciah

implementation PersonasViewController

synthesize listadoPersonas = _listadoPersonas

Persistencia

50Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

- (id)initWithStyle(UITableViewStyle)style

self = [super initWithStylestyle]if (self)

Custom initializationreturn self

- (void)dealloc

[super dealloc]

selflistadoPersonas = nil

- (void)didReceiveMemoryWarning

Releases the view if it doesnt have a superview[super didReceiveMemoryWarning]

Release any cached data images etc that arent inuse

pragma mark - View lifecycle

- (void)viewDidLoad

[super viewDidLoad]

selftitle = Listado de personas

selflistadoPersonas = [BDPersistenciadatabase]arrayPersonas

- (void)viewDidUnload

[super viewDidUnload] Release any retained subviews of the main view eg selfmyOutlet = nil

- (void)viewWillAppear(BOOL)animated

[super viewWillAppearanimated]

- (void)viewDidAppear(BOOL)animated

[super viewDidAppearanimated]

- (void)viewWillDisappear(BOOL)animated

[super viewWillDisappearanimated]

- (void)viewDidDisappear(BOOL)animated

[super viewDidDisappearanimated]

- (BOOL)shouldAutorotateToInterfaceOrientation

Persistencia

51Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

(UIInterfaceOrientation)interfaceOrientation

Return YES for supported orientationsreturn (interfaceOrientation ==

UIInterfaceOrientationPortrait)

pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView(UITableView)tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection

(NSInteger)section

Return the number of rows in the sectionreturn [selflistadoPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath

(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]if (cell == nil)

cell = [[[UITableViewCell alloc]initWithStyleUITableViewCellStyleSubtitle

reuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona persona = (Persona )[selflistadoPersonas

objectAtIndexindexPathrow]celltextLabeltext = [NSString stringWithFormat

(d) personanombrepersonaapellidos personaedad]celldetailTextLabeltext = personalocalidad

return cell

pragma mark - Table view delegate

- (void)tableView(UITableView )tableViewdidSelectRowAtIndexPath

(NSIndexPath )indexPath

Navigation logic may go here Create and pushanother view controller

end

Persistencia

52Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora soacutelo nos queda incorporar la vista que acabamos de crear dentro de nuestraaplicacioacuten Para ello vamos a llamarla desde el Delegate y a meterla dentro de unNavigation Controller Primero debemos de antildeadir un Outlet en la clasedelegada hacia un Navigation Controller

imports iniciales

interface sesion03_ejemplo1AppDelegate NSObjectltUIApplicationDelegategt

UIWindow _windowUINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet

UINavigationController navController

end

Ahora hacemos click sobre la vista MainWindowxib y arrastramos un Navigation

Controller desde el listado de la columna de la derecha hacia la columna de laizquierda donde pone Objects Nos apareceraacute el Navigation Controller dibujadoen pantalla Ahora desplegamos el Navigation Controller y hacemos click sobre elView Controller que hay dentro Nos vamos a la pestantildea de Identity Inspector

de la columna de la derecha y escribimos dentro de Class el nombre de la vista dellistado de personas PersonasViewController

Finalmente hacemos click sobre el objeto Delegate vamos a la pestantildea de Show

Connections Inspector y arrastramos desde navController hasta el objetoNavigation Controller de la columna de la izquierda Ya tenemos los controladoresy las vistas conectadas

Una vez hecho esto nos queda indicar dentro de nuestro coacutedigo que la aplicacioacuten arranquecon el Navigation Controller para ello escribimos lo siguiente al final del meacutetododidFinishLaunchingWithOptions de la implementacioacuten de la clase delegada

selfwindowrootViewController = selfnavController[selfwindow makeKeyAndVisible]return YES

Ya estaacute listo todo y preparado para compilar Si ejecutamos el proyecto veremos que nosaparece el listado de personas en la vista de tabla

Persistencia

53Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios

61 Creando y leyendo un fichero plist (15 puntos)

En este primer ejercicio vamos a crear nuestra primera aplicacioacuten usando el meacutetodo depersistencia de ficheros La aplicacioacuten mostraraacute en una vista de tabla TableView unlistado de series en la que si seleccionamos una veremos su informacioacuten maacutes importanteen otra vista Comenzaremos creando un fichero de propiedades (plist) siguiendo unaestructura determinada y posteriormente mostraremos en una vista de tabla todos susdatos iexclComenzamos

1) Crear un proyecto usando la plantilla Master-Detail application Lo llamaremosejercicio-plist-ios como identificador de empresa escribiremos esuajtech y como prefijode clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremos UseAutomatic Reference Counting para olvidarnos de la gestioacuten de memoria El resto deopciones las dejaremos sin marcar

2) Crear un archivo de propiedades (plist) con el nombre de series

3) Editar el archivo plist creando la siguiente estructura de datos (5 series miacutenimo)

Plist series

4) Rellenar el archivo plist con datos de series reales

5) Escribir el coacutedigo necesario en UAMasterViewControllerm que recorra el archivoplist que acabamos de crear y muestre por consola los titulos de las series

6) Mostrar en la tabla que hay implementada los tiacutetulos de la serie Para ello deberemosde completar todos los meacutetodos del protocolo del TableView

7) Ejecutar la aplicacioacuten y comprobar que funciona correctamente

Persistencia

54Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

62 Implementar vista de detalle de la serie (05 puntos)

Dentro del proyecto anterior (no hace falta crear uno nuevo) vamos a mostrar toda lainformacioacuten adicional de cada una de las series en una vista de detalle Cuandoseleccionemos una fila de la tabla nos deberaacute aparecer una nueva vista con el resto deinformacioacuten (antildeo director principal sinopsis y duracioacuten)

63 Uso baacutesico de una base de datos SQLite (1 punto)

En este ejercicio vamos a crear la misma aplicacioacuten que en el ejercicio anterior perousando el meacutetodo de persistencia de SQLite Para completar el ejercicio deberemos seguirlos siguientes pasos

1) Crear un proyecto usando la plantilla Master-Detail application lo llamaremosejercicio-sqlite-ios como identificador de empresa escribiremos esuajtech y comoprefijo de clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremosUse Automatic Reference Counting para olvidarnos de la gestioacuten de memoria El restode opciones las dejaremos sin marcar

2) Nos descargamos e instalamos el programa SQLite Database Browser desde estadireccioacuten

3) Disentildear la estructura de la base de datos usando SQLite Database Browser tal y comose muestra en la imagen siguiente

Sqlite series

4) Insertar al menos 5 series en la base de datos usando el programa SQLite DatabaseBrowser

5) Cargar todos los datos de la base de datos desde el meacutetododidFinishLaunchingWithOptions de la clase UAAppDelegate Mostrar por consola losdatos cargados

6) Mostrar los datos cargados en el paso anterior en la vista de tabla UITableView quehay creada en la clase UAMasterViewController

Persistencia

55Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7) Arrancar la aplicacioacuten y comprobar que se muestran las series en la tabla de formacorrecta

Persistencia

56Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7 Persistencia de datos en iOS User Defaults y Core Data

71 Introduccioacuten

En esta sesioacuten veremos como utilizar dos nuevos meacutetodos de persistencia en iOS ambosmuy interesantes y usados muy frecuentemente

bull User Defaults Muy uacutetil para almacenar datos simples y de uso repetido dentro denuestras aplicaciones iOS

bull Core Data Meacutetodo avanzado de almacenamiento de objetos enteros dentro de lamemoria del dispositivo iOS Muy uacutetil para almacenar datos complejos conrelaciones etc

72 User Defaults

Este meacutetodo es muy uacutetil cuando queremos almacenar pequentildeas cantidades de datos comopuntuaciones informacioacuten de usuario el estado de nuestra aplicacioacuten configuracionesetc

Este meacutetodo es el maacutes raacutepido de usar ya que no requiere de una base de datos ni ficherosni ninguna otra capa intermedia Los datos se almacenan directamente en la memoria deldispositivo dentro de un objeto de la clase NSUserDefaults

Para aprender a usar este meacutetodo de almacenamiento de datos vamos a crear unaaplicacioacuten muy simple que lea un nombre y una edad de pantalla y las guarde en unobjeto NSUserDefaults Para ello seguimos los siguientes pasos

bull 1) Comenzamos creando un nuevo proyecto llamado sesion04-ejemplo1 dentrode XCode

bull 2) Abrimos la vista sesion04_ejemplo1ViewController y arrastramos dos TextField un botoacuten y dos labels quedando la vista de la siguiente manera

Persistencia

57Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista de la aplicacioacuten

bull 3) Abrimos el fichero sesion04_ejemplo1ViewControllerh y definimos el Outletdel evento onclick del botoacuten El fichero quedaraacute de la siguiente manera

imports iniciales

interface sesion04_ejemplo1ViewController UIViewController UITextField tfNombreUITextField tfEdad

property(nonatomic retain) IBOutlet UITextField tfNombreproperty(nonatomic retain) IBOutlet UITextField tfEdad

-(IBAction) guardaDatos

end

bull 4) Ahora implementamos el coacutedigo para el meacutetodo del botoacuten dentro desesion04_ejemplo1ViewControllerm

synthesize tfEdadsynthesize tfNombre

-(IBAction) guardaDatos NSLog(Guardamos los datos)

Persistencia

58Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creamos el objeto NSUserDefaultsNSUserDefaults datos = [NSUserDefaults standardUserDefaults]

guardamos el nombre (NSString)[datos setObjecttfNombretext forKeynombre]

guardamos la edad (Integer)[datos setInteger[tfEdadtext integerValue] forKeyedad]

almacenamos los datos en la memoria[datos synchronize]

bull 5) Tenemos que enlazar el botoacuten con el evento que hemos programado en el pasoanterior para ello abrimos la vista sesion04_ejemplo1ViewControllerxib y conla tecla ctlr apretada arrastramos desde el botoacuten hasta Files Owner (columna de laizquierda parte superior) y seleccionamos el meacutetodo que hemos creado(guardaDatos) De esta forma ya tenemos conectado el botoacuten

bull 6) Ahora hacemos lo mismo con los Text Field Tenemos que conectarlos alcontrolador para ello seleccionamos el objeto Files Owner y abrimos la pestantildeade conexiones (la que estaacute maacutes a la derecha en la columna de la derecha) Hacemosclick en tfEdad y arrastramos hasta el Text Field de edad Hacemos lo mismo para eltext field de nombre Ya tenemos todos los elementos de la vista conectados

Conexioacuten de Outlets

bull 7) Arrancamos la aplicacioacuten y la probamos

Persistencia

59Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Aplicacioacuten

bull 8) Ahora vamos a hacer que la aplicacioacuten cargue los datos que se han guardado alarrancar la vista para ello simplemente tenemos que escribir el siguiente coacutedigo en elmeacutetodo (void)viewDidLoad de la clase sesion04_ejemplo1ViewControllerm

- (void)viewDidLoad

[super viewDidLoad]

NSUserDefaults datos = [NSUserDefaults standardUserDefaults]

obtenemos un NSStringNSString nombre = [datos stringForKeynombre]

obtenemos un NSIntegerNSInteger edad = [datos integerForKeyedad]

if (nombre=nil ampamp edad=0)NSLog(Nombre cargado edad dnombreedad)

else

NSLog(Sin datos)

bull 9) Ya podemos arrancar de nuevo la aplicacioacuten y veremos que si escribimos cualquier

Persistencia

60Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

texto en los Text Fields estos se almacenaraacuten dentro del objeto NSUserDefaults Alapagar la aplicacioacuten y volver a arrancarla estos datos se cargaraacuten (apareceraacuten enconsola)

Consola

NotaLos datos permaneceraacuten en nuestro dispositivo hasta que se elimine la aplicacioacuten en ese casotodos los datos guardados en NSUserDefaults se borraraacuten

bull 10) Probar eliminar la aplicacioacuten del dispositivosimulador y volver arrancarComprobamos que los datos se han borrado

Consola

73 Core Data

De todas las formas de persistencia de datos que existen en iOS Core Data es la maacutesapropiada para usar en caso de manejar datos no triviales con relaciones algo maacutescomplejas entre ellos El uso de Core Data dentro de nuestra aplicacioacuten optimiza almaacuteximo el consumo de memoria mejora el tiempo de respuesta y reduce la cantidad decoacutedigo redundante a escribir

Core Data utiliza de fondo el meacutetodo de almacenamiento SQLite visto en la sesioacuten 3 deeste moacutedulo por tanto para explicar el funcionamiento de este meacutetodo de persistenciavamos a utilizar el mismo modelo de datos que usamos en esa sesioacuten Persona -gtDetallePersona

El el siguiente ejemplo haremos la misma aplicacioacuten que hicimos con SQLite perousando este nuevo meacutetodo de persistencia

731 Creando el proyecto

Para empezar tenemos que crearnos un nuevo proyecto en XCode Para ello abrimosXCode y seleccionamos crear una nueva aplicacioacuten basada en ventanas (Window-basedapplication) Hacemos click en Next escribimos como nombre del proyectosesion04-ejemplo2 y seleccionamos Use Core Data

Persistencia

61Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creando un proyecto nuevo

Con esto ya tenemos la estructura baacutesica para empezar a programar Antes de nadahechamos un vistazo raacutepido a toda la estructura de directorios creada y vemos que dentrodel directorio principal tenemos un archivo que se llamasesion04_ejemplo2xcdatamodeld Al hacer click sobre el archivo se nos abre uneditor especial que en principio estaacute vacio y que maacutes adelante utilizaremos para disentildearnuestro modelo de datos

Ahora hechamos un vistazo a la clase delegada sesion04_ejemplo2AppDelegatemvemos que hay unos cuantos meacutetodos nuevos que serviraacuten para configurar el Core DataVamos a explicar los maacutes importantes

bull - (NSManagedObjectModel )managedObjectModel NSManagedObjectModel esla clase que contiene la definicioacuten de cada uno de los objetos o entidades quealmacenamos en la base de datos Normalmente este meacutetodo no lo utilizaremos ya quenosotros vamos a utilizar el editor que hemos visto antes con el que podremos crearnuevas entidades crear sus atributos y relaciones

bull - (NSPersistentStoreCoordinator )persistentStoreCoordinator Aquiacute esdonde configuramos los nombres y las ubicaciones de las bases de datos que se usaraacutenpara almacenar los objetos Cuando un managed object necesite guardar algo pasaraacutepor este meacutetodo

bull - (NSManagedObjectContext )managedObjectContext Esta claseNSManagedObjectContext seraacute la maacutes usada con diferencia de las tres y por tanto la

Persistencia

62Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

maacutes importante La utilizaremos baacutesicamente para obtener objetos insertarlos oborrarlos

732 Definiendo el modelo de datos

En el ejemplo que hicimos en la sesioacuten anterior de SQLite creamos dos tablas una paralos datos baacutesicos de la persona y otra para el detalle Ahora haremos lo mismo perousando Core Data la diferencia principal es que mientras que con SQLite obteniamos soacutelolos datos que necesitaacutebamos de la base de datos aquiacute obtenemos el objeto completo

Para definir nuestro modelo de datos basado en dos objetos (Persona y DetallePersona)abrimos el editor visual haciendo click sobre el ficherosesion04_ejemplo2xcdatamodeld Empezaremos creando una nueva entidad para ellohacemos click sobre el botoacuten Add Entity (+) que se encuentra en la parte inferior dela pantalla A la nueva entidad le pondremos como nombre Persona

Dentro de la seccioacuten de Attributes hacemos click sobre (+) y creamos un nuevoatributo para nuestra entidad le llamamos nombre y le asignamos como tipo StringRealizamos este mismo paso 2 veces maacutes para antildeadir apellidos y edad este uacutetlimode tipo Integer 16

Atributos Persona

Ahora creamos la entidad de DetallePersona de la misma manera que la anterior perocreando los atributos localidad pais direccion cp y telefono Todasde tipo String

Atributos DetallePersona

Finalmente nos queda relacionar de alguacuten modo las dos entidades para ello antildeadimos una

Persistencia

63Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

relacioacuten dentro de la entidad de Persona hacia DetallePersona simplementehaciendo click sobre el botoacuten (+) de la seccioacuten de Relationships A la relacioacuten leponemos como nombre detalle seleccionamos como destino la entidadDetallePersona y seraacute No inverse Este tipo de relacioacuten es uno a uno Por dentroCore Data realizaraacute crearaacute una clave ajena en la tabla de persona que apunte aDetallePersona pero eso a nosotros no nos interesa

Vista general modelo de datos (1)

Apple recomienda que siempre que hagamos una relacioacuten de una entidad a otra hagamostambieacuten la relacioacuten inversa por lo tanto hacemos justo lo anterior pero al reveacutes (deDetallePersona a Persona) con la diferencia que en la columna de Inverse

seleccionamos datosPersona

Ya tenemos las entidades y las relaciones creadas Si hacemos click en el botoacuten deEditor Style en la parte de abajo a la derecha del editor podemos ver de una formamaacutes clara la estructura que hemos disentildeado

Persistencia

64Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista general modelo de datos (2)

733 Probando el modelo

Una vez que tenemos el modelo creado pasamos a probarlo para comprobar que estaacute todocorrecto para ello abrimos la clase delegada (sesion04_ejemplo2AppDelegatem) yantildeadimos el siguiente fragmento de coacutedigo arriba del meacutetodoapplicationDidFinishLaunching

NSManagedObjectContext context = [self managedObjectContext]NSManagedObject persona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

[persona setValueJuan forKeynombre][persona setValueMartinez forKeyapellidos][persona setValue[NSNumber numberWithInt28] forKeyedad]

NSManagedObject detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersonainManagedObjectContextcontext]

[detallePersona setValueCalle Falsa 123 forKeydireccion][detallePersona setValueAlicante forKeylocalidad][detallePersona setValueEspantildea forKeypais][detallePersona setValue965656565 forKeytelefono]

[detallePersona setValuepersona forKeydatosPersona][persona setValuedetallePersona forKeydetalle]

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Persistencia

65Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Con este coacutedigo hemos creado un objeto Persona enlazado con otro objetoDetallePersona iexclComo puedes ver no hemos necesitado nada de coacutedigo SQLPrimero creamos un objeto del tipo NSManagedObjectContext que almacena el conextode Core Data Despueacutes creamos el objeto Persona en siacute con NSManagedObject A esteobjeto le pondremos los valores para cada una de las propiedades definidas en el modeloHacemos lo mismo con DetallePersona Finalmente relacionamos el objetoPersona con el objeto DetallePersona

Compilamos el proyecto comprobamos que todo funciona bien y no salen errores

Para comprobar que los datos se han almacenado correctamente en nuestro dispositivo losmostramos en la consola de la siguiente manera

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

intValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(------------------)

[fetchRequest release]

El coacutedigo de arriba lo escribiremos dentro del meacutetododidFinishLaunchingWithOptions de la clase delegada Lo que hace este coacutedigo esrecorrer todos los objetos almacenados en memoria y mostrarlos por consola Siarrancamos la aplicacioacuten veremos que se muestran todos los datos de los objetosalmacenados Cada vez que arranquemos la aplicacioacuten se mostraraacuten maacutes objetos ya quetambieacuten los creamos

Para listar los objetos usamos la clase NSFetchRequest Esta clase es equivalente aejecutar una sentencia Select de SQL Al objeto NSFetchRequest le asignamos laentidad que queremos listar (SELECT FROM ) en nuestro caso seraacute PersonaEl listado de los objetos se obtiene mediante el meacutetodo executeFetchRequest de laclase NSManagedObjectContext que hemos definido en el bloque anterior de coacutedigoEste meacutetodo devolveraacute un NSArray con todos los objetos NSManagedObject

Persistencia

66Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Salida de consola

NotaSi queremos ver queacute sentencias SQL estaacute ejecutando Core Data por debajo tenemos que activarel flag de debug para ello seleccionamos la opcioacuten Product gt Edit Scheme Enla columna de la izquierda seleccionamos el esquema que estamos utilizando para Debug y en laparte de la derecha en el cuadro de Arguments Passed on Launch antildeadimos 2paraacutemetros -comappleCoreDataSQLDebug y 1

Ahora arrancamos de nuevo la aplicacioacuten y veremos que en la consola apareceraacuten lassentencias SQL utilizadas por Core Data

()

INSERT INTO ZDETALLEPERSONA(Z_PK Z_ENT Z_OPT ZDATOSPERSONA ZTELEFONOZLOCALIDAD ZDIRECCION ZPAIS)VALUES( )CoreData sql SELECT 0 t0Z_PK t0Z_OPT t0ZTELEFONO t0ZLOCALIDADt0ZDIRECCION t0ZPAIS t0ZDATOSPERSONAFROM ZDETALLEPERSONA t0 WHERE t0Z_PK =

()

734 Generando los modelos automaacuteticamente

Generar los modelos a mano directamente desde el editor no es lo maacutes recomendado yaque podemos cometer errores de escritura errores de tipo etc La mejor manera dehacerlo es creando un archivo para cada entidad Esto se puede hacer desde 0 peroXCode tiene un generador de clases que es muy faacutecil de usar y ahorremos bastantetiempo iexclVamos a usarlo

Hacemos click sobre el raiz de nuestro proyecto y seleccionamos New File En lacolumna de la izquierda seleccionamos iOS gt Core Data y en el cuadro de laderecha NSManagedObject subclass Click en next En la siguiente pantallaseleccionamos nuestro modelo de datos sesion04_ejemplo2 y click en next Ahoranos aseguramos de seleccionar las dos entidades que hemos creado anteriormentePersona y DetallePersona click en next Seleccionamos el raiz de nuestroproyecto para guardar los ficheros que se generen y aceptamos

Persistencia

67Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ventana de seleccioacuten de entidad

Ahora veremos que xsource ha creado automaacuteticamente 4 ficheros nuevos dentro denuestro proyecto Personamh y DetallePersonamh con todos los atributosespecificados Estas nuevas clases que heredan de NSManagedObject seraacuten las queutilicemos a partir de ahora

Cambiamos el coacutedigo de la clase delegada por el siguiente

NSManagedObjectContext context = [self managedObjectContext]Persona datosPersona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

datosPersonanombre = JuandatosPersonaapellidos = MartinezdatosPersonaedad = [NSNumber numberWithInt28]

DetallePersona detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersona

inManagedObjectContextcontext]detallePersonadireccion = Calle Falsa 123

Persistencia

68Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

detallePersonalocalidad = AlicantedetallePersonapais = EspantildeadetallePersonatelefono = 965656565

detallePersonadatosPersona = datosPersonadatosPersonadetalle = detallePersona

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Listamos los datosNSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

integerValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(-----------------)[fetchRequest release]

Ejecutamos el proyecto y comprobamos que en la consola aparece lo mismo que antespero ahora tenemos el coacutedigo bastante maacutes claro

735 Creando la vista de tabla

Ahora vamos a mostrar los objetos que antes hemos listado por consola en una vista detabla UITableView Para ello creamos un UITableViewController

Click en New file gt UIViewController subclass En la siguiente pantallanos aseguramos de seleccionar Subclass of UITableViewController de esta formanos apareceraacuten ya todos los meacutetodos de la tabla creados por defecto Guardamos elarchivo con el nombre que queramos por ejemplo PersonasViewController

Abrimos ahora PersonasViewControllerh y antildeadimos 2 atributos a la clase

bull Un NSArray que se seraacute el listado de personas (objetos de la clase Persona)bull Una referencia a NSManagedObjectContext

El fichero PersonasViewControllerh quedaraacute de la siguiente manera

Persistencia

69Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

imports iniciales

interface PersonasViewController UITableViewController NSArray _listaPersonasNSManagedObjectContext _context

property (nonatomic retain) NSArray listaPersonasproperty (nonatomic retain) NSManagedObjectContext context

end

Ahora en el m importamos en el encabezado las clases Personah yDetallePersonah y en el meacutetodo viewDidLoad creamos el array listaPersonas contodos los objetos Persona de forma similar a cuando lo hicimos en el apartado anterior

import PersonasViewControllerh

import Personahimport DetallePersonah

implementation PersonasViewController

synthesize listaPersonas = _listaPersonassynthesize context = _context

En viewDidLoad

- (void)viewDidLoad

[super viewDidLoad]

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContext_context]

[fetchRequest setEntityentity]NSError errorselflistaPersonas = [_context executeFetchRequestfetchRequest

erroramperror]selftitle = Listado Personas[fetchRequest release]

Ahora completamos el resto de meacutetodos heredados de UITableViewController de lamisma forma que hicimos en sesiones anteriores

- (NSInteger)numberOfSectionsInTableView(UITableView )tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection(NSInteger)section

Persistencia

70Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Return the number of rows in the sectionreturn [_listaPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]

if (cell == nil) cell = [[[UITableViewCell alloc]

initWithStyleUITableViewCellStyleSubtitlereuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona p = (Persona )[_listaPersonas objectAtIndexindexPathrow]celltextLabeltext = pnombrecelldetailTextLabeltext = [NSString stringWithFormatd antildeos

[pedad intValue]]

return cell

Ahora para poder mostrar la tabla dentro de nuestra aplicacioacuten debemos asignarla dentrode la clase delegada para ello abrimos la vista MainWindowxib y arrastramos unacontroladora de navegacioacuten Navigation Controller a la lista de objetos de laizquierda La desplegamos y dentro de View Controller en la pestantildea de la derechade Identity Inspector escribimos el nombre de nuestra clase que tiene la vista de latabla PersonasViewController

Pantalla del disentildeador

Ahora abrimos el fichero sesion04_ejemplo2AppDelegateh y creamos un

Persistencia

71Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

UINavigationController dentro de la declaracioacuten de variables dejando el ficherocomo sigue

imports iniciales

interface sesion04_ejemplo2AppDelegate NSObject ltUIApplicationDelegategt

UINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet UINavigationControllernavController

property (nonatomic retain readonly) NSManagedObjectContextmanagedObjectContextproperty (nonatomic retain readonly) NSManagedObjectModelmanagedObjectModelproperty (nonatomic retain readonly) NSPersistentStoreCoordinatorpersistentStoreCoordinator

- (void)saveContext- (NSURL )applicationDocumentsDirectory

end

Dentro del m importamos la clase PersonasViewControllerh y completamos elsynthetize

synthesize navController = _navController

Y por uacuteltimo justo antes de la llamada al meacutetodo makeKeyAndVisible escribimos elsiguiente coacutedigo para asignar el contexto de la clase delegada alPersonasViewController

PersonasViewController root = (PersonasViewController ) [_navControllertopViewController]

rootcontext = [self managedObjectContext][selfwindow addSubview_navControllerview]

Ahora compilamos y ejecutamos nuestro proyecto y si todo ha ido bien deberiacutea de salirla tabla con los datos de las personas

Persistencia

72Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Listado de personas

736 Migraciones de bases de datos con Core Data

Una vez que tenemos disentildeada nuestra base de datos inicial iquestqueacute pasariacutea si queremosmodificarla para antildeadirle un campo maacutes a una tabla iquestQueacute haraacute Core Data para realizar lamigracioacuten de la versioacuten anterior a la actual

Siguiendo con el ejemplo anterior si despueacutes de ejecutar la aplicacioacuten en nuestrodispositivo o simulador modificamos la estructura de la base de datos antildeadiendo unatributo nuevo en la entidad DetallePersona por ejemplo codigoPostal con el tipoInteger 16 veremos que al volver a arrancar la aplicacioacuten nos apareceraacute un error entiempo de ejecucioacuten similar al siguiente

Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 Theoperationcouldnrsquot be completed(Cocoa error 134100) UserInfo=0x6b6c680 metadata=ltCFBasicHash 0x6b68380[0x1337b38]gttype = immutable dict count = 7entries =gt

2 ltCFString 0x6b70320 [0x1337b38]gtcontents =NSStoreModelVersionIdentifiers

=ltCFArray 0x6b706e0 [0x1337b38]gttype = immutable count = 1

values = (0 ltCFString 0x1332cd8 [0x1337b38]gtcontents =

)4 ltCFString 0x6b70350 [0x1337b38]gtcontents =

NSPersistenceFrameworkVersion=

Persistencia

73Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltCFNumber 0x6b6f2c0 [0x1337b38]gtvalue = +386 type =kCFNumberSInt64Type

6 ltCFString 0x6b70380 [0x1337b38]gtcontents =NSStoreModelVersionHashes =

ltCFBasicHash 0x6b707e0 [0x1337b38]gttype = immutable dict count =2entries =gt

1 ltCFString 0x6b70700 [0x1337b38]gtcontents = Persona =ltCFData 0x6b70740

[0x1337b38]gtlength = 32 capacity = 32 bytes =0x1c50e5a379ff0ddc3cda7b82a3934c5e 75649ac3c919beea2 ltCFString 0x6b70720 [0x1337b38]gtcontents = DetallePersona

=ltCFData 0x6b70790[0x1337b38]gtlength = 32 capacity = 32 bytes =0xb7ba2eb498f9a1acdd6630d56f6cd12c e8963dd3e488ba95

7 ltCFString 0x10e3aa8 [0x1337b38]gtcontents = NSStoreUUID =ltCFString 0x6b70510[0x1337b38]gtcontents = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E8 ltCFString 0x10e3948 [0x1337b38]gtcontents = NSStoreType =ltCFString 0x10e3958[0x1337b38]gtcontents = SQLite9 ltCFString 0x6b703b0 [0x1337b38]gtcontents =

_NSAutoVacuumLevel = ltCFString0x6b70830 [0x1337b38]gtcontents = 210 ltFString 0x6b703d0 [0x1337b38]gtcontents =

NSStoreModelVersionHashesVersion=ltCFNumber 0x6b61950 [0x1337b38]gtvalue = +3 type =

kCFNumberSInt32Type reason=The model used to open the store is incompatible with the oneused tocreate the store

metadata = NSPersistenceFrameworkVersion = 386NSStoreModelVersionHashes =

DetallePersona = ltb7ba2eb4 98f9a1ac dd6630d5 6f6cd12c f890007746b16f00

e8963dd3 e488ba95gtPersona = lt1c50e5a3 79ff0ddc 3cda7b82 a3934c5e 5cb4ee4b

4ac98838 75649ac3c919beeagt

NSStoreModelVersionHashesVersion = 3NSStoreModelVersionIdentifiers = (

)NSStoreType = SQLiteNSStoreUUID = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E_NSAutoVacuumLevel = 2

reason = The model used to open the store is incompatible with the

one usedto create the store

Este error es muy comuacuten cuando estamos desarrollando aplicaciones con Core DataBaacutesicamente nos dice que el modelo de base de datos que estaacute intentando cargar dememoria es distinto al que se indica en la aplicacioacuten esto es porque no hemos indicadoninguacuten meacutetodo de migracioacuten Realmente existen dos formas de solucionar este error

Persistencia

74Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Por un lado y soacutelo cuando estemos desarrollando y no tengamos ninguna versioacutende nuestra aplicacioacuten lanzada en la App Store podremos eliminar la base de datosdel dispositivo simulador simplemente borrando la aplicacioacuten de el Tambieacutenpodemos ejecutar el siguiente coacutedigo una soacutela vez [[NSFileManagerdefaultManager] removeItemAtURLstoreURL errornil]

bull En el caso de que estemos realizando una migracioacuten sobre una versioacuten ya publicadaen la App Store el meacutetodo anterior no nos serviriacutea ya que obligariacuteamos al usuario aborrar su aplicacioacuten del dispositivo con lo que todos los datos que tuviera seperderiacutean Esto no es viable por lo que necesitaremos usar otro meacutetodo para realizarla migracioacuten y solucionar el error de una forma maacutes limpia este meacutetodo es el quevamos a comentar a continuacioacuten

XCode soporta Versioning para Core Data esto es que podemos crear versiones de labase de datos de una forma sencilla si seguimos una serie de pasos

1) Antildeadimos las siguientes opciones en forma de NSDictionary a nuestro coacutedigo delpersistentStoreCoordinator

NSDictionary options = [NSDictionary dictionaryWithObjectsAndKeys[NSNumber numberWithBoolYES]NSMigratePersistentStoresAutomaticallyOption[NSNumber numberWithBoolYES]NSInferMappingModelAutomaticallyOptionnil]

Y modificamos el meacutetodo addPersistentStoreWithType pasaacutendole como paraacutemetro eldiccionario options

if ([__persistentStoreCoordinatoraddPersistentStoreWithTypeNSSQLiteStoreTypeconfigurationnil URLstoreURL optionsoptions erroramperror])

Con el coacutedigo anterior indicamos al persistentStoreCoordinator que la migracioacuten enel caso de que se deba de realizar sea de forma automaacutetica

AtencioacutenDeberemos de tener en cuenta que este tipo de migracioacuten que estamos estudiando en este puntosoacutelo es vaacutelida para cambios simples en Core Data lo que Apple llama como lightweightmigration Este tipo de migracioacuten admite una serie de cambios que se indican dentro de ladocumentacioacuten de Apple para cambios maacutes complejos deberemos realizar migracionespersonalizadas y mucho maacutes complejas El documento en donde Apple explica el tipo demigraciones que existen para Core Data y su forma de implementarlas lo podemos consultardesde esta web

2) Ahora deberemos de crear la versioacuten del esquema de Core Data dentro de XCode para

Persistencia

75Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ello seleccionamos el fichero del esquema sesion04_ejemplo2xcdatamodeld yseleccionamos en el menuacute principal Editor gt Add Model Version A la nueva versioacuten lallamaremos sesion04_ejemplo_2 (versioacuten 2) Una vez que hayamos pulsado sobreFinish se nos habraacute creado la nueva versioacuten del esquema dentro de la principal y estaraacutemarcada con un tick la versioacuten anterior

3) Una vez que tenemos creada la nueva versioacuten deberemos modificarla antildeadiendole elnuevo atributo Seleccionamos la versioacuten recieacuten creadasesion04_ejemplo_2xcdatamodel y antildeadimos a la entidad DetallePersona elatributo codigoPostal Atencioacuten Si este atributo lo hemos antildeadido anteriormentedeberemos de borrarlo de la versioacuten anterior y antildeadirlo en la segunda versioacuten

4) Ahora nos queda indicar a Core Data que queremos usar la versioacuten 2 de la base dedatos para nuestra aplicacioacuten Para ello seleccionamos el esquema principal (el raiz)sesion04_ejemplo2xcdatamodeld y en la ventana de la derecha en la pestantildea de FileInspector seleccionamos como Versioacuten actual la versioacuten 2 esto lo hacemos dentro delapartado de Versioned Core Data Model Automaacuteticamente veremos que el siacutembolo detick cambia a la versioacuten 2

5) Ahora ya podemos volver a compilar y veremos que no nos aparece el error y funcionatodo seguacuten lo esperado

Magical RecordExisten varias librerias de coacutedigo libre que facilitan enormemente el uso de Core Data ennuestras aplicaciones iOS Una de ellas es Magical Record Mediante esta libreriacutea podremos usarCore Data sin tener que preocuparnos de todo el proceso de configuracioacuten en las controladorasde la configuracioacuten de migraciones etc Su instalacioacuten en un proyecto de XCode es muy faacutecil yaltamente recomendable ya que proporciona una API muy clara y sencilla de usar

Persistencia

76Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios

81 Usando User Defaults (1 punto)

En este ejercicio vamos a usar las variables de usuario para almacenar y mostrar los datosdel dispositivo que estamos usando y las veces que hemos arrancado la aplicacioacuten

Crear un proyecto nuevo usando la plantilla Single View Application con el nombreejercicio-userdefaults-ios Se debe de ejecutar en iPhone e iPad (Universal) Comoidentificador de empresa escribiremos esuajtech y como prefijo de clase UA Debe detener el ARC activado (desmarcar el resto de opciones)

Ayuda meacutetodos de UIDevicePara obtener los datos del dispositivo usaremos el singleton UIDevice estos son algunos de losmeacutetodos que podemos usar [[UIDevice currentDevice] name] [[UIDevicecurrentDevice] systemName] [[UIDevice currentDevice]systemVersion] [[UIDevice currentDevice] model] [[UIDevicecurrentDevice] localizedModel]

82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)

En este ejercicio disentildearemos un modelo de datos de dos tablas relacionadas entre ellasantildeadiremos datos por coacutedigo y los mostraremos en una vista de tabla La base de datosque vamos a implementar constaraacute de dos tablas por un lado una que contendraacute los tiacutetulosde series y otra que tendraacute los detalles A la primera la llamaremos Serie y la segundaDetalleSerie Para realizar correctamente el ejercicio deberemos seguir los siguientespasos

1) Crear un proyecto nuevo usando la plantilla Master-Detail Application con elnombre ejercicio-coredata-ios Se debe de ejecutar en iPhone como identificador deempresa escribiremos esuajtech y como prefijo de clase UA Debe de tener el ARCactivado y la opcioacuten de Use Core Data (desmarcar el resto de opciones)

2) Disentildear el siguiente modelo de datos

Persistencia

77Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esquema modelo de datos

3) Revisar el fichero UAAppDelegatem y comprobar que tenemos todos los meacutetodosnecesarios para que funcione Core Data en nuestra aplicacioacuten

4) Modificar el fichero UAMasterViewControllerm para que se muestren los tiacutetulos delas series en la tabla

5) Probar la aplicacioacuten antildeadiendo unos tiacutetulos de prueba

83 Migrando datos con Core Data (05 puntos)

Una vez que hemos disentildeado la Base de Datos con Core Data vamos a modificarlaantildeadiendo un atributo maacutes a la tabla de DetalleSerie valoracion

a) iquestQueacute pasa si cambiamos el modelo de datos y arrancamos la aplicacioacuten iquestQueacute errornos da y por queacute

b) iquestCoacutemo podemos evitar este tipo de errores cuando estemos desarrollando iquestY siestamos en produccioacuten con la aplicacioacuten ya a la venta

c) Crea una nueva versioacuten del modelo

d) Modifica el coacutedigo necesario dentro de la clase UAAppDelegate para evitar este tipo deerrores cuando estemos en un entorno de produccioacuten

e) Vuelve a arrancar la aplicacioacuten y comprueba que funciona bien

Persistencia

78Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Persistencia

79Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

  • 1 Persistencia en Android ficheros y SQLite
    • 11 Introduccioacuten
    • 12 Manejo de ficheros tradicionales en Android
      • 121 Apertura de ficheros
      • 122 Ficheros como recursos
      • 123 Operar con ficheros
      • 124 Almacenar datos en la tarjeta de memoria
        • 13 Base de datos SQLite
          • 131 Content Values
          • 132 Cursores
          • 133 Trabajar con bases de datos SQLite
          • 134 La clase SQLiteOpenHelper
          • 135 Crear una base de datos sin SQLiteHelper
          • 136 Realizar una consulta
          • 137 Extraer resultados de un cursor
          • 138 Antildeadir actualizar y borrar filas
              • 2 Ejercicios - Persistencia en Android ficheros y SQLite
                • 21 Uso de ficheros (05 puntos)
                • 22 Persistencia con ficheros (05 puntos)
                • 23 Base de datos SQLiteOpenHelper (05 puntos)
                • 24 Base de datos insercioacuten y borrado (05 puntos)
                • 25 Base de datos probar nuestro adaptador (05 puntos)
                • 26 Base de datos cambios en la base de datos (05 puntos)
                  • 3 Persistencia en Android proveedores de contenidos y SharedPreferences
                    • 31 Shared Preferences
                      • 311 Guardar Shared Preferences
                      • 312 Leer Shared Preferences
                      • 313 Interfaces para Shared Preferences
                      • 314 Definiendo una pantalla de preferencias con un layout en XML
                      • 315 Controles nativos para preferencias
                      • 316 Actividades de preferencias
                      • 317 Shared Preference Change Listeners
                        • 32 Proveedores de contenidos
                          • 321 Proveedores nativos
                          • 322 Proveedores propios crear un nuevo proveedor de contenidos
                          • 323 Proveedores propios crear la interfaz de consultas
                          • 324 Proveedores propios tipo MIME
                          • 325 Proveedores propios registrar el proveedor
                          • 326 Content Resolvers
                          • 327 Otras operaciones con Content Resolvers
                              • 4 Ejercicios - Persistencia en Android proveedores de contenidos y SharedPreferences
                                • 41 Compartir datos entre actividades con Shared Preferences (075 puntos)
                                • 42 Actividad de preferencias (075 puntos)
                                • 43 Proveedor de contenidos propio (075 puntos)
                                • 44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)
                                  • 5 Persistencia de datos en iOS Ficheros y SQLite
                                    • 51 Introduccioacuten
                                    • 52 Ficheros plist (Property Lists)
                                      • 521 Leyendo datos desde ficheros plist
                                      • 522 Escribiendo datos en ficheros plist
                                        • 53 SQLite
                                          • 531 Herramientas disponibles
                                          • 532 Lectura de datos
                                          • 533 Mostrando los datos en una tabla
                                              • 6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios
                                                • 61 Creando y leyendo un fichero plist (15 puntos)
                                                • 62 Implementar vista de detalle de la serie (05 puntos)
                                                • 63 Uso baacutesico de una base de datos SQLite (1 punto)
                                                  • 7 Persistencia de datos en iOS User Defaults y Core Data
                                                    • 71 Introduccioacuten
                                                    • 72 User Defaults
                                                    • 73 Core Data
                                                      • 731 Creando el proyecto
                                                      • 732 Definiendo el modelo de datos
                                                      • 733 Probando el modelo
                                                      • 734 Generando los modelos automaacuteticamente
                                                      • 735 Creando la vista de tabla
                                                      • 736 Migraciones de bases de datos con Core Data
                                                          • 8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios
                                                            • 81 Usando User Defaults (1 punto)
                                                            • 82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)
                                                            • 83 Migrando datos con Core Data (05 puntos)
Page 14: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de

nueva fila pasaremos como paraacutemetro este objeto del tipo ContentValues al meacutetodoinsert de la instancia de la base de datos junto por supuesto con el nombre de la tablaA continuacioacuten se muestra un ejemplo

Crear la nueva filaContentValues nuevaFila = new ContentValues()

Asignamos valores a cada columnanuevaFilaput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas ]

Insertar la nueva fila en la tabladbinsert(TABLA_BASE_DATOS null nuevaFila)

NotaLos ficheros como imaacutegenes o ficheros de audio no se suelen almacenar dentro de tablas en unabase de datos En estos casos se suele optar por almacenar en la base de datos una cadena con laruta al fichero o una URI

La operacioacuten de actualizar una fila tambieacuten puede ser llevada a cabo por medio del usode la clase ContentValues En este caso deberemos llamar al meacutetodo update de lainstancia de la base de datos pasando como paraacutemetro el nombre de la tabla el objetoContentValues y una claacuteusula where que especifique la fila o filas a actualizar tal comose puede ver en el siguiente ejemplo

Creamos el objeto utilizado para definir el contenido a actualizarContentValues valoresActualizar = new ContentValues()

Asignamos valores a las columnas correspondientesvaloresActualizarput(NOMBRE_COLUMNA nuevoValor)[ Repetir para el resto de columnas a actualizar ]

String where = CLAVE_ID + = + idFila

Actualizamos la fila con el iacutendice especificado en la instruccioacutenanteriordbupdate(TABLA_BASE_DATOS valoresActualizar where null)

Por uacuteltimo para borrar una fila simplemente llamaremos al meacutetodo delete de lainstancia de la base de datos especificando en este caso el nombre de la tabla que se veraacuteafectada por la operacioacuten y una claacuteusula where que utilizaremos para especificar las filasque queremos borrar Un ejemplo podriacutea ser el siguiente

dbdelete(TABLA_BASE_DATOS CLAVE_ID + = + idFila null)

Persistencia

14Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

2 Ejercicios - Persistencia en Android ficheros y SQLite

21 Uso de ficheros (05 puntos)

En este ejercicio vamos a crear una aplicacioacuten que muestre un listado de cadenas porpantalla Estas cadenas se almacenaraacuten en un fichero de texto privado para la aplicacioacutenPodremos antildeadir nuevas cadenas a partir de un cuadro de edicioacuten presente en la interfazPartiremos del proyecto Ficheros proporcionado en las plantillas de la aplicacioacuten Debesseguir los siguientes pasos

bull Antildeadir un manejador al botoacuten de la actividad principal para que cada vez que seapulsado se guarde en un fichero de texto El fichero se llamaraacute ficherotxt Al abrirel fichero para escritura se utilizaraacute el paraacutemetro ContextMODE_APPEND con lo quecada nueva cadena se antildeadiraacute al final del fichero en el caso en el que eacuteste ya existieraPara escribir en el fichero utilizaremos el meacutetodo writeBytes del objetoDataOutputStream correspondiente

bull Al pulsar el botoacuten tambieacuten se deberaacute borrar el contenido del elemento EditText (leasignamos a la vista una cadena vaciacutea con el meacutetodo setText)

bull Ejecutamos la aplicacioacuten e introducimos algunas liacuteneas en el fichero Vamos acomprobar ahora que todo ha funcionado correctamente Para ello accedemos alsistema de ficheros de nuestro dispositivo ejecutando el comando adb shell

dentro de la carpeta platform-tools de nuestra carpeta de instalacioacuten del SDK deAndroid

bull Comprueba que se ha creado el fichero ficherotxt en la carpetadatadataesuajtechandroidficherosfiles Examina su contenido ejecutando cat

ficherotxt Si algo no ha salido bien siempre podraacutes eliminar el fichero con rm

ficherotxtbull En la interfaz de la actividad se ha incluido una vista de tipo TextView debajo del

botoacuten Antildeade un manejador para dicha vista de tal forma que cada vez que se hagaclick sobre ella se muestre el contenido del fichero ficherotxt Para ello leeremos elfichero liacutenea a liacutenea antildeadiendo cada una al TextView por medio del meacutetodo append

Persistencia

15Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Interfaz de la aplicacioacuten Ficheros

22 Persistencia con ficheros (05 puntos)

Seguramente habraacutes observado que si abandonas la aplicacioacuten anterior (pulsando el botoacutenBACK del dispositivo) al volver a ejecutarla el contenido del TextView ha desaparecidoDebes volver a pulsar sobre la vista para que se vuelva a mostrar el contenido del ficheroHaz las modificaciones pertinentes para que esto no sea asiacute es decir para que cuandovuelva a mostrarse la actividad tras haber sido eliminada de la memoria se muestre elTextView tal cual se veiacutea antes de abandonar la aplicacioacuten

AvisoEn este ejercicio no se estaacute pidiendo que cargues el contenido del fichero en el TextView nadamaacutes arrancar la aplicacioacuten Puede darse el caso de que lo que esteacute mostrando el TextViewcuando la actividad sea eliminada de memoria no se corresponda con el contenido actualizado delfichero

NotaPara poder completar el ejercicio deberaacutes repasar los manejadores de evento de la primera sesioacutendel moacutedulo de Android relacionados con el ciclo de vida de ejecucioacuten de actividades

23 Base de datos SQLiteOpenHelper (05 puntos)

En las plantillas de la aplicacioacuten se incluye la aplicacioacuten BaseDatos En el proyecto de laaplicacioacuten se incluye el esqueleto de una clase MiAdaptadorBD que tendremos quecompletar Se trata de un patroacuten que nos va a permitir acceder a una base de datos de

Persistencia

16Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

usuarios dentro de la aplicacioacuten sin necesidad de hacer uso de coacutedigo SQL

La clase MiAdaptadorBD incluye a su vez otro patroacuten en este caso implementado comouna subclase de SQLiteOpenHelper Eacuteste nos obliga a definir queacute ocurre cuando la basede datos todaviacutea no existe y debe ser creada y queacute ocurre si ya existe pero debe seractualizada porque ha cambiado de versioacuten Asiacute el SQLiteOpenHelper queimplementemos en este caso MiOpenHelper nos devolveraacute siempre una base de datosseparaacutendonos de la loacutegica encargada de comprobar si la base de datos existe o no

En este primer ejercicio se pide hacer lo siguiente

bull Ejecutar la sentencia de creacioacuten de bases de datos (la tenemos declarada comoconstante de la clase) en el meacutetodo MiOpenHelperonCreate

bull Implementar tambieacuten el meacutetodo onUpgrade Idealmente eacuteste deberiacutea portar las tablasde la versioacuten antigua a la versioacuten nueva copiando todos los datos Nosotros vamos aeliminar directamente la tabla que tenemos con la sentencia SQL DROP TABLE IF

EXISTS + NOMBRE_TABLA y volveremos a crearlabull En el constructor de MiAdaptadorBD debemos obtener en el campo db la base de

datos utilizando MiOpenHelper

24 Base de datos insercioacuten y borrado (05 puntos)

Continuamos trabajando con el proyecto anterior En este ejercicio completaremos elcoacutedigo relacionado con las sentencias de insercioacuten y borrado Para realizar la insercioacutenvamos a hacer uso de un mecanismo que no hemos visto en la sesioacuten de teoriacutea y queconsiste en utilizar una sentencia de insercioacuten SQL precompilada que se completaraacute conlos valores concretos a insertar en la base de datos justo antes de realizar dicha insercioacutenLa ventaja de las sentencias compiladas es que evitan que se produzcan ataques deinyeccioacuten de coacutedigo

Como se puede observar se ha incluido un atributo a la clase que representa la insercioacutenSQL

private static final String INSERT = insert into + NOMBRE_TABLA +(+COLUMNAS[1]+) values ()

En esta sentencia no se indica ninguacuten valor concreto para la columna nombre En el lugaren el que deberiacutean aparecer los valores de dicho campo se ha escrito simplemente unsiacutembolo de interrogacioacuten Tambieacuten se ha antildeadido una instancia de la claseSQLiteStatement como parte de los atributos de la clase

private SQLiteStatement insertStatement

Realizamos los siguientes pasos

bull En el constructor de la clase MiAdaptadorBD llevamos a cabo la compilacioacuten de lasentencia

thisinsertStatement = thisdbcompileStatement(INSERT)

Persistencia

17Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Una vez hecho esto cada vez que deseemos insertar un nuevo usuario en la base dedatos deberemos dar valores concretos a la columna nombre y ejecutar la sentenciaPara ello debemos antildeadir el siguiente coacutedigo dentro del meacutetodo insert de la claseMiAdaptadorBD

thisinsertStatementbindString(1 nombreUsuario)return thisinsertStatementexecuteInsert()

bull Por uacuteltimo completamos el coacutedigo del meacutetodo deleteAll cuyo contenido es eliminara todos los usuarios de la base de datos Para que ademaacutes se devuelva el nuacutemero defilas afectadas podemos insertar la siguiente liacutenea en dicho meacutetodo

return dbdelete(NOMBRE_TABLA null null)

Una vez hechos todos estos cambios podremos utilizar la clase MiAdaptadorBD parahacer operaciones con la base de datos de usuarios de manera transparente

25 Base de datos probar nuestro adaptador (05 puntos)

Antildeade coacutedigo en la actividad Main para eliminar todos los usuarios de la base de datosantildeadir dos cualesquiera y listarlos por medio de la vista de tipo TextView que seencuentra en el layout de dicha actividad

Podemos comprobar mediante la liacutenea de comandos (comando adb shell) que la basede datos ha sido creada Para ello puede ser uacutetil hacer uso de los siguientes comandos

cd datadataesuajtechandroidbasedatosdatabasessqlite3 misusuariosdbsqlitegt schemasqlitegt tablessqlitegt select from usuarios

26 Base de datos cambios en la base de datos (05 puntos)

Ahora vamos a cambiar en la clase MiAdaptadorBD el nombre de la segunda columnaque en lugar de nombre se va a llamar nombres Ejecutamos la aplicacioacuten y comprobamosque sale con una excepcioacuten Comprueba por medio de LogCat cuaacutel ha sido el erroriquestCoacutemo lo podemos solucionar

NotaPista conforme hemos programado la clase MiAdaptadorBD y siguiendo el patroacuten de disentildeode SQLiteOpenHelper es posible arreglar el problema simplemente modificando el valor deun campo

Persistencia

18Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

3 Persistencia en Android proveedores de contenidos ySharedPreferences

31 Shared Preferences

Comenzamos esta sesioacuten hablando de una de las teacutecnicas maacutes simples de persistencia enAndroid junto al uso de ficheros Shared Preferences Se trata de un mecanismo ligeropara almacenar datos basado en pares clave-valor utilizado normalmente para guardarpreferencias u opciones de la aplicacioacuten Tambieacuten puede ser utilizado para almacenar elestado de la interfaz graacutefica para contemplar el caso de que nuestra actividad debafinalizar abruptamente su ejecucioacuten Otro posible uso es el de compartir informacioacutenentre componentes de una misma aplicacioacuten

NotaLos Shared Preferences nunca son compartidos entre diferentes aplicaciones

Este meacutetodo se basa en el uso de la clase SharedPreferences Es posible utilizarla paraguardar datos en muy diversos formatos booleanos cadenas flotantes y enteros

311 Guardar Shared Preferences

Para crear o modificar un Shared Preference llamamos al meacutetodogetSharedPreferences del contexto de la aplicacioacuten pasando como paraacutemetro elidentificador del conjunto de valores de preferencias con el que queremos trabajar Pararealizar la modificacioacuten del valor de un Shared Preference hacemos uso de la claseSharedPreferencesEditor Para obtener el objeto editor invocamos el meacutetodo edit

del objeto Shared Preferences que queremos modificar Los cambios se guardan medianteel meacutetodo commit Veamos un ejemplo

int modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences = getSharedPreferences(Mispreferencias modo)

Obtenemos un editor para modificar las preferenciasSharedPreferencesEditor editor = misSharedPreferencesedit()

Guardamos nuevos valores en el objeto SharedPreferenceseditorputBoolean(isTruetrue)editorputFloat(unFloat10)editorputInt(numeroEntero12)editorputLong(otroNumero2)editorputString(unaCadenavalor)

Guardamos los cambioseditorcommit()

Persistencia

19Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

312 Leer Shared Preferences

Para leer Shared Preferences utilizamos el meacutetodo getSharedPreferences tal como seha comentado anteriormente Pasamos como paraacutemetro el identificador del conjunto deShared Preferences al que deseamos acceder Una vez hecho esto ya podemos hacer usode meacutetodos de tipo get para acceder a preferencias individuales Hay diferentes meacutetodosget para diferentes tipos de datos Cada uno de ellos requiere como paraacutemetro la claveque identifica la preferencia cuyo valor deseamos obtener y un valor por defecto el cualse usaraacute en el caso concreto en el que no existiera la preferencia con la clave pasada comoprimer paraacutemetro Podemos ver un ejemplo a continuacioacuten

public static String MIS_PREFS = MIS_PREFS

public void cargarPreferences() Obtener las preferencias almacenadasint modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences =

getSharedPreferences(MIS_PREFS modo)

boolean isTrue = misSharedPreferencesgetBoolean(isTruefalse)float unFloat = misSharedPreferencesgetFloat(unFloat0)int numeroEntero = misSharedPreferencesgetInt(numeroEntero0)long otroNumero = misSharedPreferencesgetLong(otroNumero0)String unaCadena = misSharedPreferencesgetString(unaCadena)

313 Interfaces para Shared Preferences

Android proporciona una plataforma basada en XML para la creacioacuten de interfacesgraacuteficas para el manejo de preferencias Estas interfaces tendraacuten un aspecto similar al delas del resto de aplicaciones del sistema Al utilizarla nos estaremos asegurando de quenuestras actividades para el manejo de preferencias seraacuten consistentes con las del resto deaplicaciones del sistema De esta forma los usuarios estaraacuten familiarizados con el manejode esta parte de nuestra aplicacioacuten

La plataforma consiste en tres elementos

bull Layout de la actividad de preferencias se trata de un archivo XML que definiraacute lainterfaz graacutefica de la actividad que muestre los controles para modificar los valores delas preferencias Permite especificar las vistas a mostrar los posibles valores que sepueden introducir y a queacute clave de Shared Preferences se corresponde cada vista

bull Actividad de preferencias una subclase de PreferenceActivity que secorresponderaacute con la pantalla de preferencias de nuestra aplicacioacuten

bull Shared Preference Change Listener una implementacioacuten de la claseonSharedPreferenceChangeListener que actuaraacute en el caso en el que cambie elvalor de alguna Shared Preference

314 Definiendo una pantalla de preferencias con un layout en XML

Persistencia

20Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esta es sin duda la parte maacutes importante de una actividad de preferencias Se trata de unarchivo XML utilizado para definir varios aspectos de dicha actividad Al contrario queen el caso de los archivos de layout para interfaces graacuteficas que vimos en la sesioacutencorrespondiente del moacutedulo de Android estos archivos de layout se guardan en la carpetaresxml de los recursos de la aplicacioacuten

Aunque conceptualmente estos layouts son similares a los utilizados para definirinterfaces graacuteficas los layouts de actividades de preferencias utilizan un conjuntoespecializado de componentes especiacuteficos para este tipo de actividades Estaacuten pensadospara proporcionar interfaces con un aspecto similar al del resto de actividades depreferencias del sistema Estos controles se describen en maacutes detalle en la siguienteseccioacuten

Los layout de preferencias contendraacuten un elemento PreferenceScreen

ltxml version=10 encoding=utf-8gtltPreferenceScreen

xmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceScreengt

Se pueden antildeadir maacutes elementos de este tipo si se desea Al hacerlo eacutestos serepresentaraacuten como un elemento seleccionable en un listado que mostraraacute una nuevaventana de preferencias en caso de que sea seleccionado

Dentro de cada elemento PreferenceScreen podemos incluir tantos elementos de tipoPreferenceCategory y elementos especiacuteficos para antildeadir controles como se desee Loselementos PreferenceCategory son utilizados para dividir cada pantalla de preferenciasen subcategoriacuteas mediante una barra de tiacutetulo Su sintaxis es la siguiente

ltPreferenceCategoryandroidtitle=My Preference Categorygt

ltPreferenceCategorygt

Una vez dividida la pantalla en subcategoriacuteas tan soacutelo quedariacutea antildeadir los elementoscorrespondientes a los controles especiacuteficos que veremos en la siguiente seccioacuten Losatributos de los elementos XML para estos controles pueden variar aunque existe unconjunto de atributos comunes

bull androidkey la clave de Shared Preference que se usaraacute para guardar el valor delcontrol

bull androidtitle texto a mostrar para describir la preferenciabull androidsummary una descripcioacuten maacutes larga a mostrar debajo del texto definido con

el campo anteriorbull androiddefaultValue el valor por defecto que se mostraraacute inicialmente y tambieacuten

el que finalmente se guardaraacute si ninguacuten otro valor fue asignado a este campo

El siguiente listado muestra una pantalla de preferencias muy simple con una uacutenicacategoriacutea y un uacutenico control (un check box)

ltxml version=10 encoding=utf-8gt

Persistencia

21Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltPreferenceScreenxmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceCategory

androidtitle=Mi categoriacuteagtltCheckBoxPreference

androidkey=MI_CHECK_BOXandroidtitle=Preferencia Check Boxandroidsummary=Descripcioacuten de la preferencia

Check BoxandroiddefaultValue=true

gtltPreferenceCategorygt

ltPreferenceScreengt

Esta pantalla de preferencias se mostrariacutea de la siguiente forma

Ejemplo sencillo de ventana de preferencias

315 Controles nativos para preferencias

Android incluye diferentes controles que podemos antildeadir a nuestras ventanas depreferencias por medio de los elementos XML que se indican a continuacioacuten

bull CheckBoxPreference un check box estaacutendar que puede ser utilizado parapreferencias de tipo booleano

bull EditTextPreference permite al usuario introducir una cadena como valor para unapreferencia Al seleccionar el texto se mostraraacute un diaacutelogo con el que introducir elnuevo valor

bull ListPreference el equivalente a un Spinner Seleccionar este elemento de lainterfaz mostraraacute un diaacutelogo con las diferentes opciones que es posible seleccionar Seutilizan arrays para asociar valores y textos a las opciones de la lista

bull RingtonePreference un tipo especiacutefico de preferencia de tipo listado que permiteseleccionar entre diferentes tonos de teleacutefono Esta opcioacuten es particularmente uacutetil en

Persistencia

22Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

el caso en el que se esteacute creando una pantalla de preferencias para configurar lasopciones de notificacioacuten de una aplicacioacuten o componente de la misma

Tambieacuten es posible crear nuestros propios controles personalizados definiendo unasubclase de Preference (o cualquiera de sus subclases) Se puede encontrar maacutesinformacioacuten en la documentacioacuten de AndroidhttpdeveloperandroidcomreferenceandroidpreferencePreferencehtml

316 Actividades de preferencias

Para mostrar una pantalla de preferencias debemos definir una subclase dePreferenceActivity

public class MisPreferencias extends PreferenceActivity

La interfaz graacutefica de la interfaz se puede crear a partir del layout en el meacutetodo onCreatemediante una llamada a addPreferencesFromResource

Overridepublic void onCreate(Bundle savedInstaceState)

superonCreate(savedInstanceState)addPreferencesFromResource(Rxmlmilayout)

Como en el caso de cualquier otra actividad la actividad de preferencias que hayamosdefinido debe ser incluida en el Manifest de la aplicacioacuten

ltactivity androidname=MisPreferenciasandroidlabel=Mis preferenciasgt

ltactivitygt

Eso es todo lo que necesitamos para crear este tipo de actividades y mostrar una ventanacon las preferencias definidas en el layout Esta actividad puede ser iniciada comocualquier otra mediante un Intent ya sea a traveacutes de una llamada a startActivity obien a startActivityForResult

Intent i = new Intent(this MisPreferenciasclass)startActivityForResult(i MOSTRAR_PREFERENCIAS)

Los valores para las diferentes preferencias de la actividad son almacenadas en elcontexto de la aplicacioacuten Esto permite a cualquier componente de dicha aplicacioacutenincluyendo a cualquier actividad y servicio de la misma acceder a los valores tal como semuestra en el siguiente coacutedigo de ejemplo

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto) PENDIENTE hacer uso de meacutetodos get para obtener los valores

317 Shared Preference Change Listeners

Persistencia

23Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El uacuteltimo elemento que nos queda por examinar en esta plataforma de Shared Preferenceses la interfaz onSharedPreferenceChangeListener que es utilizada para invocar unevento cada vez que una preferencia es antildeadida eliminada o modificada Para registrarListeners usamos un coacutedigo como el que se muestra a continuacioacuten en el que la parte maacutesimportante es sin duda el meacutetodo onSharedPreferenceChanged dentro del cualdeberemos determinar queacute preferencia ha cambiado de estado a partir de sus paraacutemetrospara llevar a cabo las acciones oportunas

public class MiActividad extends Activity implementsOnSharedPreferenceChangeListener

Overridepublic void onCreate(Bundle SavedInstanceState)

Registramos este objetoOnSharedPreferenceChangeListener

Context contexto = getApplicationContext()SharedPreferences prefs =

PreferenceManagergetDefaultSharedPreferences(contexto)prefsregisterOnSharedPreferenceChangeListener(this)

public void onSharedPreferenceChanged(SharedPreferences prefsString clave)

PENDIENTE comprobar queacute clave concreta ha cambiado ysu nuevo valor

para modificar la interfaz de una actividad o elcomportamiento de

alguacuten componente

Otra forma maacutes sencilla de controlar los cambios en las preferencias es registrar unlistener en el constructor de la actividad principal o aquella que haga uso de los valores depreferencias de la forma

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto)

mListener = new OnSharedPreferenceChangeListener() public void onSharedPreferenceChanged(SharedPreferences

sharedPreferences String key)

if( keyequals(MICLAVE) )boolean valor =

sharedPreferencesgetBoolean(MICLAVE false)

prefsregisterOnSharedPreferenceChangeListener(mListener)

NotaEn este segundo ejemplo mListener es una variable miembro de la clase Para este caso esnecesario registrar el listener de esta forma porque sino el garbage collector lo eliminariacutea (debidoa particularidades en la programacioacuten de los listeners asociados a las SharedPreferences)

Persistencia

24Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

32 Proveedores de contenidos

Los proveedores de contenidos o ContentProvider proporcionan una interfaz parapublicar y consumir datos identificando la fuente de datos con una direccioacuten URI queempieza por content Son una forma maacutes estaacutendar que los adaptadores (como el quevimos en la sesioacuten anterior en la seccioacuten dedicada a SQLite) de desacoplar la capa deaplicacioacuten de la capa de datos

Podemos encontrar dos tipos principales de proveedores de contenidos en Android Losproveedores nativos son proveedores que el propio sistema nos proporciona para poderacceder a datos del dispostivo incluyendo audio viacutedeo imaacutegenes informacioacuten personaletc Por otra parte tambieacuten es posible que creemos nuestros propios proveedores con talde permitir a nuestra aplicacioacuten compartir datos con el resto del sistema En esta sesioacutenhablaremos de ambos tipos

321 Proveedores nativos

Android incluye una serie de proveedores de contenidos que nos pueden proporcionarinformacioacuten de diferentes tipos informacioacuten sobre nuestros contactos informacioacuten delcalendario e incluso ficheros multimedia Ejemplos de estos proveedores de contenidosnativos son el Browser CallLog ContactsContract MediaStore Settings y elUserDictionary Para poder hacer uso de estos proveedores de contenidos y acceder alos datos que nos proporcionan debemos antildeadir los permisos adecuados al ficheroAndroidManifestxml Por ejemplo para acceder al listiacuten telefoacutenico podemos antildeadir elpermiso correspondiente de la siguiente forma

ltuses-sdk androidminSdkVersion=8 gtltuses-permission androidname=androidpermissionREAD_CONTACTSgt

ltmanifestgt

Para obtener informacioacuten de un ContentProvider debemos hacer uso del meacutetodo query

de la clase ContentResolver El primero de los paraacutemetros de dicho meacutetodo seraacute la URIdel proveedor de contenidos al que deseamos acceder Este meacutetodo devuelve un cursorque nos permitiraacute iterar entre los resultados tal como se vio en la sesioacuten anterior en elapartado de cursores

ContentResolverquery(Uri uriString[] projectionString selectionString[] selectionArgsString sortOrder)

Por ejemplo la siguiente llamada nos proporcionaraacute un cursor que nos permitiraacute acceder atodos los contactos de nuestro teleacutefono moacutevil Para ello hemos hecho uso de la constante

Persistencia

25Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ContactsContractContactsCONTENT_URI que almacena la URI del proveedor decontenidos correspondiente Al resto de paraacutemetros se le ha asignado el valor null

ContentResolver cr = getContentResolver()Cursor cursor = crquery(ContactsContractContactsCONTENT_URI

null null null null)

NotaEn versiones anteriores a Android 20 las estructuras de datos de los contactos son diferentes y nose accede a esta URI

Una vez obtenido el cursor es posible mapear sus campos con alguacuten componente de lainterfaz graacutefica de la actividad De esta forma cualquier cambio que se produzca en elcursor se reflejaraacute automaacuteticamente en el componente graacutefico sin que sea necesario queprogramemos el coacutedigo que lo refresque Para esto tenemos que llamar al meacutetodosetNotificationUri del cursor con lo cual nos aseguraremos de que los listeners seraacutennotificados cuando se produzca alguacuten cambio en los datos referenciados por dicho cursor

cursorsetNotificationUri(crContactsContractContactsCONTENT_URI)

Para asignar un adaptador a una lista de la interfaz graacutefica de la actividad maacutesconcretamente una ListView utilizamos el meacutetodo setAdapter(Adapter)

ListView lv = (ListView)findViewById(RidListView01)SimpleCursorAdapter adapter = new SimpleCursorAdapter(

getApplicationContext()Rlayouttextviewlayoutcursornew String[]

ContactsContractContacts_IDContactsContractContactsDISPLAY_NAME

new int[]RidTextView1RidTextView2)

lvsetAdapter(adapter)

En este ejemplo los identificadores RidTextView1 y RidTextView2 se correspondencon views del layout que define cada fila de la ListView como se puede ver en elarchivo textviewlayoutxml que tendriacuteamos que crear

ltxml version=10 encoding=utf-8gt

ltLinearLayout xmlnsandroid=httpschemasandroidcomapkresandroidandroidorientation=horizontalandroidlayout_width=fill_parentandroidlayout_height=wrap_contentgt

ltTextView androidid=+idTextView1androidtextStyle=bold

Persistencia

26Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

androidems=2androidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltTextView androidid=+idTextView2androidtextStyle=boldandroidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltLinearLayoutgt

La lista en siacute (identificada por RidListView01 en el presente ejemplo) estariacutea en otroXML layout por ejemplo en mainxml

Se debe tener en cuenta que en el caso de este ejemplo si quisieacuteramos obtener losnuacutemeros de teleacutefono de cada uno de nuestros contactos tendriacuteamos que recorrer el cursorobtenido a partir del proveedor de contenidos y por cada persona en nuestra agenda crearun nuevo cursor que recorriera los teleacutefonos ya que una persona puede tener asignadosvarios teleacutefonos

Puedes acceder a un listado completo de los proveedores de contenidos nativos existentesen Android consultando su manual de desarrollo Concretamente leyendo la seccioacutendedicada al paquete androidprovider enhttpdeveloperandroidcomreferenceandroidproviderpackage-summaryhtml

322 Proveedores propios crear un nuevo proveedor de contenidos

Para acceder a nuestras fuentes de datos propias de manera estaacutendar nos interesaimplementar nuestros propios ContentProvider Gracias a que proporcionamos nuestrosdatos a partir de una URI quien use nuestro proveedor de contenidos no deberaacutepreocuparse de doacutende provienen los datos indicados por dicha URI podriacutean provenir deficheros locales en la tarjeta de memoria de un servidor en Internet o de una base dedatos SQLite Dada una URI nuestro proveedor de contenidos deberaacute proporcionar en suinterfaz los meacutetodos necesarios para realizar las operaciones baacutesicas en una base de datosinsercioacuten lectura actualizacioacuten y borrado

Para crear un nuevo proveedor de contenidos definimos una subclase deContentProvider Utilizaremos una sobrecarga del meacutetodo onCreate para crear einicializar la fuente de datos que queramos hacer puacuteblica a traveacutes de dicho proveedor Unesqueleto de subclase de ContentProvide podriacutea ser el siguiente

public class MiProveedor extends ContentProvider

Overridepublic boolean onCreate()

Inicializar la base de datosreturn true

Dentro de la clase deberemos crear un atributo puacuteblico y estaacutetico de nombreCONTENT_URI en el cual almacenaremos el URI completo del proveedor que estamos

Persistencia

27Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creando Este URI debe ser uacutenico por lo que una buena idea puede ser tomar como baseel nombre de paquete de nuestra aplicacioacuten La forma general de un URI es la siguiente

content[NOMBRE_PAQUETE]provider[NOMBRE_APLICACION][RUTA_DATOS]

Por ejemplo

contentesuajtechandroidprovidermiaplicacionelementos

En general (y por simplicidad) no es necesario antildeadir la cadena provider al nombre delpaquete por lo que podemos utilizar directamente el nombre del paquete seguido de laruta de datos de la forma

contentesuajtechandroidmiaplicacionelementos

Estos URIs se pueden presentar en dos formas La URI anterior representa una consultadirigida a obtener todos los valores de ese tipo (en este caso todos los elementos) Si a laURI anterior antildeadimos un sufijo [NUMERO_FILA] estamos representando una consultadestinada a obtener un uacutenico elemento (por ejemplo el quinto elemento en el siguienteejemplo)

contentesuajtechandroidprovidermiaplicacionelementos5

NotaSe considera una buena praacutectica de programacioacuten el proporcionar acceso a nuestro proveedor decontenidos utilizando cualquiera de estas dos formas

La forma maacutes simple de determinar cuaacutel de las dos formas anteriores de URI se utilizoacutepara hacer una peticioacuten a nuestro proveedor de contenidos es mediante un Uri MatcherCrearemos y configuramos un elemento Uri Matcher para analizar una URI y determinara cuaacutel de las dos formas se corresponde En el siguiente coacutedigo se muestra el coacutedigobaacutesico que deberemos emplear para implementar este patroacuten

public class MiProveedor extends ContentProvider

private static final String miURI =contentesuajtechandroidprovidermiaplicacionelementos

public static final Uri CONTENT_URI = Uriparse(miUri)

Creamos las constantes que nos van a permitir diferenciar entre los dos tipos distintos de peticiones a partir de la URIprivate static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

Inicializamos el objeto UriMatcher Una URI que termine en elementos se corresponderaacute con una consulta para todas las filas mientras que una URI con el sufijo elementos[FILA] representaraacute una uacutenica filastatic

uriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidprovidermiaplicacion

elementos TODAS_FILAS)

Persistencia

28Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

uriMatcheraddURI(esuajtechandroidprovidermiaplicacionelementos UNA_FILA)

Overridepublic boolean onCreate()

PENDIENTE inicializar la base de datosreturn true

Esta misma teacutecnica se puede emplear para proporcionar diferentes alternativas de URIpara diferentes subconjuntos de datos como por ejemplo diferentes tablas en una base dedatos a traveacutes del mismo proveedor de contenidos

323 Proveedores propios crear la interfaz de consultas

Para permitir realizar operaciones con los datos publicados a traveacutes de nuestro proveedorde contenidos deberemos implementar los meacutetodos delete insert update y query

para el borrado insercioacuten actualizacioacuten y realizacioacuten de consultas respectivamenteEstos meacutetodos son la interfaz estaacutendar utilizada con proveedores de contenidos de formaque para compartir datos entre aplicaciones no se tendraacuten interfaces distintos paradiferentes fuentes de datos

Lo maacutes habitual suele ser implementar un proveedor de contenidos como un mecanismopara permitir el acceso a una base de datos SQLite privada a una aplicacioacuten aunque atraveacutes de estos meacutetodos indicados se podriacutea en principio acceder a cualquier fuente dedatos

El siguiente coacutedigo extiende el ejemplo anterior (MiProveedor) y muestra el esqueleto deestos meacutetodos dentro de una subclase de ContentProvider Obseacutervese que se hace usodel objeto UriMatcher para determinar que tipo de consulta se estaacute realizando

Overridepublic Cursor query(Uri uri

String[] projectionString selectionString[] selectionArgsString sort)

Si se trata de una consulta para obtener una uacutenica filalimitamos

los resultados a obtener de la fuente de datos por medio de una claacuteusula whereswitch(UriMatchermatch(uri))

case UNA_FILA PENDIENTE modifica la sentencia SELECT

mediante una claacuteusula where obteniendo el identificador de fila

como numeroFila = urigetPathSegments()get(1))

return null

Override

Persistencia

29Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public Uri insert(Uri _uri ContentValues _valores) long idFila = [ Antildeadir un nuevo elemento ]

Devolvemos la URI del elemento recieacuten antildeadidoif (idFila gt 0)

return ContentUriswithAppendendId(CONTENT_URI idFila)

throw new SQLException(No se pudo antildeadir un nuevo elemento a +_uri)

Overridepublic int delete(Uri uri String where String[] whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

Overridepublic int update(Uri uri ContentValues valores String where String[]whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

324 Proveedores propios tipo MIME

El uacuteltimo paso para crear un proveedor de contenidos es definir el tipo MIME queidentifica el tipo de datos que devuelve el proveedor Debemos sobrecargar el meacutetodogetType para que devuelva una cadena que identifique nuestro tipo de datos de manerauacutenica Se deberiacutean definir dos tipos posibles uno para el caso de una uacutenica fila y otro parael caso de devolver todos los resultados Se debe utilizar una sintaxis similar a la delsiguiente ejemplo

bull Un uacutenico elementovndesuajtechandroidmiaplicacioncursoritemmiproveedorcontent

bull Todos los elementosvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

Como se puede observar la cadena comienza siempre por vnd A continuacioacutentendriacuteamos el nombre del paquete de nuestra aplicacioacuten Lo siguiente es cursor ya quenuestro proveedor de contenidos estaacute devolviendo datos de tipo Cursor Justo antes de labarra pondremos item en el caso del tipo MIME para un uacutenico elemento y dir para elcaso del tipo MIME para todos los elementos Finalmente tras la barra pondremos elnombre de nuestra clase (en minuacutesculas) seguido de content En el siguiente coacutedigo deejemplo se muestra coacutemo sobrecargar getType seguacuten la URI

Persistencia

30Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Overridepublic String getType(Uri _uri)

switch (uriMatchermatch(_uri)) case TODAS_FILAS

returnvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

case UNA_FILAreturn

vndesuajtechandroidmiaplicacioncursoritemmiproveedorcontentdefault

throw new IllegalArgumentException(URI nosoportada + _uri)

325 Proveedores propios registrar el proveedor

No debemos olvidar incorporar nuestro proveedor de contenidos al Manifest de laaplicacioacuten una vez que lo hemos completado Esto se haraacute por medio del elementoprovider cuyo atributo authorities podremos utilizar para especificar la URI base talcomo se puede ver en el siguiente ejemplo

ltprovider androidname=MiProveedorandroidauthorities=esuajtechandroidmiaplicaciongt

326 Content Resolvers

Aunque en la seccioacuten de proveedores nativos ya se ha dado alguacuten ejemplo de coacutemo haceruso de un Content Resolver para realizar una consulta ahora que hemos podido observaren detalle la estructura de un proveedor de contenidos propio es un buen momento paraver en detalle coacutemo realizar cada una de las operaciones que eacutestos permiten

El contexto de una aplicacioacuten incluye siempre una instancia de la claseContentResolver a la cual se puede acceder mediante el meacutetodo getContentResolver

ContentResolver cr = getContentResolver()

Esta clase incorpora diversos meacutetodos para realizar consultas a proveedores decontenidos Cada uno de estos meacutetodos acepta como paraacutemetro una URI que indica conqueacute proveedor de contenidos se desea interactuar

Las consultas realizadas sobre un proveedor de contenidos recuerdan a las consultasrealizadas sobre bases de datos Los resultados de las consultas se devuelven comoobjetos de la clase Cursor Se pueden extraer valores del cursor de la misma forma en laque se explicoacute en el apartado de bases de datos de la sesioacuten anterior El meacutetodo query dela clase ContentResolver acepta los siguientes paraacutemetros

bull La URI del proveedor del contenidos al que se desea realizar la consultabull Una proyeccioacuten que enumera las columnas que se quieren incluir en los resultados

obtenidosbull Una claacuteusula where que define las filas a obtener Se pueden utilizar comodines que

Persistencia

31Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

se reemplazaraacuten por valores incluidos en el siguiente paraacutemetrobull Una matriz de cadenas que se pueden utilizar para reemplazar los comodines en la

claacuteusula where anteriorbull Una cadena que describa la ordenacioacuten de los elementos devueltos

El siguiente coacutedigo muestra un ejemplo

ContentResolver cr = getContentResolver() Devolver todas las filasCursor todasFilas = crquery(MiProveedorCONTENT_URI null null nullnull) Devolver todas columnas de las filas para las que la columna 3 tiene un valor determinado ordenadas seguacuten el valor de la columna 5String where = COL3 + = + valorString orden = COL5Cursor algunasFilas = crquery(MiProveedorCONTENT_URI null where nullorden)

327 Otras operaciones con Content Resolvers

Para realizar otro tipo de operaciones con los datos de un proveedor de contenidosharemos uso de los meacutetodos delete update e insert de un objeto ContentResolver

Con respecto a la insercioacuten la clase ContentResolver proporciona en su interfaz dosmeacutetodos diferentes insert y bulkInsert Ambos aceptan como paraacutemetro la URI delproveedor de contenidos pero mientras que el primer meacutetodo tan solo toma comoparaacutemetro un objeto de la clase ContentValues (ya vistos en la seccioacuten de bases dedatos) el segundo toma como paraacutemetro una matriz de elementos de este tipo El meacutetodoinsert devolveraacute la URI del elemento recieacuten antildeadido mientras que bulkInsert

devolveraacute el nuacutemero de filas correctamente insertadas

Obtener el Content ResolverContentResolver cr = getContentResolver()

Crear una nueva fila de valores a insertarContentValues nuevosValores = new ContentValues()

Asignar valores a cada columnanuevosValoresput(NOMBRE_COLUMNA nuevoValor)[ Repetir para cada columna ]

Uri miFilaUri = crinsert(MiProveedorCONTENT_URI nuevosValores)

Insertamos ahora una matriz de nuevas filasContentValues[] arrayValores = new ContentValues[5] PENDIENTE rellenar el array con los valores correspondientesint count = crbulkInsert(MiProveedorCONTENT_URI arrayValores)

Para el caso del borrado hacemos uso del meacutetodo delete del Content Resolver pasandocomo paraacutemetro la URI de la fila que deseamos eliminar de la fuente de datos Otraposibilidad es incluir una claacuteusula where para eliminar muacuteltiples filas Ambas teacutecnicas semuestran en el siguiente ejemplo

ContentResolver cr = getContentResolver()

Persistencia

32Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Eliminar una fila especiacuteficacrdelete(miFilaUri null null) Eliminar las primeras cinco filasString where = _id lt 5crdelete(MiProveedorCONTENT_URI where null)

Una actualizacioacuten se lleva a cabo mediante el meacutetodo update del objeto ContentResolver Este meacutetodo toma como paraacutemetro la URI del proveedor de contenidosobjetivo un objeto de la clase ContentValues que empareja nombres de columna convalores actualizados y una claacuteusula where que indica queacute filas deben ser actualizadas Elresultado de la actualizacioacuten seraacute que en cada fila en la que se cumpla la condicioacuten de laclaacuteusula where se cambiaraacuten los valores de las columnas especificados por el objeto de laclase ContentValues tambieacuten se devolveraacute el nuacutemero de filas correctamenteactualizadas A continuacioacuten se muestra un ejemplo

ContentValues nuevosValores = new ContentValues()

Utilizamos el objeto ContentValues para especificar en queacute columnas queremos que se produzca un cambio y cuaacutel debe ser el nuevo valor de dichas columnasnuevosValuesput(NOMBRE_COLUMNA nuevoValor)

Aplicamos los cambios a las primeras cinco filasString where = _id lt 5

getContentResolver()update(MiProveedorCONTENT_URI nuevosValores wherenull)

Persistencia

33Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

4 Ejercicios - Persistencia en Android proveedores de contenidos ySharedPreferences

41 Compartir datos entre actividades con Shared Preferences (075 puntos)

Descarga de las plantillas el proyecto Dni Dicho proyecto estaacute compuesto de dosactividades Formulario y Resumen El objetivo del ejercicio es conseguir que al pulsar elbotoacuten Siguiente en la actividad Formulario se muestre en la actividad Resumen cuaacutelesfueron los datos introducidos

En este ejercicio vamos a hacer uso de SharedPreferences para pasar los datos de unaactividad a la siguiente Al pulsar en Siguiente en Formulario deberemos guardar el valorde todos los campos mediante esta plataforma Al mostrarse la actividad Resumendeberemos leer estos datos tambieacuten a partir de SharedPreferences para mostrarlos porpantalla

Por uacuteltimo crea un meacutetodo que valide el DNI (debe tratarse de una secuencia de ochodiacutegitos entre 0 y 9 y una letra al final) Si se pulsa Siguiente y el DNI no tiene el formatocorrecto no se deberaacute pasar a la actividad Resumen En lugar de esto se mostraraacute unToast en pantalla con un mensaje de error

42 Actividad de preferencias (075 puntos)

En este ejercicio seguimos trabajando con el proyecto del ejercicio anterior Vamos aantildeadir un menuacute de opciones que permita escoger en queacute momento se comprobaraacute lavalidez del DNI Para ello antildeade en primer lugar un menuacute a la actividad Formulario conuna uacutenica opcioacuten cuyo nombre seraacute Opciones

Al seleccionar esta opcioacuten se deberaacute mostrar una actividad de preferencias cuyo aspectoseraacute el siguiente

Persistencia

34Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Preferencias de la aplicacioacuten Dni

El significado de las opciones seraacute el siguiente

bull En el campo con esta opcioacuten seleccionada al pulsar sobre el campo de DNI en elformulario deberaacute mostrarse un Toast indicando si la sintaxis del DNI es correcta ono

bull Al pulsar si se desactiva esta opcioacuten (que deberaacute estar activada por defecto) no seharaacute la comprobacioacuten del DNI al pulsar el botoacuten Siguiente por lo que siempre seraacuteposible pasar de la actividad Formulario a la actividad Resumen sea cual sea elformato del DNI introducido

bull El uacuteltimo check box se encontraraacute siempre deshabilitado iquestQueacute atributo debemosutilizar para conseguir esto

Crea dos variables booleanas llamadas enElCampo y alPulsar Su valor dependeraacute delestado de los checkboxes anteriores y se actualizaraacute por medio del eventoonSharedPreferenceChange Utiliza el valor de estas variables en el coacutedigo de tuactividad para que esta tenga el comportamiento indicado

43 Proveedor de contenidos propio (075 puntos)

Vamos a implementar otra forma de acceder a la base de datos de usuarios de laaplicacioacuten BaseDatos desarrollada durante los ejercicios de la sesioacuten anterior siguiendoesta vez el patroacuten de disentildeo ContentProvider de Android Seguiremos trabajando puescon dicho proyecto

bull Creamos una nueva clase llamada UsuariosProvider que herede deContentProvider Esto nos obligaraacute a sobrecargar una serie de meacutetodos abstractosAntes de implementar la query vamos a configurar el provider

bull Antildeadimos algunos campos tiacutepicos de los content provider

Persistencia

35Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public static final Uri CONTENT_URI =Uriparse(contentesuajtechandroidbasedatosusuarios)

private static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

staticuriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

TODAS_FILAS)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

UNA_FILA)

bull Vamos a acceder a la misma base de datos de usuarios utilizada en los ejercicios de lasesioacuten anterior pero no vamos a hacerlo a traveacutes del adaptador que tuvimos queimplementar sino que vamos a copiar de eacutel el coacutedigo que nos haga falta Copia loscampos que definen el nombre de la base de datos de la tabla de las columnas laversioacuten asiacute como la referencia al contexto y a la base de datos La sentenciacompilada del insert ya no va a hacer falta Inicializa los valores necesarios en elconstructor Para inicializar la referencia a la base de datos vamos a utilizar una vezmaacutes MiOpenHelper Podemos copiarlo del adaptador que ya implementamos en losejercicios de la sesioacuten anterior

bull Implementa de forma apropiada el getType para devolver un tipo MIME diferenteseguacuten si se trata de una URI de una fila o de todas las filas Para ello ayuacutedate deluriMatcher

bull Implementa el meacutetodo query Simplemente se trata de devolver el cursor queobtenemos al hacer una consulta a la base de datos SQLite Algunos de los paraacutemetrosque le pasaremos los recibimos como paraacutemetros del meacutetodo del provider Los que notengamos iraacuten con valor null

bull Aunque no tenemos todos los meacutetodos del UsuariosProvider implementadospodemos probarlo Para ello debemos registarlo en el AndroidManifestxml

ltprovider androidname=UsuariosProvider

androidauthorities=esuajtechandroidbasedatosgtltapplicationgt

ltmanifestgt

bull En la clase Main se antildeadioacute codigo para insertar una serie de valores en la base dedatos y mostrarlos en el campo de texto Manteniendo este coacutedigo vamos a antildeadir alcampo de texto el resultado obtenido con la consulta del UsuariosProvider paracomprobar que tanto el adaptador de la base de datos como el proveedor nosdevuelven el mismo resultado

44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)

Porque es la forma estaacutendar que establece Android de acceder a contenidos Ademaacutes elproveedor de contenidos nos permitiraacute notificar al ContentResolver de los cambiosocurridos Asiacute componentes en la pantalla podraacuten refrescarse de forma automaacutetica

Persistencia

36Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Utiliza el proyecto ProveedorContenidos de las plantillas Implementa la insercioacuten enel proveedor de contenidos Prueacutebala insertando algunos usuarios de ejemplo en laclase Main Implementa tambieacuten el OnClickListener del botoacuten que inserta nuevosusuarios El nombre del nuevo usuario iraacute indicado en el EditText

bull Comprueba que la insercioacuten funciona y que gracias a la siguiente liacutenea y al estarusando un proveedor de contenidos la lista se actualiza automaacuteticamente cuandoocurre alguacuten cambio sin necesidad de pedir expliacutecitamente la actualizacioacuten al pulsarel botoacuten

cursorsetNotificationUri(cr UsuariosProviderCONTENT_URI)

NotaEsto no va a funcionar si se notifica que se ha producido un cambio al insertar o borrar unelemento Para ello usamosgetContext()getContentResolver()notifyChange(UsuariosProviderCONTENT_URI null)

bull Implementa el meacutetodo delete del proveedor de contenidos Prueacutebalo en la claseMain Termina de implementar el onCreateContextMenuListener que se ejecutaraacutecada vez que se haga una pulsacioacuten larga sobre alguna entrada de la lista Compruebaque funciona (eliminando el usuario correspondiente y no otro)

Persistencia

37Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

5 Persistencia de datos en iOS Ficheros y SQLite

51 Introduccioacuten

La persistencia de datos en iOS se realiza en una pequentildea memoria flash que tienenuestro dispositivo la cual es equivalente a un disco duro de tamantildeo limitado Los datosque ahiacute se almacenen se conservaraacuten aunque el dispositivo se apague Por motivos deseguridad el SO de iOS no permite que las aplicaciones accedan directamente a lamemoria interna pero a cambio existe una API completa de acceso a la porcioacuten dememoria que le corresponde a la app que desarrollemos

En una aplicacioacuten iOS encontraremos baacutesicamente 4 modos de almacenamiento deinformacioacuten de forma persistente

bull Ficheros de propiedades (plist)bull Bases de datos embebidas SQLitebull Datos de usuario (User Defaults)bull Core Data

En esta primera sesioacuten veremos coacutemo usar los ficheros de texto (Property Lists) y elalmacenamiento en base de datos de tipo SQLite

52 Ficheros plist (Property Lists)

El almacenamiento de datos en ficheros de propiedades es uno de los maacutes usados en eldesarrollo de las aplicaciones para iOS Se usa principalmente para guardar datos deconfiguracioacuten de la aplicacioacuten u otra informacioacuten poco compleja y acceder a ella deforma sencilla Si estamos desarrollando un juego podemos usarlos para la definicioacuten delos niveles almacenar las puntuaciones del jugador etc

521 Leyendo datos desde ficheros plist

Los ficheros plist (Property Lists) tienen un formato XML aunque XCode nosproporciona una interfaz que nos permite acceder a ellos de forma muy sencilla Paraentender el funcionamiento de este tipo de ficheros vamos a realizar un ejemplo en el queprimero crearemos un fichero plist lo completaremos con datos de ejemplo y despueacutes loleeremos para mostrarlo por pantalla

Un ejemplo de un fichero plist podriacutea ser el siguiente

Persistencia

38Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Fichero plist

Antes de nada creamos un proyecto nuevo en XCode de tipo Window-based

Application al que llamaremos sesion03-ejemplo1 Para crear un fichero plistdebemos hacer click derecho sobre el directorio Supporting Files de nuestro proyectoy seleccionamos iOS gt Resource gt Property List Lo guardaremos con el nombreconfigUsuario Seguidamente ya podemos acceder al fichero recieacuten creado haciendoclick sobre el en este momento XCode nos mostraraacute un pequentildeo editor totalmente vacioen el que iremos rellenando con datos Podemos verlo tambieacuten en formato XML sihacemos click derecho sobre el y seleccionamos Open As gt Preview

Para empezar a rellenar el fichero que acabamos de crear hacemos click derecho dentrode su contenido (ahora vacio) y seleccionamos Add row Entonces nos apareceraacute unafila vaciacutea Dentro del campo Key escribimos la clave un nombre descriptivo quedeberemos utilizar maacutes adelante para acceder a los datos En nuestro caso vamos aescribir config como clave Dentro de la columna Type seleccionamosDictionary Veremos que aparece una flecha en el lado izquierdo que indica quepueden haber subniveles dentro de nuestro diccionario

Ahora antildeadimos un nuevo item que cuelgue del diccionario que acabamos de crear paraello hacemos click sobre la flecha de la izquierda esta se giraraacute hacia abajo entonceshacemos click ahora sobre el siacutembolo + (maacutes) Nos apareceraacute una nueva liacutenea que cuelgadel diccionario

En esta nueva liacutenea dentro del campo key escribimos nombre el campo type lodejamos como de tipo String ya que va a ser una cadena de texto y en la columna deValue escribimos nuestro nombre por ejemplo Javi

Ahora vamos a antildeadir otro campo nuevo en nuestra configuracioacuten para ello hacemosclick sobre el siacutembolo + y nos apareceraacute una nueva liacutenea justo al mismo nivel que laanterior y colgando del diccionario Dentro del campo de Key escribimos ciudad yen Value escribimos Alicante El campo de Type lo dejamos como String

Persistencia

39Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora dentro de nuestro fichero plist vamos a indicar los dispositivos que dispone elusuario para ello creamos una nueva linea dentro del diccionario y le pondremos comoclave dispositivos Seleccionamos el tipo Array y seguimos los mismos pasosque al crear el diccionario para ello hacemos click sobre la flecha de la izquierda ydespueacutes sobre el siacutembolo + (maacutes) Ahora nos apareceraacute una nueva fila con clave Item 0Seleccionamos el tipo String y en el campo Value iPhone

Ahora repetimos el paso 3 veces indicando como values iPad Android y

Blackberry

Una vez seguidos todos estos pasos tendremos nuestro fichero de propiedades listo Debede quedar como se muestra a continuacioacuten

Fichero plist terminado

Este fichero se almacenaraacute dentro del directorio de bundle cuando compilemos laaplicacioacuten En el caso de que queramos escribir en el debermos almacenar el ficherodentro del directorio de Documents del binario

Ahora desde nuestro coacutedigo vamos a acceder a eacutel y a mostrarlo por pantalla para elloescribimos el siguiente coacutedigo dentro del meacutetodo didFinishLaunchingWithOptions dela clase delegada

Cargamos el fichero PLISTNSString mainBundlePath = [[NSBundle mainBundle] bundlePath]NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]NSDictionary diccionario = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionario delraiz

NSString nombre = [dicConfig objectForKeynombre]

Persistencia

40Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

[selfwindow makeKeyAndVisible]return YES

Si todo ha ido correctamente deberemos obtener la siguiente salida por la consola

Lista de dispositivos

522 Escribiendo datos en ficheros plist

Partiendo del ejemplo anterior vamos a escribir ahora en el fichero para ello primerodebemos comprobar que el fichero plist se encuentre dentro del directorio de Documents

del dispositivo en el caso de que no lo esteacute (por ejemplo si es la primera vez que arrancala aplicacioacuten) debemos copiarlo a ese directorio Una vez que tenemos el fichero dentrodel directorio de documentos ya podemos escribir en el

Para realizar todo el proceso de comprobacioacuten de la existencia del fichero dentro deldirectorio de documentos copiarlo en ese directorio y leer los datos debemos de sustituirel coacutedigo que hay dentro del meacutetodo didFinishLaunchingWithOptions de la clasedelegada por este otro

AtencioacutenPara que funcione la escritura en un fichero plist este debe de estar dentro del directorio deDocuments de nuestro dispositivo iOS no en el boundle

NSDictionary diccionarioRaiz

Ruta del directorio de documentos de nuestro dispositivoNSArray paths =

NSSearchPathForDirectoriesInDomains(NSDocumentDirectoryNSUserDomainMask YES)

if ([paths count] gt 0)

Persistencia

41Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

Primero comprobamos si existe el fichero en eldirectorio de documentos

BOOL fileExists = [[NSFileManager defaultManager]fileExistsAtPathdocumentsFilename]if (fileExists)

NSLog(Fichero encontrado en directorio de documentosOK

documentsFilename)

Si existe ya cargamos los datos desde aquidirectamente

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

else

NSLog(No se encuentra el fichero configUsuarioplisten

el directorio de documentos-gtLo creamos)

Si no existe primero cargamos el fichero PLISTdesde el Boundle

NSString mainBundlePath = [[NSBundle mainBundle]bundlePath]

NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Y despues escribimos los datos cargados (eldiccionario completo)

a un fichero nuevo de la carpeta de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

NSLog(plist de configUsuario creado)

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionariodel raiz

NSString nombre = [dicConfig objectForKeynombre]NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

Persistencia

42Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Por ultimo liberamos la memoria del diccionario raiz[diccionarioRaiz release]

[selfwindow makeKeyAndVisible]return YES

Para comprobar el funcionamiento arrancamos la aplicacioacuten y veremos que la primera vezse crearaacute el fichero dentro de la carpeta de Documents de nuestro dispositivo y en lassiguientes ya no se crearaacute y simplemente lo leeraacute desde la carpeta de Documents

Ahora que ya tenemos la gestioacuten de ficheros baacutesica completada vamos a escribir sobre elalguacuten dato Para ello simplemente escribimos el siguiente fragmento de coacutedigo justo antesde la liacutenea [selfwindow makeKeyAndVisible]

Cambiamos el nombre y lo guardamos en el fichero Ruta del directorio de documentos de nuestro dispositivo

if ([paths count] gt 0)

Cargamos el fichero desde el directorio dedocumentos del dispositivo

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cambiamos el valor del nombre en el diccionario[dicConfig setValueOtro nombre forKeynombre]

Escribimos todo el diccionario de nuevo al fichero del directorio de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

Por ultimo liberamos el diccionario de lamemoria

[diccionarioRaiz release]

Lo que hemos hecho en el coacutedigo anterior es cargar todo el diccionario de nuevo desde eldirectorio de documentos modificar los datos que queremos cambiar o incluso antildeadir ydespueacutes guardar el diccionario en el fichero

Volvemos a arrancar la aplicacioacuten y veremos que ha cambiado el campo de nombre

Nota

Persistencia

43Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

La lectura y escritura de datos en un fichero plist se debe de hacer de una soacutela vez nunca porpartes Siempre se debe de cargar o escribir un diccionario NSDictionary como elementoprincipal del fichero

53 SQLite

SQLite es una base de datos relacional ligera con la que se puede trabajar de formasencilla mediante sentencias SQL La base de datos se almacenaraacute en un fichero dentrode nuestra aplicacioacuten Este meacutetodo de persistencia puede ser el adecuado cuando los datosa almacenar esteacuten en un formato de tablas filas y columnas y se necesite que los datossean accesibles de forma raacutepida

531 Herramientas disponibles

Para explicar el funcionamiento de las librerias de SQLite de Cocoa Touch vamos a seguirun ejemplo completo Comenzamos creando la base de datos para ello podemos utilizaruno de los frontends que facilitan la gestioacuten del SQLite uno bastante sencillo yrecomendado es el SQLite Database Browser y otro es fmdb

Nosotros utilizaremos el primero (SQLite Database Browser) Abrimos la aplicacioacuten yhacemos click sobre el botoacuten de crear nueva base de datos que llamaremospersonasDBsqlite Seguidamente creamos las tablas para simplificar en nuestroejemplo crearemos una soacutela tabla llamada personas Esta tabla tendraacute las siguientescolumnas id (NUMERIC) nombre (TEXT) apellidos (TEXT) dni (TEXT) edad

(NUMERIC) y localidad (TEXT)

Persistencia

44Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Estructura de la Base de Datos

Una vez creada la tabla pasamos a insertar datos dentro de ella para ello abrimos lapestantildea de Browse Data y hacemos click en New Record Cuando tengamos al menos 3registros metidos dentro de la tabla ya podemos guardar la base de datos Hacemos clicksobre guardar le ponemos el nombre que queramos y elegimos cualquier directorio delMAC

Datos de la Base de Datos

532 Lectura de datos

Una vez que tenemos la base de datos SQLite creada ya podemos empezar a escribir elcoacutedigo para leer y mostrar los datos dentro de nuestra aplicacioacuten

Primero empezamos creando un proyecto nuevo en XCode usando la plantilla basada envistas View-based application y lo llamaremos sesion03-ejemplo2 Ahora antildeadimosel framework de sqlite a nuestro proyecto para ello hacemos click sobre el raiz delproyecto y seleccionamos la pestantildea Build Phases Desplegamos el listado de Link

Binary With Libraries y hacemos click sobre el botoacuten (+) El el listadoseleccionamos el framework libsqlite3dylib y hacemos click en Add

Persistencia

45Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Antildeadiendo el framework libsqlite3dylib

Ahora antildeadimos a nuestro proyecto la base de datos que hemos creado anteriormentepara ello arrastramos el fichero personasDBsqlite a nuestro directorio Supporting

Files Asegurate de seleccionar la opcioacuten de Copy items to destination groups

folder (if needed) para que se copie el fichero dentro de la carpeta del proyecto

Para acelerar el proceso de lectura de la base de datos y ahorrar memoria vamos a cargarprimero todos los datos en objetos para ello vamos a crear una clase que se encargue dealmacenar los datos Hacemos click derecho sobre el raiz del proyecto New file gtObjective-C class seleccionamos NSObject como clase principal y le damos a NextEscribimos como nombre de la clase Persona y hacemos click en save

Personah

imports iniciales

interface Persona NSObject int _uIdNSString _nombreNSString _apellidos

Persistencia

46Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSInteger _edadNSString _localidad

property (nonatomic assign) int uIdproperty (nonatomic copy) NSString nombreproperty (nonatomic copy) NSString apellidosproperty (nonatomic) NSInteger edadproperty (nonatomic copy) NSString localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidos edad(NSInteger)edadlocalidad(NSString )localidad

end

Personam

import Personah

implementation Persona

synthesize uId = _uIdsynthesize nombre = _nombresynthesize apellidos = _apellidossynthesize edad = _edadsynthesize localidad = _localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidosedad(NSInteger)edad localidad(NSString )localidad

if ((self = [super init])) selfuId = uIdselfnombre = nombreselfapellidos = apellidosselfedad = edadselflocalidad = localidad

return self

- (void) dealloc selfnombre = nilselfapellidos = nilselflocalidad = nil[super dealloc]

end

Una vez creada la clase Persona vamos a crear la clase encargada de manejar la basede datos sqlite3 Otra opcioacuten seriacutea cargar todos los datos directamente desde la clasedelegada al arrancar la aplicacioacuten pero no es recomendable ya que si en alguacuten momentotenemos que cambiar de motor de base de datos lo tendremos maacutes complicado

Persistencia

47Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creamos entonces una clase dentro de nuestro proyecto llamada BDPersistencia queherede de NSObject y escribimos el siguiente coacutedigo

BDPersistenciah

imports iniciales

interface BDPersistencia NSObject sqlite3 _database

+ (BDPersistencia)database- (NSArray )arrayPersonas

end

BDPersistenciam

import BDPersistenciahimport Personah

implementation BDPersistencia

static BDPersistencia _database

+ (BDPersistencia)database if (_database == nil)

_database = [[BDPersistencia alloc] init]return _database

- (id)init if ((self = [super init]))

NSString sqLiteDb = [[NSBundle mainBundle]pathForResourcepersonasDB

ofTypesqlite]

if (sqlite3_open([sqLiteDb UTF8String]amp_database) = SQLITE_OK)

NSLog(Fallo al abrir la BD)

return self

- (void)dealloc sqlite3_close(_database)[super dealloc]

- (NSArray )arrayPersonas

NSMutableArray arraySalida = [[[NSMutableArray alloc]init]

autorelease]

Persistencia

48Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString query = SELECT id nombre apellidoslocalidad edad

FROM personas ORDER BY apellidos DESC

sqlite3_stmt statementif (sqlite3_prepare_v2(_database [query UTF8String]

-1ampstatement nil)

== SQLITE_OK) while (sqlite3_step(statement) == SQLITE_ROW)

int uniqueId = sqlite3_column_int(statement0)

char nombreChar = (char )sqlite3_column_text(statement 1)

char apellidosChar = (char )sqlite3_column_text(statement 2)

char localidadChar = (char )sqlite3_column_text(statement 3)

int edad = sqlite3_column_int(statement 4)

NSString nombre = [[NSString alloc]initWithUTF8StringnombreChar]NSString apellidos = [[NSString alloc]initWithUTF8StringapellidosChar]NSString localidad = [[NSString alloc]initWithUTF8StringlocalidadChar]Persona persona = [[Persona alloc]initWithUiduniqueId nombrenombreapellidosapellidos edadedad

localidadlocalidad]

[arraySalida addObjectpersona][nombre release][apellidos release][localidad release][persona release]

sqlite3_finalize(statement)

return arraySalida

end

Con esto ya podemos leer de nuestra base de datos las personas Para ver que funcionatodo a la perfeccioacuten escribimos el siguiente coacutedigo dentro de la clase delegada en elmeacutetodo applicationDidFinishLaunching Antes debemos incluir en la parte superiorlas clases creadas

import BDPersistenciahimport Personah

NSArray personas = [BDPersistenciadatabase]arrayPersonas

for (Persona persona in personas) NSLog(d d personauId

personanombrepersonaapellidos personalocalidad personaedad)

Persistencia

49Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Si ejecutamos el proyecto obtendremos la siguiente salida por consola

Listado de personas

533 Mostrando los datos en una tabla

Una vez que tenemos el coacutedigo para obtener todas las filas de la tabla de personas de labase de datos queda lo maacutes faacutecil mostrarlas dentro de una vista de la aplicacioacuten En estecaso utilizaremos la vista de tabla UITableView

Comenzamos creando una nueva vista en nuestro proyecto para ello hacemos clickderecho sobre el raiz y seleccionamos New File gt UIVIewController SubclassAsegurate de marcar Subclass of UITableViewController y la opcoacuten de With

XIB for user interface Hacemos click en Next y guardamos el fichero comoPersonasViewController por ejemplo

Ahora abrimos el fichero PersonasViewControllerh y antildeadimos un NSArray queseraacute el que almacene las personas de la base de datos

imports iniciales

interface PersonasViewController UITableViewController

NSArray _listadoPersonas

property (nonatomic retain) NSArray listadoPersonas

end

Ahora abrimos el fichero PersonasViewControllerm antildeadimos el syntetizeimportamos los ficheros de gestioacuten de la base de datos la clase Persona y todo el coacutedigobaacutesico para la gestioacuten de la vista de la tabla

import PersonasViewControllerhimport Personahimport BDPersistenciah

implementation PersonasViewController

synthesize listadoPersonas = _listadoPersonas

Persistencia

50Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

- (id)initWithStyle(UITableViewStyle)style

self = [super initWithStylestyle]if (self)

Custom initializationreturn self

- (void)dealloc

[super dealloc]

selflistadoPersonas = nil

- (void)didReceiveMemoryWarning

Releases the view if it doesnt have a superview[super didReceiveMemoryWarning]

Release any cached data images etc that arent inuse

pragma mark - View lifecycle

- (void)viewDidLoad

[super viewDidLoad]

selftitle = Listado de personas

selflistadoPersonas = [BDPersistenciadatabase]arrayPersonas

- (void)viewDidUnload

[super viewDidUnload] Release any retained subviews of the main view eg selfmyOutlet = nil

- (void)viewWillAppear(BOOL)animated

[super viewWillAppearanimated]

- (void)viewDidAppear(BOOL)animated

[super viewDidAppearanimated]

- (void)viewWillDisappear(BOOL)animated

[super viewWillDisappearanimated]

- (void)viewDidDisappear(BOOL)animated

[super viewDidDisappearanimated]

- (BOOL)shouldAutorotateToInterfaceOrientation

Persistencia

51Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

(UIInterfaceOrientation)interfaceOrientation

Return YES for supported orientationsreturn (interfaceOrientation ==

UIInterfaceOrientationPortrait)

pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView(UITableView)tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection

(NSInteger)section

Return the number of rows in the sectionreturn [selflistadoPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath

(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]if (cell == nil)

cell = [[[UITableViewCell alloc]initWithStyleUITableViewCellStyleSubtitle

reuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona persona = (Persona )[selflistadoPersonas

objectAtIndexindexPathrow]celltextLabeltext = [NSString stringWithFormat

(d) personanombrepersonaapellidos personaedad]celldetailTextLabeltext = personalocalidad

return cell

pragma mark - Table view delegate

- (void)tableView(UITableView )tableViewdidSelectRowAtIndexPath

(NSIndexPath )indexPath

Navigation logic may go here Create and pushanother view controller

end

Persistencia

52Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora soacutelo nos queda incorporar la vista que acabamos de crear dentro de nuestraaplicacioacuten Para ello vamos a llamarla desde el Delegate y a meterla dentro de unNavigation Controller Primero debemos de antildeadir un Outlet en la clasedelegada hacia un Navigation Controller

imports iniciales

interface sesion03_ejemplo1AppDelegate NSObjectltUIApplicationDelegategt

UIWindow _windowUINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet

UINavigationController navController

end

Ahora hacemos click sobre la vista MainWindowxib y arrastramos un Navigation

Controller desde el listado de la columna de la derecha hacia la columna de laizquierda donde pone Objects Nos apareceraacute el Navigation Controller dibujadoen pantalla Ahora desplegamos el Navigation Controller y hacemos click sobre elView Controller que hay dentro Nos vamos a la pestantildea de Identity Inspector

de la columna de la derecha y escribimos dentro de Class el nombre de la vista dellistado de personas PersonasViewController

Finalmente hacemos click sobre el objeto Delegate vamos a la pestantildea de Show

Connections Inspector y arrastramos desde navController hasta el objetoNavigation Controller de la columna de la izquierda Ya tenemos los controladoresy las vistas conectadas

Una vez hecho esto nos queda indicar dentro de nuestro coacutedigo que la aplicacioacuten arranquecon el Navigation Controller para ello escribimos lo siguiente al final del meacutetododidFinishLaunchingWithOptions de la implementacioacuten de la clase delegada

selfwindowrootViewController = selfnavController[selfwindow makeKeyAndVisible]return YES

Ya estaacute listo todo y preparado para compilar Si ejecutamos el proyecto veremos que nosaparece el listado de personas en la vista de tabla

Persistencia

53Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios

61 Creando y leyendo un fichero plist (15 puntos)

En este primer ejercicio vamos a crear nuestra primera aplicacioacuten usando el meacutetodo depersistencia de ficheros La aplicacioacuten mostraraacute en una vista de tabla TableView unlistado de series en la que si seleccionamos una veremos su informacioacuten maacutes importanteen otra vista Comenzaremos creando un fichero de propiedades (plist) siguiendo unaestructura determinada y posteriormente mostraremos en una vista de tabla todos susdatos iexclComenzamos

1) Crear un proyecto usando la plantilla Master-Detail application Lo llamaremosejercicio-plist-ios como identificador de empresa escribiremos esuajtech y como prefijode clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremos UseAutomatic Reference Counting para olvidarnos de la gestioacuten de memoria El resto deopciones las dejaremos sin marcar

2) Crear un archivo de propiedades (plist) con el nombre de series

3) Editar el archivo plist creando la siguiente estructura de datos (5 series miacutenimo)

Plist series

4) Rellenar el archivo plist con datos de series reales

5) Escribir el coacutedigo necesario en UAMasterViewControllerm que recorra el archivoplist que acabamos de crear y muestre por consola los titulos de las series

6) Mostrar en la tabla que hay implementada los tiacutetulos de la serie Para ello deberemosde completar todos los meacutetodos del protocolo del TableView

7) Ejecutar la aplicacioacuten y comprobar que funciona correctamente

Persistencia

54Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

62 Implementar vista de detalle de la serie (05 puntos)

Dentro del proyecto anterior (no hace falta crear uno nuevo) vamos a mostrar toda lainformacioacuten adicional de cada una de las series en una vista de detalle Cuandoseleccionemos una fila de la tabla nos deberaacute aparecer una nueva vista con el resto deinformacioacuten (antildeo director principal sinopsis y duracioacuten)

63 Uso baacutesico de una base de datos SQLite (1 punto)

En este ejercicio vamos a crear la misma aplicacioacuten que en el ejercicio anterior perousando el meacutetodo de persistencia de SQLite Para completar el ejercicio deberemos seguirlos siguientes pasos

1) Crear un proyecto usando la plantilla Master-Detail application lo llamaremosejercicio-sqlite-ios como identificador de empresa escribiremos esuajtech y comoprefijo de clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremosUse Automatic Reference Counting para olvidarnos de la gestioacuten de memoria El restode opciones las dejaremos sin marcar

2) Nos descargamos e instalamos el programa SQLite Database Browser desde estadireccioacuten

3) Disentildear la estructura de la base de datos usando SQLite Database Browser tal y comose muestra en la imagen siguiente

Sqlite series

4) Insertar al menos 5 series en la base de datos usando el programa SQLite DatabaseBrowser

5) Cargar todos los datos de la base de datos desde el meacutetododidFinishLaunchingWithOptions de la clase UAAppDelegate Mostrar por consola losdatos cargados

6) Mostrar los datos cargados en el paso anterior en la vista de tabla UITableView quehay creada en la clase UAMasterViewController

Persistencia

55Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7) Arrancar la aplicacioacuten y comprobar que se muestran las series en la tabla de formacorrecta

Persistencia

56Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7 Persistencia de datos en iOS User Defaults y Core Data

71 Introduccioacuten

En esta sesioacuten veremos como utilizar dos nuevos meacutetodos de persistencia en iOS ambosmuy interesantes y usados muy frecuentemente

bull User Defaults Muy uacutetil para almacenar datos simples y de uso repetido dentro denuestras aplicaciones iOS

bull Core Data Meacutetodo avanzado de almacenamiento de objetos enteros dentro de lamemoria del dispositivo iOS Muy uacutetil para almacenar datos complejos conrelaciones etc

72 User Defaults

Este meacutetodo es muy uacutetil cuando queremos almacenar pequentildeas cantidades de datos comopuntuaciones informacioacuten de usuario el estado de nuestra aplicacioacuten configuracionesetc

Este meacutetodo es el maacutes raacutepido de usar ya que no requiere de una base de datos ni ficherosni ninguna otra capa intermedia Los datos se almacenan directamente en la memoria deldispositivo dentro de un objeto de la clase NSUserDefaults

Para aprender a usar este meacutetodo de almacenamiento de datos vamos a crear unaaplicacioacuten muy simple que lea un nombre y una edad de pantalla y las guarde en unobjeto NSUserDefaults Para ello seguimos los siguientes pasos

bull 1) Comenzamos creando un nuevo proyecto llamado sesion04-ejemplo1 dentrode XCode

bull 2) Abrimos la vista sesion04_ejemplo1ViewController y arrastramos dos TextField un botoacuten y dos labels quedando la vista de la siguiente manera

Persistencia

57Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista de la aplicacioacuten

bull 3) Abrimos el fichero sesion04_ejemplo1ViewControllerh y definimos el Outletdel evento onclick del botoacuten El fichero quedaraacute de la siguiente manera

imports iniciales

interface sesion04_ejemplo1ViewController UIViewController UITextField tfNombreUITextField tfEdad

property(nonatomic retain) IBOutlet UITextField tfNombreproperty(nonatomic retain) IBOutlet UITextField tfEdad

-(IBAction) guardaDatos

end

bull 4) Ahora implementamos el coacutedigo para el meacutetodo del botoacuten dentro desesion04_ejemplo1ViewControllerm

synthesize tfEdadsynthesize tfNombre

-(IBAction) guardaDatos NSLog(Guardamos los datos)

Persistencia

58Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creamos el objeto NSUserDefaultsNSUserDefaults datos = [NSUserDefaults standardUserDefaults]

guardamos el nombre (NSString)[datos setObjecttfNombretext forKeynombre]

guardamos la edad (Integer)[datos setInteger[tfEdadtext integerValue] forKeyedad]

almacenamos los datos en la memoria[datos synchronize]

bull 5) Tenemos que enlazar el botoacuten con el evento que hemos programado en el pasoanterior para ello abrimos la vista sesion04_ejemplo1ViewControllerxib y conla tecla ctlr apretada arrastramos desde el botoacuten hasta Files Owner (columna de laizquierda parte superior) y seleccionamos el meacutetodo que hemos creado(guardaDatos) De esta forma ya tenemos conectado el botoacuten

bull 6) Ahora hacemos lo mismo con los Text Field Tenemos que conectarlos alcontrolador para ello seleccionamos el objeto Files Owner y abrimos la pestantildeade conexiones (la que estaacute maacutes a la derecha en la columna de la derecha) Hacemosclick en tfEdad y arrastramos hasta el Text Field de edad Hacemos lo mismo para eltext field de nombre Ya tenemos todos los elementos de la vista conectados

Conexioacuten de Outlets

bull 7) Arrancamos la aplicacioacuten y la probamos

Persistencia

59Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Aplicacioacuten

bull 8) Ahora vamos a hacer que la aplicacioacuten cargue los datos que se han guardado alarrancar la vista para ello simplemente tenemos que escribir el siguiente coacutedigo en elmeacutetodo (void)viewDidLoad de la clase sesion04_ejemplo1ViewControllerm

- (void)viewDidLoad

[super viewDidLoad]

NSUserDefaults datos = [NSUserDefaults standardUserDefaults]

obtenemos un NSStringNSString nombre = [datos stringForKeynombre]

obtenemos un NSIntegerNSInteger edad = [datos integerForKeyedad]

if (nombre=nil ampamp edad=0)NSLog(Nombre cargado edad dnombreedad)

else

NSLog(Sin datos)

bull 9) Ya podemos arrancar de nuevo la aplicacioacuten y veremos que si escribimos cualquier

Persistencia

60Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

texto en los Text Fields estos se almacenaraacuten dentro del objeto NSUserDefaults Alapagar la aplicacioacuten y volver a arrancarla estos datos se cargaraacuten (apareceraacuten enconsola)

Consola

NotaLos datos permaneceraacuten en nuestro dispositivo hasta que se elimine la aplicacioacuten en ese casotodos los datos guardados en NSUserDefaults se borraraacuten

bull 10) Probar eliminar la aplicacioacuten del dispositivosimulador y volver arrancarComprobamos que los datos se han borrado

Consola

73 Core Data

De todas las formas de persistencia de datos que existen en iOS Core Data es la maacutesapropiada para usar en caso de manejar datos no triviales con relaciones algo maacutescomplejas entre ellos El uso de Core Data dentro de nuestra aplicacioacuten optimiza almaacuteximo el consumo de memoria mejora el tiempo de respuesta y reduce la cantidad decoacutedigo redundante a escribir

Core Data utiliza de fondo el meacutetodo de almacenamiento SQLite visto en la sesioacuten 3 deeste moacutedulo por tanto para explicar el funcionamiento de este meacutetodo de persistenciavamos a utilizar el mismo modelo de datos que usamos en esa sesioacuten Persona -gtDetallePersona

El el siguiente ejemplo haremos la misma aplicacioacuten que hicimos con SQLite perousando este nuevo meacutetodo de persistencia

731 Creando el proyecto

Para empezar tenemos que crearnos un nuevo proyecto en XCode Para ello abrimosXCode y seleccionamos crear una nueva aplicacioacuten basada en ventanas (Window-basedapplication) Hacemos click en Next escribimos como nombre del proyectosesion04-ejemplo2 y seleccionamos Use Core Data

Persistencia

61Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creando un proyecto nuevo

Con esto ya tenemos la estructura baacutesica para empezar a programar Antes de nadahechamos un vistazo raacutepido a toda la estructura de directorios creada y vemos que dentrodel directorio principal tenemos un archivo que se llamasesion04_ejemplo2xcdatamodeld Al hacer click sobre el archivo se nos abre uneditor especial que en principio estaacute vacio y que maacutes adelante utilizaremos para disentildearnuestro modelo de datos

Ahora hechamos un vistazo a la clase delegada sesion04_ejemplo2AppDelegatemvemos que hay unos cuantos meacutetodos nuevos que serviraacuten para configurar el Core DataVamos a explicar los maacutes importantes

bull - (NSManagedObjectModel )managedObjectModel NSManagedObjectModel esla clase que contiene la definicioacuten de cada uno de los objetos o entidades quealmacenamos en la base de datos Normalmente este meacutetodo no lo utilizaremos ya quenosotros vamos a utilizar el editor que hemos visto antes con el que podremos crearnuevas entidades crear sus atributos y relaciones

bull - (NSPersistentStoreCoordinator )persistentStoreCoordinator Aquiacute esdonde configuramos los nombres y las ubicaciones de las bases de datos que se usaraacutenpara almacenar los objetos Cuando un managed object necesite guardar algo pasaraacutepor este meacutetodo

bull - (NSManagedObjectContext )managedObjectContext Esta claseNSManagedObjectContext seraacute la maacutes usada con diferencia de las tres y por tanto la

Persistencia

62Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

maacutes importante La utilizaremos baacutesicamente para obtener objetos insertarlos oborrarlos

732 Definiendo el modelo de datos

En el ejemplo que hicimos en la sesioacuten anterior de SQLite creamos dos tablas una paralos datos baacutesicos de la persona y otra para el detalle Ahora haremos lo mismo perousando Core Data la diferencia principal es que mientras que con SQLite obteniamos soacutelolos datos que necesitaacutebamos de la base de datos aquiacute obtenemos el objeto completo

Para definir nuestro modelo de datos basado en dos objetos (Persona y DetallePersona)abrimos el editor visual haciendo click sobre el ficherosesion04_ejemplo2xcdatamodeld Empezaremos creando una nueva entidad para ellohacemos click sobre el botoacuten Add Entity (+) que se encuentra en la parte inferior dela pantalla A la nueva entidad le pondremos como nombre Persona

Dentro de la seccioacuten de Attributes hacemos click sobre (+) y creamos un nuevoatributo para nuestra entidad le llamamos nombre y le asignamos como tipo StringRealizamos este mismo paso 2 veces maacutes para antildeadir apellidos y edad este uacutetlimode tipo Integer 16

Atributos Persona

Ahora creamos la entidad de DetallePersona de la misma manera que la anterior perocreando los atributos localidad pais direccion cp y telefono Todasde tipo String

Atributos DetallePersona

Finalmente nos queda relacionar de alguacuten modo las dos entidades para ello antildeadimos una

Persistencia

63Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

relacioacuten dentro de la entidad de Persona hacia DetallePersona simplementehaciendo click sobre el botoacuten (+) de la seccioacuten de Relationships A la relacioacuten leponemos como nombre detalle seleccionamos como destino la entidadDetallePersona y seraacute No inverse Este tipo de relacioacuten es uno a uno Por dentroCore Data realizaraacute crearaacute una clave ajena en la tabla de persona que apunte aDetallePersona pero eso a nosotros no nos interesa

Vista general modelo de datos (1)

Apple recomienda que siempre que hagamos una relacioacuten de una entidad a otra hagamostambieacuten la relacioacuten inversa por lo tanto hacemos justo lo anterior pero al reveacutes (deDetallePersona a Persona) con la diferencia que en la columna de Inverse

seleccionamos datosPersona

Ya tenemos las entidades y las relaciones creadas Si hacemos click en el botoacuten deEditor Style en la parte de abajo a la derecha del editor podemos ver de una formamaacutes clara la estructura que hemos disentildeado

Persistencia

64Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista general modelo de datos (2)

733 Probando el modelo

Una vez que tenemos el modelo creado pasamos a probarlo para comprobar que estaacute todocorrecto para ello abrimos la clase delegada (sesion04_ejemplo2AppDelegatem) yantildeadimos el siguiente fragmento de coacutedigo arriba del meacutetodoapplicationDidFinishLaunching

NSManagedObjectContext context = [self managedObjectContext]NSManagedObject persona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

[persona setValueJuan forKeynombre][persona setValueMartinez forKeyapellidos][persona setValue[NSNumber numberWithInt28] forKeyedad]

NSManagedObject detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersonainManagedObjectContextcontext]

[detallePersona setValueCalle Falsa 123 forKeydireccion][detallePersona setValueAlicante forKeylocalidad][detallePersona setValueEspantildea forKeypais][detallePersona setValue965656565 forKeytelefono]

[detallePersona setValuepersona forKeydatosPersona][persona setValuedetallePersona forKeydetalle]

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Persistencia

65Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Con este coacutedigo hemos creado un objeto Persona enlazado con otro objetoDetallePersona iexclComo puedes ver no hemos necesitado nada de coacutedigo SQLPrimero creamos un objeto del tipo NSManagedObjectContext que almacena el conextode Core Data Despueacutes creamos el objeto Persona en siacute con NSManagedObject A esteobjeto le pondremos los valores para cada una de las propiedades definidas en el modeloHacemos lo mismo con DetallePersona Finalmente relacionamos el objetoPersona con el objeto DetallePersona

Compilamos el proyecto comprobamos que todo funciona bien y no salen errores

Para comprobar que los datos se han almacenado correctamente en nuestro dispositivo losmostramos en la consola de la siguiente manera

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

intValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(------------------)

[fetchRequest release]

El coacutedigo de arriba lo escribiremos dentro del meacutetododidFinishLaunchingWithOptions de la clase delegada Lo que hace este coacutedigo esrecorrer todos los objetos almacenados en memoria y mostrarlos por consola Siarrancamos la aplicacioacuten veremos que se muestran todos los datos de los objetosalmacenados Cada vez que arranquemos la aplicacioacuten se mostraraacuten maacutes objetos ya quetambieacuten los creamos

Para listar los objetos usamos la clase NSFetchRequest Esta clase es equivalente aejecutar una sentencia Select de SQL Al objeto NSFetchRequest le asignamos laentidad que queremos listar (SELECT FROM ) en nuestro caso seraacute PersonaEl listado de los objetos se obtiene mediante el meacutetodo executeFetchRequest de laclase NSManagedObjectContext que hemos definido en el bloque anterior de coacutedigoEste meacutetodo devolveraacute un NSArray con todos los objetos NSManagedObject

Persistencia

66Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Salida de consola

NotaSi queremos ver queacute sentencias SQL estaacute ejecutando Core Data por debajo tenemos que activarel flag de debug para ello seleccionamos la opcioacuten Product gt Edit Scheme Enla columna de la izquierda seleccionamos el esquema que estamos utilizando para Debug y en laparte de la derecha en el cuadro de Arguments Passed on Launch antildeadimos 2paraacutemetros -comappleCoreDataSQLDebug y 1

Ahora arrancamos de nuevo la aplicacioacuten y veremos que en la consola apareceraacuten lassentencias SQL utilizadas por Core Data

()

INSERT INTO ZDETALLEPERSONA(Z_PK Z_ENT Z_OPT ZDATOSPERSONA ZTELEFONOZLOCALIDAD ZDIRECCION ZPAIS)VALUES( )CoreData sql SELECT 0 t0Z_PK t0Z_OPT t0ZTELEFONO t0ZLOCALIDADt0ZDIRECCION t0ZPAIS t0ZDATOSPERSONAFROM ZDETALLEPERSONA t0 WHERE t0Z_PK =

()

734 Generando los modelos automaacuteticamente

Generar los modelos a mano directamente desde el editor no es lo maacutes recomendado yaque podemos cometer errores de escritura errores de tipo etc La mejor manera dehacerlo es creando un archivo para cada entidad Esto se puede hacer desde 0 peroXCode tiene un generador de clases que es muy faacutecil de usar y ahorremos bastantetiempo iexclVamos a usarlo

Hacemos click sobre el raiz de nuestro proyecto y seleccionamos New File En lacolumna de la izquierda seleccionamos iOS gt Core Data y en el cuadro de laderecha NSManagedObject subclass Click en next En la siguiente pantallaseleccionamos nuestro modelo de datos sesion04_ejemplo2 y click en next Ahoranos aseguramos de seleccionar las dos entidades que hemos creado anteriormentePersona y DetallePersona click en next Seleccionamos el raiz de nuestroproyecto para guardar los ficheros que se generen y aceptamos

Persistencia

67Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ventana de seleccioacuten de entidad

Ahora veremos que xsource ha creado automaacuteticamente 4 ficheros nuevos dentro denuestro proyecto Personamh y DetallePersonamh con todos los atributosespecificados Estas nuevas clases que heredan de NSManagedObject seraacuten las queutilicemos a partir de ahora

Cambiamos el coacutedigo de la clase delegada por el siguiente

NSManagedObjectContext context = [self managedObjectContext]Persona datosPersona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

datosPersonanombre = JuandatosPersonaapellidos = MartinezdatosPersonaedad = [NSNumber numberWithInt28]

DetallePersona detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersona

inManagedObjectContextcontext]detallePersonadireccion = Calle Falsa 123

Persistencia

68Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

detallePersonalocalidad = AlicantedetallePersonapais = EspantildeadetallePersonatelefono = 965656565

detallePersonadatosPersona = datosPersonadatosPersonadetalle = detallePersona

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Listamos los datosNSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

integerValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(-----------------)[fetchRequest release]

Ejecutamos el proyecto y comprobamos que en la consola aparece lo mismo que antespero ahora tenemos el coacutedigo bastante maacutes claro

735 Creando la vista de tabla

Ahora vamos a mostrar los objetos que antes hemos listado por consola en una vista detabla UITableView Para ello creamos un UITableViewController

Click en New file gt UIViewController subclass En la siguiente pantallanos aseguramos de seleccionar Subclass of UITableViewController de esta formanos apareceraacuten ya todos los meacutetodos de la tabla creados por defecto Guardamos elarchivo con el nombre que queramos por ejemplo PersonasViewController

Abrimos ahora PersonasViewControllerh y antildeadimos 2 atributos a la clase

bull Un NSArray que se seraacute el listado de personas (objetos de la clase Persona)bull Una referencia a NSManagedObjectContext

El fichero PersonasViewControllerh quedaraacute de la siguiente manera

Persistencia

69Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

imports iniciales

interface PersonasViewController UITableViewController NSArray _listaPersonasNSManagedObjectContext _context

property (nonatomic retain) NSArray listaPersonasproperty (nonatomic retain) NSManagedObjectContext context

end

Ahora en el m importamos en el encabezado las clases Personah yDetallePersonah y en el meacutetodo viewDidLoad creamos el array listaPersonas contodos los objetos Persona de forma similar a cuando lo hicimos en el apartado anterior

import PersonasViewControllerh

import Personahimport DetallePersonah

implementation PersonasViewController

synthesize listaPersonas = _listaPersonassynthesize context = _context

En viewDidLoad

- (void)viewDidLoad

[super viewDidLoad]

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContext_context]

[fetchRequest setEntityentity]NSError errorselflistaPersonas = [_context executeFetchRequestfetchRequest

erroramperror]selftitle = Listado Personas[fetchRequest release]

Ahora completamos el resto de meacutetodos heredados de UITableViewController de lamisma forma que hicimos en sesiones anteriores

- (NSInteger)numberOfSectionsInTableView(UITableView )tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection(NSInteger)section

Persistencia

70Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Return the number of rows in the sectionreturn [_listaPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]

if (cell == nil) cell = [[[UITableViewCell alloc]

initWithStyleUITableViewCellStyleSubtitlereuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona p = (Persona )[_listaPersonas objectAtIndexindexPathrow]celltextLabeltext = pnombrecelldetailTextLabeltext = [NSString stringWithFormatd antildeos

[pedad intValue]]

return cell

Ahora para poder mostrar la tabla dentro de nuestra aplicacioacuten debemos asignarla dentrode la clase delegada para ello abrimos la vista MainWindowxib y arrastramos unacontroladora de navegacioacuten Navigation Controller a la lista de objetos de laizquierda La desplegamos y dentro de View Controller en la pestantildea de la derechade Identity Inspector escribimos el nombre de nuestra clase que tiene la vista de latabla PersonasViewController

Pantalla del disentildeador

Ahora abrimos el fichero sesion04_ejemplo2AppDelegateh y creamos un

Persistencia

71Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

UINavigationController dentro de la declaracioacuten de variables dejando el ficherocomo sigue

imports iniciales

interface sesion04_ejemplo2AppDelegate NSObject ltUIApplicationDelegategt

UINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet UINavigationControllernavController

property (nonatomic retain readonly) NSManagedObjectContextmanagedObjectContextproperty (nonatomic retain readonly) NSManagedObjectModelmanagedObjectModelproperty (nonatomic retain readonly) NSPersistentStoreCoordinatorpersistentStoreCoordinator

- (void)saveContext- (NSURL )applicationDocumentsDirectory

end

Dentro del m importamos la clase PersonasViewControllerh y completamos elsynthetize

synthesize navController = _navController

Y por uacuteltimo justo antes de la llamada al meacutetodo makeKeyAndVisible escribimos elsiguiente coacutedigo para asignar el contexto de la clase delegada alPersonasViewController

PersonasViewController root = (PersonasViewController ) [_navControllertopViewController]

rootcontext = [self managedObjectContext][selfwindow addSubview_navControllerview]

Ahora compilamos y ejecutamos nuestro proyecto y si todo ha ido bien deberiacutea de salirla tabla con los datos de las personas

Persistencia

72Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Listado de personas

736 Migraciones de bases de datos con Core Data

Una vez que tenemos disentildeada nuestra base de datos inicial iquestqueacute pasariacutea si queremosmodificarla para antildeadirle un campo maacutes a una tabla iquestQueacute haraacute Core Data para realizar lamigracioacuten de la versioacuten anterior a la actual

Siguiendo con el ejemplo anterior si despueacutes de ejecutar la aplicacioacuten en nuestrodispositivo o simulador modificamos la estructura de la base de datos antildeadiendo unatributo nuevo en la entidad DetallePersona por ejemplo codigoPostal con el tipoInteger 16 veremos que al volver a arrancar la aplicacioacuten nos apareceraacute un error entiempo de ejecucioacuten similar al siguiente

Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 Theoperationcouldnrsquot be completed(Cocoa error 134100) UserInfo=0x6b6c680 metadata=ltCFBasicHash 0x6b68380[0x1337b38]gttype = immutable dict count = 7entries =gt

2 ltCFString 0x6b70320 [0x1337b38]gtcontents =NSStoreModelVersionIdentifiers

=ltCFArray 0x6b706e0 [0x1337b38]gttype = immutable count = 1

values = (0 ltCFString 0x1332cd8 [0x1337b38]gtcontents =

)4 ltCFString 0x6b70350 [0x1337b38]gtcontents =

NSPersistenceFrameworkVersion=

Persistencia

73Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltCFNumber 0x6b6f2c0 [0x1337b38]gtvalue = +386 type =kCFNumberSInt64Type

6 ltCFString 0x6b70380 [0x1337b38]gtcontents =NSStoreModelVersionHashes =

ltCFBasicHash 0x6b707e0 [0x1337b38]gttype = immutable dict count =2entries =gt

1 ltCFString 0x6b70700 [0x1337b38]gtcontents = Persona =ltCFData 0x6b70740

[0x1337b38]gtlength = 32 capacity = 32 bytes =0x1c50e5a379ff0ddc3cda7b82a3934c5e 75649ac3c919beea2 ltCFString 0x6b70720 [0x1337b38]gtcontents = DetallePersona

=ltCFData 0x6b70790[0x1337b38]gtlength = 32 capacity = 32 bytes =0xb7ba2eb498f9a1acdd6630d56f6cd12c e8963dd3e488ba95

7 ltCFString 0x10e3aa8 [0x1337b38]gtcontents = NSStoreUUID =ltCFString 0x6b70510[0x1337b38]gtcontents = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E8 ltCFString 0x10e3948 [0x1337b38]gtcontents = NSStoreType =ltCFString 0x10e3958[0x1337b38]gtcontents = SQLite9 ltCFString 0x6b703b0 [0x1337b38]gtcontents =

_NSAutoVacuumLevel = ltCFString0x6b70830 [0x1337b38]gtcontents = 210 ltFString 0x6b703d0 [0x1337b38]gtcontents =

NSStoreModelVersionHashesVersion=ltCFNumber 0x6b61950 [0x1337b38]gtvalue = +3 type =

kCFNumberSInt32Type reason=The model used to open the store is incompatible with the oneused tocreate the store

metadata = NSPersistenceFrameworkVersion = 386NSStoreModelVersionHashes =

DetallePersona = ltb7ba2eb4 98f9a1ac dd6630d5 6f6cd12c f890007746b16f00

e8963dd3 e488ba95gtPersona = lt1c50e5a3 79ff0ddc 3cda7b82 a3934c5e 5cb4ee4b

4ac98838 75649ac3c919beeagt

NSStoreModelVersionHashesVersion = 3NSStoreModelVersionIdentifiers = (

)NSStoreType = SQLiteNSStoreUUID = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E_NSAutoVacuumLevel = 2

reason = The model used to open the store is incompatible with the

one usedto create the store

Este error es muy comuacuten cuando estamos desarrollando aplicaciones con Core DataBaacutesicamente nos dice que el modelo de base de datos que estaacute intentando cargar dememoria es distinto al que se indica en la aplicacioacuten esto es porque no hemos indicadoninguacuten meacutetodo de migracioacuten Realmente existen dos formas de solucionar este error

Persistencia

74Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Por un lado y soacutelo cuando estemos desarrollando y no tengamos ninguna versioacutende nuestra aplicacioacuten lanzada en la App Store podremos eliminar la base de datosdel dispositivo simulador simplemente borrando la aplicacioacuten de el Tambieacutenpodemos ejecutar el siguiente coacutedigo una soacutela vez [[NSFileManagerdefaultManager] removeItemAtURLstoreURL errornil]

bull En el caso de que estemos realizando una migracioacuten sobre una versioacuten ya publicadaen la App Store el meacutetodo anterior no nos serviriacutea ya que obligariacuteamos al usuario aborrar su aplicacioacuten del dispositivo con lo que todos los datos que tuviera seperderiacutean Esto no es viable por lo que necesitaremos usar otro meacutetodo para realizarla migracioacuten y solucionar el error de una forma maacutes limpia este meacutetodo es el quevamos a comentar a continuacioacuten

XCode soporta Versioning para Core Data esto es que podemos crear versiones de labase de datos de una forma sencilla si seguimos una serie de pasos

1) Antildeadimos las siguientes opciones en forma de NSDictionary a nuestro coacutedigo delpersistentStoreCoordinator

NSDictionary options = [NSDictionary dictionaryWithObjectsAndKeys[NSNumber numberWithBoolYES]NSMigratePersistentStoresAutomaticallyOption[NSNumber numberWithBoolYES]NSInferMappingModelAutomaticallyOptionnil]

Y modificamos el meacutetodo addPersistentStoreWithType pasaacutendole como paraacutemetro eldiccionario options

if ([__persistentStoreCoordinatoraddPersistentStoreWithTypeNSSQLiteStoreTypeconfigurationnil URLstoreURL optionsoptions erroramperror])

Con el coacutedigo anterior indicamos al persistentStoreCoordinator que la migracioacuten enel caso de que se deba de realizar sea de forma automaacutetica

AtencioacutenDeberemos de tener en cuenta que este tipo de migracioacuten que estamos estudiando en este puntosoacutelo es vaacutelida para cambios simples en Core Data lo que Apple llama como lightweightmigration Este tipo de migracioacuten admite una serie de cambios que se indican dentro de ladocumentacioacuten de Apple para cambios maacutes complejos deberemos realizar migracionespersonalizadas y mucho maacutes complejas El documento en donde Apple explica el tipo demigraciones que existen para Core Data y su forma de implementarlas lo podemos consultardesde esta web

2) Ahora deberemos de crear la versioacuten del esquema de Core Data dentro de XCode para

Persistencia

75Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ello seleccionamos el fichero del esquema sesion04_ejemplo2xcdatamodeld yseleccionamos en el menuacute principal Editor gt Add Model Version A la nueva versioacuten lallamaremos sesion04_ejemplo_2 (versioacuten 2) Una vez que hayamos pulsado sobreFinish se nos habraacute creado la nueva versioacuten del esquema dentro de la principal y estaraacutemarcada con un tick la versioacuten anterior

3) Una vez que tenemos creada la nueva versioacuten deberemos modificarla antildeadiendole elnuevo atributo Seleccionamos la versioacuten recieacuten creadasesion04_ejemplo_2xcdatamodel y antildeadimos a la entidad DetallePersona elatributo codigoPostal Atencioacuten Si este atributo lo hemos antildeadido anteriormentedeberemos de borrarlo de la versioacuten anterior y antildeadirlo en la segunda versioacuten

4) Ahora nos queda indicar a Core Data que queremos usar la versioacuten 2 de la base dedatos para nuestra aplicacioacuten Para ello seleccionamos el esquema principal (el raiz)sesion04_ejemplo2xcdatamodeld y en la ventana de la derecha en la pestantildea de FileInspector seleccionamos como Versioacuten actual la versioacuten 2 esto lo hacemos dentro delapartado de Versioned Core Data Model Automaacuteticamente veremos que el siacutembolo detick cambia a la versioacuten 2

5) Ahora ya podemos volver a compilar y veremos que no nos aparece el error y funcionatodo seguacuten lo esperado

Magical RecordExisten varias librerias de coacutedigo libre que facilitan enormemente el uso de Core Data ennuestras aplicaciones iOS Una de ellas es Magical Record Mediante esta libreriacutea podremos usarCore Data sin tener que preocuparnos de todo el proceso de configuracioacuten en las controladorasde la configuracioacuten de migraciones etc Su instalacioacuten en un proyecto de XCode es muy faacutecil yaltamente recomendable ya que proporciona una API muy clara y sencilla de usar

Persistencia

76Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios

81 Usando User Defaults (1 punto)

En este ejercicio vamos a usar las variables de usuario para almacenar y mostrar los datosdel dispositivo que estamos usando y las veces que hemos arrancado la aplicacioacuten

Crear un proyecto nuevo usando la plantilla Single View Application con el nombreejercicio-userdefaults-ios Se debe de ejecutar en iPhone e iPad (Universal) Comoidentificador de empresa escribiremos esuajtech y como prefijo de clase UA Debe detener el ARC activado (desmarcar el resto de opciones)

Ayuda meacutetodos de UIDevicePara obtener los datos del dispositivo usaremos el singleton UIDevice estos son algunos de losmeacutetodos que podemos usar [[UIDevice currentDevice] name] [[UIDevicecurrentDevice] systemName] [[UIDevice currentDevice]systemVersion] [[UIDevice currentDevice] model] [[UIDevicecurrentDevice] localizedModel]

82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)

En este ejercicio disentildearemos un modelo de datos de dos tablas relacionadas entre ellasantildeadiremos datos por coacutedigo y los mostraremos en una vista de tabla La base de datosque vamos a implementar constaraacute de dos tablas por un lado una que contendraacute los tiacutetulosde series y otra que tendraacute los detalles A la primera la llamaremos Serie y la segundaDetalleSerie Para realizar correctamente el ejercicio deberemos seguir los siguientespasos

1) Crear un proyecto nuevo usando la plantilla Master-Detail Application con elnombre ejercicio-coredata-ios Se debe de ejecutar en iPhone como identificador deempresa escribiremos esuajtech y como prefijo de clase UA Debe de tener el ARCactivado y la opcioacuten de Use Core Data (desmarcar el resto de opciones)

2) Disentildear el siguiente modelo de datos

Persistencia

77Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esquema modelo de datos

3) Revisar el fichero UAAppDelegatem y comprobar que tenemos todos los meacutetodosnecesarios para que funcione Core Data en nuestra aplicacioacuten

4) Modificar el fichero UAMasterViewControllerm para que se muestren los tiacutetulos delas series en la tabla

5) Probar la aplicacioacuten antildeadiendo unos tiacutetulos de prueba

83 Migrando datos con Core Data (05 puntos)

Una vez que hemos disentildeado la Base de Datos con Core Data vamos a modificarlaantildeadiendo un atributo maacutes a la tabla de DetalleSerie valoracion

a) iquestQueacute pasa si cambiamos el modelo de datos y arrancamos la aplicacioacuten iquestQueacute errornos da y por queacute

b) iquestCoacutemo podemos evitar este tipo de errores cuando estemos desarrollando iquestY siestamos en produccioacuten con la aplicacioacuten ya a la venta

c) Crea una nueva versioacuten del modelo

d) Modifica el coacutedigo necesario dentro de la clase UAAppDelegate para evitar este tipo deerrores cuando estemos en un entorno de produccioacuten

e) Vuelve a arrancar la aplicacioacuten y comprueba que funciona bien

Persistencia

78Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Persistencia

79Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

  • 1 Persistencia en Android ficheros y SQLite
    • 11 Introduccioacuten
    • 12 Manejo de ficheros tradicionales en Android
      • 121 Apertura de ficheros
      • 122 Ficheros como recursos
      • 123 Operar con ficheros
      • 124 Almacenar datos en la tarjeta de memoria
        • 13 Base de datos SQLite
          • 131 Content Values
          • 132 Cursores
          • 133 Trabajar con bases de datos SQLite
          • 134 La clase SQLiteOpenHelper
          • 135 Crear una base de datos sin SQLiteHelper
          • 136 Realizar una consulta
          • 137 Extraer resultados de un cursor
          • 138 Antildeadir actualizar y borrar filas
              • 2 Ejercicios - Persistencia en Android ficheros y SQLite
                • 21 Uso de ficheros (05 puntos)
                • 22 Persistencia con ficheros (05 puntos)
                • 23 Base de datos SQLiteOpenHelper (05 puntos)
                • 24 Base de datos insercioacuten y borrado (05 puntos)
                • 25 Base de datos probar nuestro adaptador (05 puntos)
                • 26 Base de datos cambios en la base de datos (05 puntos)
                  • 3 Persistencia en Android proveedores de contenidos y SharedPreferences
                    • 31 Shared Preferences
                      • 311 Guardar Shared Preferences
                      • 312 Leer Shared Preferences
                      • 313 Interfaces para Shared Preferences
                      • 314 Definiendo una pantalla de preferencias con un layout en XML
                      • 315 Controles nativos para preferencias
                      • 316 Actividades de preferencias
                      • 317 Shared Preference Change Listeners
                        • 32 Proveedores de contenidos
                          • 321 Proveedores nativos
                          • 322 Proveedores propios crear un nuevo proveedor de contenidos
                          • 323 Proveedores propios crear la interfaz de consultas
                          • 324 Proveedores propios tipo MIME
                          • 325 Proveedores propios registrar el proveedor
                          • 326 Content Resolvers
                          • 327 Otras operaciones con Content Resolvers
                              • 4 Ejercicios - Persistencia en Android proveedores de contenidos y SharedPreferences
                                • 41 Compartir datos entre actividades con Shared Preferences (075 puntos)
                                • 42 Actividad de preferencias (075 puntos)
                                • 43 Proveedor de contenidos propio (075 puntos)
                                • 44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)
                                  • 5 Persistencia de datos en iOS Ficheros y SQLite
                                    • 51 Introduccioacuten
                                    • 52 Ficheros plist (Property Lists)
                                      • 521 Leyendo datos desde ficheros plist
                                      • 522 Escribiendo datos en ficheros plist
                                        • 53 SQLite
                                          • 531 Herramientas disponibles
                                          • 532 Lectura de datos
                                          • 533 Mostrando los datos en una tabla
                                              • 6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios
                                                • 61 Creando y leyendo un fichero plist (15 puntos)
                                                • 62 Implementar vista de detalle de la serie (05 puntos)
                                                • 63 Uso baacutesico de una base de datos SQLite (1 punto)
                                                  • 7 Persistencia de datos en iOS User Defaults y Core Data
                                                    • 71 Introduccioacuten
                                                    • 72 User Defaults
                                                    • 73 Core Data
                                                      • 731 Creando el proyecto
                                                      • 732 Definiendo el modelo de datos
                                                      • 733 Probando el modelo
                                                      • 734 Generando los modelos automaacuteticamente
                                                      • 735 Creando la vista de tabla
                                                      • 736 Migraciones de bases de datos con Core Data
                                                          • 8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios
                                                            • 81 Usando User Defaults (1 punto)
                                                            • 82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)
                                                            • 83 Migrando datos con Core Data (05 puntos)
Page 15: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de

2 Ejercicios - Persistencia en Android ficheros y SQLite

21 Uso de ficheros (05 puntos)

En este ejercicio vamos a crear una aplicacioacuten que muestre un listado de cadenas porpantalla Estas cadenas se almacenaraacuten en un fichero de texto privado para la aplicacioacutenPodremos antildeadir nuevas cadenas a partir de un cuadro de edicioacuten presente en la interfazPartiremos del proyecto Ficheros proporcionado en las plantillas de la aplicacioacuten Debesseguir los siguientes pasos

bull Antildeadir un manejador al botoacuten de la actividad principal para que cada vez que seapulsado se guarde en un fichero de texto El fichero se llamaraacute ficherotxt Al abrirel fichero para escritura se utilizaraacute el paraacutemetro ContextMODE_APPEND con lo quecada nueva cadena se antildeadiraacute al final del fichero en el caso en el que eacuteste ya existieraPara escribir en el fichero utilizaremos el meacutetodo writeBytes del objetoDataOutputStream correspondiente

bull Al pulsar el botoacuten tambieacuten se deberaacute borrar el contenido del elemento EditText (leasignamos a la vista una cadena vaciacutea con el meacutetodo setText)

bull Ejecutamos la aplicacioacuten e introducimos algunas liacuteneas en el fichero Vamos acomprobar ahora que todo ha funcionado correctamente Para ello accedemos alsistema de ficheros de nuestro dispositivo ejecutando el comando adb shell

dentro de la carpeta platform-tools de nuestra carpeta de instalacioacuten del SDK deAndroid

bull Comprueba que se ha creado el fichero ficherotxt en la carpetadatadataesuajtechandroidficherosfiles Examina su contenido ejecutando cat

ficherotxt Si algo no ha salido bien siempre podraacutes eliminar el fichero con rm

ficherotxtbull En la interfaz de la actividad se ha incluido una vista de tipo TextView debajo del

botoacuten Antildeade un manejador para dicha vista de tal forma que cada vez que se hagaclick sobre ella se muestre el contenido del fichero ficherotxt Para ello leeremos elfichero liacutenea a liacutenea antildeadiendo cada una al TextView por medio del meacutetodo append

Persistencia

15Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Interfaz de la aplicacioacuten Ficheros

22 Persistencia con ficheros (05 puntos)

Seguramente habraacutes observado que si abandonas la aplicacioacuten anterior (pulsando el botoacutenBACK del dispositivo) al volver a ejecutarla el contenido del TextView ha desaparecidoDebes volver a pulsar sobre la vista para que se vuelva a mostrar el contenido del ficheroHaz las modificaciones pertinentes para que esto no sea asiacute es decir para que cuandovuelva a mostrarse la actividad tras haber sido eliminada de la memoria se muestre elTextView tal cual se veiacutea antes de abandonar la aplicacioacuten

AvisoEn este ejercicio no se estaacute pidiendo que cargues el contenido del fichero en el TextView nadamaacutes arrancar la aplicacioacuten Puede darse el caso de que lo que esteacute mostrando el TextViewcuando la actividad sea eliminada de memoria no se corresponda con el contenido actualizado delfichero

NotaPara poder completar el ejercicio deberaacutes repasar los manejadores de evento de la primera sesioacutendel moacutedulo de Android relacionados con el ciclo de vida de ejecucioacuten de actividades

23 Base de datos SQLiteOpenHelper (05 puntos)

En las plantillas de la aplicacioacuten se incluye la aplicacioacuten BaseDatos En el proyecto de laaplicacioacuten se incluye el esqueleto de una clase MiAdaptadorBD que tendremos quecompletar Se trata de un patroacuten que nos va a permitir acceder a una base de datos de

Persistencia

16Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

usuarios dentro de la aplicacioacuten sin necesidad de hacer uso de coacutedigo SQL

La clase MiAdaptadorBD incluye a su vez otro patroacuten en este caso implementado comouna subclase de SQLiteOpenHelper Eacuteste nos obliga a definir queacute ocurre cuando la basede datos todaviacutea no existe y debe ser creada y queacute ocurre si ya existe pero debe seractualizada porque ha cambiado de versioacuten Asiacute el SQLiteOpenHelper queimplementemos en este caso MiOpenHelper nos devolveraacute siempre una base de datosseparaacutendonos de la loacutegica encargada de comprobar si la base de datos existe o no

En este primer ejercicio se pide hacer lo siguiente

bull Ejecutar la sentencia de creacioacuten de bases de datos (la tenemos declarada comoconstante de la clase) en el meacutetodo MiOpenHelperonCreate

bull Implementar tambieacuten el meacutetodo onUpgrade Idealmente eacuteste deberiacutea portar las tablasde la versioacuten antigua a la versioacuten nueva copiando todos los datos Nosotros vamos aeliminar directamente la tabla que tenemos con la sentencia SQL DROP TABLE IF

EXISTS + NOMBRE_TABLA y volveremos a crearlabull En el constructor de MiAdaptadorBD debemos obtener en el campo db la base de

datos utilizando MiOpenHelper

24 Base de datos insercioacuten y borrado (05 puntos)

Continuamos trabajando con el proyecto anterior En este ejercicio completaremos elcoacutedigo relacionado con las sentencias de insercioacuten y borrado Para realizar la insercioacutenvamos a hacer uso de un mecanismo que no hemos visto en la sesioacuten de teoriacutea y queconsiste en utilizar una sentencia de insercioacuten SQL precompilada que se completaraacute conlos valores concretos a insertar en la base de datos justo antes de realizar dicha insercioacutenLa ventaja de las sentencias compiladas es que evitan que se produzcan ataques deinyeccioacuten de coacutedigo

Como se puede observar se ha incluido un atributo a la clase que representa la insercioacutenSQL

private static final String INSERT = insert into + NOMBRE_TABLA +(+COLUMNAS[1]+) values ()

En esta sentencia no se indica ninguacuten valor concreto para la columna nombre En el lugaren el que deberiacutean aparecer los valores de dicho campo se ha escrito simplemente unsiacutembolo de interrogacioacuten Tambieacuten se ha antildeadido una instancia de la claseSQLiteStatement como parte de los atributos de la clase

private SQLiteStatement insertStatement

Realizamos los siguientes pasos

bull En el constructor de la clase MiAdaptadorBD llevamos a cabo la compilacioacuten de lasentencia

thisinsertStatement = thisdbcompileStatement(INSERT)

Persistencia

17Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Una vez hecho esto cada vez que deseemos insertar un nuevo usuario en la base dedatos deberemos dar valores concretos a la columna nombre y ejecutar la sentenciaPara ello debemos antildeadir el siguiente coacutedigo dentro del meacutetodo insert de la claseMiAdaptadorBD

thisinsertStatementbindString(1 nombreUsuario)return thisinsertStatementexecuteInsert()

bull Por uacuteltimo completamos el coacutedigo del meacutetodo deleteAll cuyo contenido es eliminara todos los usuarios de la base de datos Para que ademaacutes se devuelva el nuacutemero defilas afectadas podemos insertar la siguiente liacutenea en dicho meacutetodo

return dbdelete(NOMBRE_TABLA null null)

Una vez hechos todos estos cambios podremos utilizar la clase MiAdaptadorBD parahacer operaciones con la base de datos de usuarios de manera transparente

25 Base de datos probar nuestro adaptador (05 puntos)

Antildeade coacutedigo en la actividad Main para eliminar todos los usuarios de la base de datosantildeadir dos cualesquiera y listarlos por medio de la vista de tipo TextView que seencuentra en el layout de dicha actividad

Podemos comprobar mediante la liacutenea de comandos (comando adb shell) que la basede datos ha sido creada Para ello puede ser uacutetil hacer uso de los siguientes comandos

cd datadataesuajtechandroidbasedatosdatabasessqlite3 misusuariosdbsqlitegt schemasqlitegt tablessqlitegt select from usuarios

26 Base de datos cambios en la base de datos (05 puntos)

Ahora vamos a cambiar en la clase MiAdaptadorBD el nombre de la segunda columnaque en lugar de nombre se va a llamar nombres Ejecutamos la aplicacioacuten y comprobamosque sale con una excepcioacuten Comprueba por medio de LogCat cuaacutel ha sido el erroriquestCoacutemo lo podemos solucionar

NotaPista conforme hemos programado la clase MiAdaptadorBD y siguiendo el patroacuten de disentildeode SQLiteOpenHelper es posible arreglar el problema simplemente modificando el valor deun campo

Persistencia

18Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

3 Persistencia en Android proveedores de contenidos ySharedPreferences

31 Shared Preferences

Comenzamos esta sesioacuten hablando de una de las teacutecnicas maacutes simples de persistencia enAndroid junto al uso de ficheros Shared Preferences Se trata de un mecanismo ligeropara almacenar datos basado en pares clave-valor utilizado normalmente para guardarpreferencias u opciones de la aplicacioacuten Tambieacuten puede ser utilizado para almacenar elestado de la interfaz graacutefica para contemplar el caso de que nuestra actividad debafinalizar abruptamente su ejecucioacuten Otro posible uso es el de compartir informacioacutenentre componentes de una misma aplicacioacuten

NotaLos Shared Preferences nunca son compartidos entre diferentes aplicaciones

Este meacutetodo se basa en el uso de la clase SharedPreferences Es posible utilizarla paraguardar datos en muy diversos formatos booleanos cadenas flotantes y enteros

311 Guardar Shared Preferences

Para crear o modificar un Shared Preference llamamos al meacutetodogetSharedPreferences del contexto de la aplicacioacuten pasando como paraacutemetro elidentificador del conjunto de valores de preferencias con el que queremos trabajar Pararealizar la modificacioacuten del valor de un Shared Preference hacemos uso de la claseSharedPreferencesEditor Para obtener el objeto editor invocamos el meacutetodo edit

del objeto Shared Preferences que queremos modificar Los cambios se guardan medianteel meacutetodo commit Veamos un ejemplo

int modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences = getSharedPreferences(Mispreferencias modo)

Obtenemos un editor para modificar las preferenciasSharedPreferencesEditor editor = misSharedPreferencesedit()

Guardamos nuevos valores en el objeto SharedPreferenceseditorputBoolean(isTruetrue)editorputFloat(unFloat10)editorputInt(numeroEntero12)editorputLong(otroNumero2)editorputString(unaCadenavalor)

Guardamos los cambioseditorcommit()

Persistencia

19Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

312 Leer Shared Preferences

Para leer Shared Preferences utilizamos el meacutetodo getSharedPreferences tal como seha comentado anteriormente Pasamos como paraacutemetro el identificador del conjunto deShared Preferences al que deseamos acceder Una vez hecho esto ya podemos hacer usode meacutetodos de tipo get para acceder a preferencias individuales Hay diferentes meacutetodosget para diferentes tipos de datos Cada uno de ellos requiere como paraacutemetro la claveque identifica la preferencia cuyo valor deseamos obtener y un valor por defecto el cualse usaraacute en el caso concreto en el que no existiera la preferencia con la clave pasada comoprimer paraacutemetro Podemos ver un ejemplo a continuacioacuten

public static String MIS_PREFS = MIS_PREFS

public void cargarPreferences() Obtener las preferencias almacenadasint modo = ActivityMODE_PRIVATESharedPreferences misSharedPreferences =

getSharedPreferences(MIS_PREFS modo)

boolean isTrue = misSharedPreferencesgetBoolean(isTruefalse)float unFloat = misSharedPreferencesgetFloat(unFloat0)int numeroEntero = misSharedPreferencesgetInt(numeroEntero0)long otroNumero = misSharedPreferencesgetLong(otroNumero0)String unaCadena = misSharedPreferencesgetString(unaCadena)

313 Interfaces para Shared Preferences

Android proporciona una plataforma basada en XML para la creacioacuten de interfacesgraacuteficas para el manejo de preferencias Estas interfaces tendraacuten un aspecto similar al delas del resto de aplicaciones del sistema Al utilizarla nos estaremos asegurando de quenuestras actividades para el manejo de preferencias seraacuten consistentes con las del resto deaplicaciones del sistema De esta forma los usuarios estaraacuten familiarizados con el manejode esta parte de nuestra aplicacioacuten

La plataforma consiste en tres elementos

bull Layout de la actividad de preferencias se trata de un archivo XML que definiraacute lainterfaz graacutefica de la actividad que muestre los controles para modificar los valores delas preferencias Permite especificar las vistas a mostrar los posibles valores que sepueden introducir y a queacute clave de Shared Preferences se corresponde cada vista

bull Actividad de preferencias una subclase de PreferenceActivity que secorresponderaacute con la pantalla de preferencias de nuestra aplicacioacuten

bull Shared Preference Change Listener una implementacioacuten de la claseonSharedPreferenceChangeListener que actuaraacute en el caso en el que cambie elvalor de alguna Shared Preference

314 Definiendo una pantalla de preferencias con un layout en XML

Persistencia

20Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esta es sin duda la parte maacutes importante de una actividad de preferencias Se trata de unarchivo XML utilizado para definir varios aspectos de dicha actividad Al contrario queen el caso de los archivos de layout para interfaces graacuteficas que vimos en la sesioacutencorrespondiente del moacutedulo de Android estos archivos de layout se guardan en la carpetaresxml de los recursos de la aplicacioacuten

Aunque conceptualmente estos layouts son similares a los utilizados para definirinterfaces graacuteficas los layouts de actividades de preferencias utilizan un conjuntoespecializado de componentes especiacuteficos para este tipo de actividades Estaacuten pensadospara proporcionar interfaces con un aspecto similar al del resto de actividades depreferencias del sistema Estos controles se describen en maacutes detalle en la siguienteseccioacuten

Los layout de preferencias contendraacuten un elemento PreferenceScreen

ltxml version=10 encoding=utf-8gtltPreferenceScreen

xmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceScreengt

Se pueden antildeadir maacutes elementos de este tipo si se desea Al hacerlo eacutestos serepresentaraacuten como un elemento seleccionable en un listado que mostraraacute una nuevaventana de preferencias en caso de que sea seleccionado

Dentro de cada elemento PreferenceScreen podemos incluir tantos elementos de tipoPreferenceCategory y elementos especiacuteficos para antildeadir controles como se desee Loselementos PreferenceCategory son utilizados para dividir cada pantalla de preferenciasen subcategoriacuteas mediante una barra de tiacutetulo Su sintaxis es la siguiente

ltPreferenceCategoryandroidtitle=My Preference Categorygt

ltPreferenceCategorygt

Una vez dividida la pantalla en subcategoriacuteas tan soacutelo quedariacutea antildeadir los elementoscorrespondientes a los controles especiacuteficos que veremos en la siguiente seccioacuten Losatributos de los elementos XML para estos controles pueden variar aunque existe unconjunto de atributos comunes

bull androidkey la clave de Shared Preference que se usaraacute para guardar el valor delcontrol

bull androidtitle texto a mostrar para describir la preferenciabull androidsummary una descripcioacuten maacutes larga a mostrar debajo del texto definido con

el campo anteriorbull androiddefaultValue el valor por defecto que se mostraraacute inicialmente y tambieacuten

el que finalmente se guardaraacute si ninguacuten otro valor fue asignado a este campo

El siguiente listado muestra una pantalla de preferencias muy simple con una uacutenicacategoriacutea y un uacutenico control (un check box)

ltxml version=10 encoding=utf-8gt

Persistencia

21Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltPreferenceScreenxmlnsandroid=httpschemasandroidcomapkresandroidgtltPreferenceCategory

androidtitle=Mi categoriacuteagtltCheckBoxPreference

androidkey=MI_CHECK_BOXandroidtitle=Preferencia Check Boxandroidsummary=Descripcioacuten de la preferencia

Check BoxandroiddefaultValue=true

gtltPreferenceCategorygt

ltPreferenceScreengt

Esta pantalla de preferencias se mostrariacutea de la siguiente forma

Ejemplo sencillo de ventana de preferencias

315 Controles nativos para preferencias

Android incluye diferentes controles que podemos antildeadir a nuestras ventanas depreferencias por medio de los elementos XML que se indican a continuacioacuten

bull CheckBoxPreference un check box estaacutendar que puede ser utilizado parapreferencias de tipo booleano

bull EditTextPreference permite al usuario introducir una cadena como valor para unapreferencia Al seleccionar el texto se mostraraacute un diaacutelogo con el que introducir elnuevo valor

bull ListPreference el equivalente a un Spinner Seleccionar este elemento de lainterfaz mostraraacute un diaacutelogo con las diferentes opciones que es posible seleccionar Seutilizan arrays para asociar valores y textos a las opciones de la lista

bull RingtonePreference un tipo especiacutefico de preferencia de tipo listado que permiteseleccionar entre diferentes tonos de teleacutefono Esta opcioacuten es particularmente uacutetil en

Persistencia

22Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

el caso en el que se esteacute creando una pantalla de preferencias para configurar lasopciones de notificacioacuten de una aplicacioacuten o componente de la misma

Tambieacuten es posible crear nuestros propios controles personalizados definiendo unasubclase de Preference (o cualquiera de sus subclases) Se puede encontrar maacutesinformacioacuten en la documentacioacuten de AndroidhttpdeveloperandroidcomreferenceandroidpreferencePreferencehtml

316 Actividades de preferencias

Para mostrar una pantalla de preferencias debemos definir una subclase dePreferenceActivity

public class MisPreferencias extends PreferenceActivity

La interfaz graacutefica de la interfaz se puede crear a partir del layout en el meacutetodo onCreatemediante una llamada a addPreferencesFromResource

Overridepublic void onCreate(Bundle savedInstaceState)

superonCreate(savedInstanceState)addPreferencesFromResource(Rxmlmilayout)

Como en el caso de cualquier otra actividad la actividad de preferencias que hayamosdefinido debe ser incluida en el Manifest de la aplicacioacuten

ltactivity androidname=MisPreferenciasandroidlabel=Mis preferenciasgt

ltactivitygt

Eso es todo lo que necesitamos para crear este tipo de actividades y mostrar una ventanacon las preferencias definidas en el layout Esta actividad puede ser iniciada comocualquier otra mediante un Intent ya sea a traveacutes de una llamada a startActivity obien a startActivityForResult

Intent i = new Intent(this MisPreferenciasclass)startActivityForResult(i MOSTRAR_PREFERENCIAS)

Los valores para las diferentes preferencias de la actividad son almacenadas en elcontexto de la aplicacioacuten Esto permite a cualquier componente de dicha aplicacioacutenincluyendo a cualquier actividad y servicio de la misma acceder a los valores tal como semuestra en el siguiente coacutedigo de ejemplo

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto) PENDIENTE hacer uso de meacutetodos get para obtener los valores

317 Shared Preference Change Listeners

Persistencia

23Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

El uacuteltimo elemento que nos queda por examinar en esta plataforma de Shared Preferenceses la interfaz onSharedPreferenceChangeListener que es utilizada para invocar unevento cada vez que una preferencia es antildeadida eliminada o modificada Para registrarListeners usamos un coacutedigo como el que se muestra a continuacioacuten en el que la parte maacutesimportante es sin duda el meacutetodo onSharedPreferenceChanged dentro del cualdeberemos determinar queacute preferencia ha cambiado de estado a partir de sus paraacutemetrospara llevar a cabo las acciones oportunas

public class MiActividad extends Activity implementsOnSharedPreferenceChangeListener

Overridepublic void onCreate(Bundle SavedInstanceState)

Registramos este objetoOnSharedPreferenceChangeListener

Context contexto = getApplicationContext()SharedPreferences prefs =

PreferenceManagergetDefaultSharedPreferences(contexto)prefsregisterOnSharedPreferenceChangeListener(this)

public void onSharedPreferenceChanged(SharedPreferences prefsString clave)

PENDIENTE comprobar queacute clave concreta ha cambiado ysu nuevo valor

para modificar la interfaz de una actividad o elcomportamiento de

alguacuten componente

Otra forma maacutes sencilla de controlar los cambios en las preferencias es registrar unlistener en el constructor de la actividad principal o aquella que haga uso de los valores depreferencias de la forma

Context contexto = getApplicationContext()SharedPreferences prefs =PreferenceManagergetDefaultSharedPreferences(contexto)

mListener = new OnSharedPreferenceChangeListener() public void onSharedPreferenceChanged(SharedPreferences

sharedPreferences String key)

if( keyequals(MICLAVE) )boolean valor =

sharedPreferencesgetBoolean(MICLAVE false)

prefsregisterOnSharedPreferenceChangeListener(mListener)

NotaEn este segundo ejemplo mListener es una variable miembro de la clase Para este caso esnecesario registrar el listener de esta forma porque sino el garbage collector lo eliminariacutea (debidoa particularidades en la programacioacuten de los listeners asociados a las SharedPreferences)

Persistencia

24Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

32 Proveedores de contenidos

Los proveedores de contenidos o ContentProvider proporcionan una interfaz parapublicar y consumir datos identificando la fuente de datos con una direccioacuten URI queempieza por content Son una forma maacutes estaacutendar que los adaptadores (como el quevimos en la sesioacuten anterior en la seccioacuten dedicada a SQLite) de desacoplar la capa deaplicacioacuten de la capa de datos

Podemos encontrar dos tipos principales de proveedores de contenidos en Android Losproveedores nativos son proveedores que el propio sistema nos proporciona para poderacceder a datos del dispostivo incluyendo audio viacutedeo imaacutegenes informacioacuten personaletc Por otra parte tambieacuten es posible que creemos nuestros propios proveedores con talde permitir a nuestra aplicacioacuten compartir datos con el resto del sistema En esta sesioacutenhablaremos de ambos tipos

321 Proveedores nativos

Android incluye una serie de proveedores de contenidos que nos pueden proporcionarinformacioacuten de diferentes tipos informacioacuten sobre nuestros contactos informacioacuten delcalendario e incluso ficheros multimedia Ejemplos de estos proveedores de contenidosnativos son el Browser CallLog ContactsContract MediaStore Settings y elUserDictionary Para poder hacer uso de estos proveedores de contenidos y acceder alos datos que nos proporcionan debemos antildeadir los permisos adecuados al ficheroAndroidManifestxml Por ejemplo para acceder al listiacuten telefoacutenico podemos antildeadir elpermiso correspondiente de la siguiente forma

ltuses-sdk androidminSdkVersion=8 gtltuses-permission androidname=androidpermissionREAD_CONTACTSgt

ltmanifestgt

Para obtener informacioacuten de un ContentProvider debemos hacer uso del meacutetodo query

de la clase ContentResolver El primero de los paraacutemetros de dicho meacutetodo seraacute la URIdel proveedor de contenidos al que deseamos acceder Este meacutetodo devuelve un cursorque nos permitiraacute iterar entre los resultados tal como se vio en la sesioacuten anterior en elapartado de cursores

ContentResolverquery(Uri uriString[] projectionString selectionString[] selectionArgsString sortOrder)

Por ejemplo la siguiente llamada nos proporcionaraacute un cursor que nos permitiraacute acceder atodos los contactos de nuestro teleacutefono moacutevil Para ello hemos hecho uso de la constante

Persistencia

25Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ContactsContractContactsCONTENT_URI que almacena la URI del proveedor decontenidos correspondiente Al resto de paraacutemetros se le ha asignado el valor null

ContentResolver cr = getContentResolver()Cursor cursor = crquery(ContactsContractContactsCONTENT_URI

null null null null)

NotaEn versiones anteriores a Android 20 las estructuras de datos de los contactos son diferentes y nose accede a esta URI

Una vez obtenido el cursor es posible mapear sus campos con alguacuten componente de lainterfaz graacutefica de la actividad De esta forma cualquier cambio que se produzca en elcursor se reflejaraacute automaacuteticamente en el componente graacutefico sin que sea necesario queprogramemos el coacutedigo que lo refresque Para esto tenemos que llamar al meacutetodosetNotificationUri del cursor con lo cual nos aseguraremos de que los listeners seraacutennotificados cuando se produzca alguacuten cambio en los datos referenciados por dicho cursor

cursorsetNotificationUri(crContactsContractContactsCONTENT_URI)

Para asignar un adaptador a una lista de la interfaz graacutefica de la actividad maacutesconcretamente una ListView utilizamos el meacutetodo setAdapter(Adapter)

ListView lv = (ListView)findViewById(RidListView01)SimpleCursorAdapter adapter = new SimpleCursorAdapter(

getApplicationContext()Rlayouttextviewlayoutcursornew String[]

ContactsContractContacts_IDContactsContractContactsDISPLAY_NAME

new int[]RidTextView1RidTextView2)

lvsetAdapter(adapter)

En este ejemplo los identificadores RidTextView1 y RidTextView2 se correspondencon views del layout que define cada fila de la ListView como se puede ver en elarchivo textviewlayoutxml que tendriacuteamos que crear

ltxml version=10 encoding=utf-8gt

ltLinearLayout xmlnsandroid=httpschemasandroidcomapkresandroidandroidorientation=horizontalandroidlayout_width=fill_parentandroidlayout_height=wrap_contentgt

ltTextView androidid=+idTextView1androidtextStyle=bold

Persistencia

26Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

androidems=2androidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltTextView androidid=+idTextView2androidtextStyle=boldandroidlayout_width=wrap_contentandroidlayout_height=wrap_contentgt

ltTextViewgtltLinearLayoutgt

La lista en siacute (identificada por RidListView01 en el presente ejemplo) estariacutea en otroXML layout por ejemplo en mainxml

Se debe tener en cuenta que en el caso de este ejemplo si quisieacuteramos obtener losnuacutemeros de teleacutefono de cada uno de nuestros contactos tendriacuteamos que recorrer el cursorobtenido a partir del proveedor de contenidos y por cada persona en nuestra agenda crearun nuevo cursor que recorriera los teleacutefonos ya que una persona puede tener asignadosvarios teleacutefonos

Puedes acceder a un listado completo de los proveedores de contenidos nativos existentesen Android consultando su manual de desarrollo Concretamente leyendo la seccioacutendedicada al paquete androidprovider enhttpdeveloperandroidcomreferenceandroidproviderpackage-summaryhtml

322 Proveedores propios crear un nuevo proveedor de contenidos

Para acceder a nuestras fuentes de datos propias de manera estaacutendar nos interesaimplementar nuestros propios ContentProvider Gracias a que proporcionamos nuestrosdatos a partir de una URI quien use nuestro proveedor de contenidos no deberaacutepreocuparse de doacutende provienen los datos indicados por dicha URI podriacutean provenir deficheros locales en la tarjeta de memoria de un servidor en Internet o de una base dedatos SQLite Dada una URI nuestro proveedor de contenidos deberaacute proporcionar en suinterfaz los meacutetodos necesarios para realizar las operaciones baacutesicas en una base de datosinsercioacuten lectura actualizacioacuten y borrado

Para crear un nuevo proveedor de contenidos definimos una subclase deContentProvider Utilizaremos una sobrecarga del meacutetodo onCreate para crear einicializar la fuente de datos que queramos hacer puacuteblica a traveacutes de dicho proveedor Unesqueleto de subclase de ContentProvide podriacutea ser el siguiente

public class MiProveedor extends ContentProvider

Overridepublic boolean onCreate()

Inicializar la base de datosreturn true

Dentro de la clase deberemos crear un atributo puacuteblico y estaacutetico de nombreCONTENT_URI en el cual almacenaremos el URI completo del proveedor que estamos

Persistencia

27Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creando Este URI debe ser uacutenico por lo que una buena idea puede ser tomar como baseel nombre de paquete de nuestra aplicacioacuten La forma general de un URI es la siguiente

content[NOMBRE_PAQUETE]provider[NOMBRE_APLICACION][RUTA_DATOS]

Por ejemplo

contentesuajtechandroidprovidermiaplicacionelementos

En general (y por simplicidad) no es necesario antildeadir la cadena provider al nombre delpaquete por lo que podemos utilizar directamente el nombre del paquete seguido de laruta de datos de la forma

contentesuajtechandroidmiaplicacionelementos

Estos URIs se pueden presentar en dos formas La URI anterior representa una consultadirigida a obtener todos los valores de ese tipo (en este caso todos los elementos) Si a laURI anterior antildeadimos un sufijo [NUMERO_FILA] estamos representando una consultadestinada a obtener un uacutenico elemento (por ejemplo el quinto elemento en el siguienteejemplo)

contentesuajtechandroidprovidermiaplicacionelementos5

NotaSe considera una buena praacutectica de programacioacuten el proporcionar acceso a nuestro proveedor decontenidos utilizando cualquiera de estas dos formas

La forma maacutes simple de determinar cuaacutel de las dos formas anteriores de URI se utilizoacutepara hacer una peticioacuten a nuestro proveedor de contenidos es mediante un Uri MatcherCrearemos y configuramos un elemento Uri Matcher para analizar una URI y determinara cuaacutel de las dos formas se corresponde En el siguiente coacutedigo se muestra el coacutedigobaacutesico que deberemos emplear para implementar este patroacuten

public class MiProveedor extends ContentProvider

private static final String miURI =contentesuajtechandroidprovidermiaplicacionelementos

public static final Uri CONTENT_URI = Uriparse(miUri)

Creamos las constantes que nos van a permitir diferenciar entre los dos tipos distintos de peticiones a partir de la URIprivate static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

Inicializamos el objeto UriMatcher Una URI que termine en elementos se corresponderaacute con una consulta para todas las filas mientras que una URI con el sufijo elementos[FILA] representaraacute una uacutenica filastatic

uriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidprovidermiaplicacion

elementos TODAS_FILAS)

Persistencia

28Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

uriMatcheraddURI(esuajtechandroidprovidermiaplicacionelementos UNA_FILA)

Overridepublic boolean onCreate()

PENDIENTE inicializar la base de datosreturn true

Esta misma teacutecnica se puede emplear para proporcionar diferentes alternativas de URIpara diferentes subconjuntos de datos como por ejemplo diferentes tablas en una base dedatos a traveacutes del mismo proveedor de contenidos

323 Proveedores propios crear la interfaz de consultas

Para permitir realizar operaciones con los datos publicados a traveacutes de nuestro proveedorde contenidos deberemos implementar los meacutetodos delete insert update y query

para el borrado insercioacuten actualizacioacuten y realizacioacuten de consultas respectivamenteEstos meacutetodos son la interfaz estaacutendar utilizada con proveedores de contenidos de formaque para compartir datos entre aplicaciones no se tendraacuten interfaces distintos paradiferentes fuentes de datos

Lo maacutes habitual suele ser implementar un proveedor de contenidos como un mecanismopara permitir el acceso a una base de datos SQLite privada a una aplicacioacuten aunque atraveacutes de estos meacutetodos indicados se podriacutea en principio acceder a cualquier fuente dedatos

El siguiente coacutedigo extiende el ejemplo anterior (MiProveedor) y muestra el esqueleto deestos meacutetodos dentro de una subclase de ContentProvider Obseacutervese que se hace usodel objeto UriMatcher para determinar que tipo de consulta se estaacute realizando

Overridepublic Cursor query(Uri uri

String[] projectionString selectionString[] selectionArgsString sort)

Si se trata de una consulta para obtener una uacutenica filalimitamos

los resultados a obtener de la fuente de datos por medio de una claacuteusula whereswitch(UriMatchermatch(uri))

case UNA_FILA PENDIENTE modifica la sentencia SELECT

mediante una claacuteusula where obteniendo el identificador de fila

como numeroFila = urigetPathSegments()get(1))

return null

Override

Persistencia

29Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public Uri insert(Uri _uri ContentValues _valores) long idFila = [ Antildeadir un nuevo elemento ]

Devolvemos la URI del elemento recieacuten antildeadidoif (idFila gt 0)

return ContentUriswithAppendendId(CONTENT_URI idFila)

throw new SQLException(No se pudo antildeadir un nuevo elemento a +_uri)

Overridepublic int delete(Uri uri String where String[] whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

Overridepublic int update(Uri uri ContentValues valores String where String[]whereArgs)

switch(uriMatchermatch(uri)) case TODAS_FILAScase UNA_FILAdefault

throw new IllegalArgumentException(URI nosoportada + uri)

324 Proveedores propios tipo MIME

El uacuteltimo paso para crear un proveedor de contenidos es definir el tipo MIME queidentifica el tipo de datos que devuelve el proveedor Debemos sobrecargar el meacutetodogetType para que devuelva una cadena que identifique nuestro tipo de datos de manerauacutenica Se deberiacutean definir dos tipos posibles uno para el caso de una uacutenica fila y otro parael caso de devolver todos los resultados Se debe utilizar una sintaxis similar a la delsiguiente ejemplo

bull Un uacutenico elementovndesuajtechandroidmiaplicacioncursoritemmiproveedorcontent

bull Todos los elementosvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

Como se puede observar la cadena comienza siempre por vnd A continuacioacutentendriacuteamos el nombre del paquete de nuestra aplicacioacuten Lo siguiente es cursor ya quenuestro proveedor de contenidos estaacute devolviendo datos de tipo Cursor Justo antes de labarra pondremos item en el caso del tipo MIME para un uacutenico elemento y dir para elcaso del tipo MIME para todos los elementos Finalmente tras la barra pondremos elnombre de nuestra clase (en minuacutesculas) seguido de content En el siguiente coacutedigo deejemplo se muestra coacutemo sobrecargar getType seguacuten la URI

Persistencia

30Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Overridepublic String getType(Uri _uri)

switch (uriMatchermatch(_uri)) case TODAS_FILAS

returnvndesuajtechandroidmiaplicacioncursordirmiproveedorcontent

case UNA_FILAreturn

vndesuajtechandroidmiaplicacioncursoritemmiproveedorcontentdefault

throw new IllegalArgumentException(URI nosoportada + _uri)

325 Proveedores propios registrar el proveedor

No debemos olvidar incorporar nuestro proveedor de contenidos al Manifest de laaplicacioacuten una vez que lo hemos completado Esto se haraacute por medio del elementoprovider cuyo atributo authorities podremos utilizar para especificar la URI base talcomo se puede ver en el siguiente ejemplo

ltprovider androidname=MiProveedorandroidauthorities=esuajtechandroidmiaplicaciongt

326 Content Resolvers

Aunque en la seccioacuten de proveedores nativos ya se ha dado alguacuten ejemplo de coacutemo haceruso de un Content Resolver para realizar una consulta ahora que hemos podido observaren detalle la estructura de un proveedor de contenidos propio es un buen momento paraver en detalle coacutemo realizar cada una de las operaciones que eacutestos permiten

El contexto de una aplicacioacuten incluye siempre una instancia de la claseContentResolver a la cual se puede acceder mediante el meacutetodo getContentResolver

ContentResolver cr = getContentResolver()

Esta clase incorpora diversos meacutetodos para realizar consultas a proveedores decontenidos Cada uno de estos meacutetodos acepta como paraacutemetro una URI que indica conqueacute proveedor de contenidos se desea interactuar

Las consultas realizadas sobre un proveedor de contenidos recuerdan a las consultasrealizadas sobre bases de datos Los resultados de las consultas se devuelven comoobjetos de la clase Cursor Se pueden extraer valores del cursor de la misma forma en laque se explicoacute en el apartado de bases de datos de la sesioacuten anterior El meacutetodo query dela clase ContentResolver acepta los siguientes paraacutemetros

bull La URI del proveedor del contenidos al que se desea realizar la consultabull Una proyeccioacuten que enumera las columnas que se quieren incluir en los resultados

obtenidosbull Una claacuteusula where que define las filas a obtener Se pueden utilizar comodines que

Persistencia

31Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

se reemplazaraacuten por valores incluidos en el siguiente paraacutemetrobull Una matriz de cadenas que se pueden utilizar para reemplazar los comodines en la

claacuteusula where anteriorbull Una cadena que describa la ordenacioacuten de los elementos devueltos

El siguiente coacutedigo muestra un ejemplo

ContentResolver cr = getContentResolver() Devolver todas las filasCursor todasFilas = crquery(MiProveedorCONTENT_URI null null nullnull) Devolver todas columnas de las filas para las que la columna 3 tiene un valor determinado ordenadas seguacuten el valor de la columna 5String where = COL3 + = + valorString orden = COL5Cursor algunasFilas = crquery(MiProveedorCONTENT_URI null where nullorden)

327 Otras operaciones con Content Resolvers

Para realizar otro tipo de operaciones con los datos de un proveedor de contenidosharemos uso de los meacutetodos delete update e insert de un objeto ContentResolver

Con respecto a la insercioacuten la clase ContentResolver proporciona en su interfaz dosmeacutetodos diferentes insert y bulkInsert Ambos aceptan como paraacutemetro la URI delproveedor de contenidos pero mientras que el primer meacutetodo tan solo toma comoparaacutemetro un objeto de la clase ContentValues (ya vistos en la seccioacuten de bases dedatos) el segundo toma como paraacutemetro una matriz de elementos de este tipo El meacutetodoinsert devolveraacute la URI del elemento recieacuten antildeadido mientras que bulkInsert

devolveraacute el nuacutemero de filas correctamente insertadas

Obtener el Content ResolverContentResolver cr = getContentResolver()

Crear una nueva fila de valores a insertarContentValues nuevosValores = new ContentValues()

Asignar valores a cada columnanuevosValoresput(NOMBRE_COLUMNA nuevoValor)[ Repetir para cada columna ]

Uri miFilaUri = crinsert(MiProveedorCONTENT_URI nuevosValores)

Insertamos ahora una matriz de nuevas filasContentValues[] arrayValores = new ContentValues[5] PENDIENTE rellenar el array con los valores correspondientesint count = crbulkInsert(MiProveedorCONTENT_URI arrayValores)

Para el caso del borrado hacemos uso del meacutetodo delete del Content Resolver pasandocomo paraacutemetro la URI de la fila que deseamos eliminar de la fuente de datos Otraposibilidad es incluir una claacuteusula where para eliminar muacuteltiples filas Ambas teacutecnicas semuestran en el siguiente ejemplo

ContentResolver cr = getContentResolver()

Persistencia

32Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Eliminar una fila especiacuteficacrdelete(miFilaUri null null) Eliminar las primeras cinco filasString where = _id lt 5crdelete(MiProveedorCONTENT_URI where null)

Una actualizacioacuten se lleva a cabo mediante el meacutetodo update del objeto ContentResolver Este meacutetodo toma como paraacutemetro la URI del proveedor de contenidosobjetivo un objeto de la clase ContentValues que empareja nombres de columna convalores actualizados y una claacuteusula where que indica queacute filas deben ser actualizadas Elresultado de la actualizacioacuten seraacute que en cada fila en la que se cumpla la condicioacuten de laclaacuteusula where se cambiaraacuten los valores de las columnas especificados por el objeto de laclase ContentValues tambieacuten se devolveraacute el nuacutemero de filas correctamenteactualizadas A continuacioacuten se muestra un ejemplo

ContentValues nuevosValores = new ContentValues()

Utilizamos el objeto ContentValues para especificar en queacute columnas queremos que se produzca un cambio y cuaacutel debe ser el nuevo valor de dichas columnasnuevosValuesput(NOMBRE_COLUMNA nuevoValor)

Aplicamos los cambios a las primeras cinco filasString where = _id lt 5

getContentResolver()update(MiProveedorCONTENT_URI nuevosValores wherenull)

Persistencia

33Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

4 Ejercicios - Persistencia en Android proveedores de contenidos ySharedPreferences

41 Compartir datos entre actividades con Shared Preferences (075 puntos)

Descarga de las plantillas el proyecto Dni Dicho proyecto estaacute compuesto de dosactividades Formulario y Resumen El objetivo del ejercicio es conseguir que al pulsar elbotoacuten Siguiente en la actividad Formulario se muestre en la actividad Resumen cuaacutelesfueron los datos introducidos

En este ejercicio vamos a hacer uso de SharedPreferences para pasar los datos de unaactividad a la siguiente Al pulsar en Siguiente en Formulario deberemos guardar el valorde todos los campos mediante esta plataforma Al mostrarse la actividad Resumendeberemos leer estos datos tambieacuten a partir de SharedPreferences para mostrarlos porpantalla

Por uacuteltimo crea un meacutetodo que valide el DNI (debe tratarse de una secuencia de ochodiacutegitos entre 0 y 9 y una letra al final) Si se pulsa Siguiente y el DNI no tiene el formatocorrecto no se deberaacute pasar a la actividad Resumen En lugar de esto se mostraraacute unToast en pantalla con un mensaje de error

42 Actividad de preferencias (075 puntos)

En este ejercicio seguimos trabajando con el proyecto del ejercicio anterior Vamos aantildeadir un menuacute de opciones que permita escoger en queacute momento se comprobaraacute lavalidez del DNI Para ello antildeade en primer lugar un menuacute a la actividad Formulario conuna uacutenica opcioacuten cuyo nombre seraacute Opciones

Al seleccionar esta opcioacuten se deberaacute mostrar una actividad de preferencias cuyo aspectoseraacute el siguiente

Persistencia

34Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Preferencias de la aplicacioacuten Dni

El significado de las opciones seraacute el siguiente

bull En el campo con esta opcioacuten seleccionada al pulsar sobre el campo de DNI en elformulario deberaacute mostrarse un Toast indicando si la sintaxis del DNI es correcta ono

bull Al pulsar si se desactiva esta opcioacuten (que deberaacute estar activada por defecto) no seharaacute la comprobacioacuten del DNI al pulsar el botoacuten Siguiente por lo que siempre seraacuteposible pasar de la actividad Formulario a la actividad Resumen sea cual sea elformato del DNI introducido

bull El uacuteltimo check box se encontraraacute siempre deshabilitado iquestQueacute atributo debemosutilizar para conseguir esto

Crea dos variables booleanas llamadas enElCampo y alPulsar Su valor dependeraacute delestado de los checkboxes anteriores y se actualizaraacute por medio del eventoonSharedPreferenceChange Utiliza el valor de estas variables en el coacutedigo de tuactividad para que esta tenga el comportamiento indicado

43 Proveedor de contenidos propio (075 puntos)

Vamos a implementar otra forma de acceder a la base de datos de usuarios de laaplicacioacuten BaseDatos desarrollada durante los ejercicios de la sesioacuten anterior siguiendoesta vez el patroacuten de disentildeo ContentProvider de Android Seguiremos trabajando puescon dicho proyecto

bull Creamos una nueva clase llamada UsuariosProvider que herede deContentProvider Esto nos obligaraacute a sobrecargar una serie de meacutetodos abstractosAntes de implementar la query vamos a configurar el provider

bull Antildeadimos algunos campos tiacutepicos de los content provider

Persistencia

35Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

public static final Uri CONTENT_URI =Uriparse(contentesuajtechandroidbasedatosusuarios)

private static final int TODAS_FILAS = 1private static final int UNA_FILA = 2

private static final UriMatcher uriMatcher

staticuriMatcher = new UriMatcher(UriMatcherNO_MATCH)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

TODAS_FILAS)uriMatcheraddURI(esuajtechandroidbasedatos usuarios

UNA_FILA)

bull Vamos a acceder a la misma base de datos de usuarios utilizada en los ejercicios de lasesioacuten anterior pero no vamos a hacerlo a traveacutes del adaptador que tuvimos queimplementar sino que vamos a copiar de eacutel el coacutedigo que nos haga falta Copia loscampos que definen el nombre de la base de datos de la tabla de las columnas laversioacuten asiacute como la referencia al contexto y a la base de datos La sentenciacompilada del insert ya no va a hacer falta Inicializa los valores necesarios en elconstructor Para inicializar la referencia a la base de datos vamos a utilizar una vezmaacutes MiOpenHelper Podemos copiarlo del adaptador que ya implementamos en losejercicios de la sesioacuten anterior

bull Implementa de forma apropiada el getType para devolver un tipo MIME diferenteseguacuten si se trata de una URI de una fila o de todas las filas Para ello ayuacutedate deluriMatcher

bull Implementa el meacutetodo query Simplemente se trata de devolver el cursor queobtenemos al hacer una consulta a la base de datos SQLite Algunos de los paraacutemetrosque le pasaremos los recibimos como paraacutemetros del meacutetodo del provider Los que notengamos iraacuten con valor null

bull Aunque no tenemos todos los meacutetodos del UsuariosProvider implementadospodemos probarlo Para ello debemos registarlo en el AndroidManifestxml

ltprovider androidname=UsuariosProvider

androidauthorities=esuajtechandroidbasedatosgtltapplicationgt

ltmanifestgt

bull En la clase Main se antildeadioacute codigo para insertar una serie de valores en la base dedatos y mostrarlos en el campo de texto Manteniendo este coacutedigo vamos a antildeadir alcampo de texto el resultado obtenido con la consulta del UsuariosProvider paracomprobar que tanto el adaptador de la base de datos como el proveedor nosdevuelven el mismo resultado

44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)

Porque es la forma estaacutendar que establece Android de acceder a contenidos Ademaacutes elproveedor de contenidos nos permitiraacute notificar al ContentResolver de los cambiosocurridos Asiacute componentes en la pantalla podraacuten refrescarse de forma automaacutetica

Persistencia

36Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Utiliza el proyecto ProveedorContenidos de las plantillas Implementa la insercioacuten enel proveedor de contenidos Prueacutebala insertando algunos usuarios de ejemplo en laclase Main Implementa tambieacuten el OnClickListener del botoacuten que inserta nuevosusuarios El nombre del nuevo usuario iraacute indicado en el EditText

bull Comprueba que la insercioacuten funciona y que gracias a la siguiente liacutenea y al estarusando un proveedor de contenidos la lista se actualiza automaacuteticamente cuandoocurre alguacuten cambio sin necesidad de pedir expliacutecitamente la actualizacioacuten al pulsarel botoacuten

cursorsetNotificationUri(cr UsuariosProviderCONTENT_URI)

NotaEsto no va a funcionar si se notifica que se ha producido un cambio al insertar o borrar unelemento Para ello usamosgetContext()getContentResolver()notifyChange(UsuariosProviderCONTENT_URI null)

bull Implementa el meacutetodo delete del proveedor de contenidos Prueacutebalo en la claseMain Termina de implementar el onCreateContextMenuListener que se ejecutaraacutecada vez que se haga una pulsacioacuten larga sobre alguna entrada de la lista Compruebaque funciona (eliminando el usuario correspondiente y no otro)

Persistencia

37Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

5 Persistencia de datos en iOS Ficheros y SQLite

51 Introduccioacuten

La persistencia de datos en iOS se realiza en una pequentildea memoria flash que tienenuestro dispositivo la cual es equivalente a un disco duro de tamantildeo limitado Los datosque ahiacute se almacenen se conservaraacuten aunque el dispositivo se apague Por motivos deseguridad el SO de iOS no permite que las aplicaciones accedan directamente a lamemoria interna pero a cambio existe una API completa de acceso a la porcioacuten dememoria que le corresponde a la app que desarrollemos

En una aplicacioacuten iOS encontraremos baacutesicamente 4 modos de almacenamiento deinformacioacuten de forma persistente

bull Ficheros de propiedades (plist)bull Bases de datos embebidas SQLitebull Datos de usuario (User Defaults)bull Core Data

En esta primera sesioacuten veremos coacutemo usar los ficheros de texto (Property Lists) y elalmacenamiento en base de datos de tipo SQLite

52 Ficheros plist (Property Lists)

El almacenamiento de datos en ficheros de propiedades es uno de los maacutes usados en eldesarrollo de las aplicaciones para iOS Se usa principalmente para guardar datos deconfiguracioacuten de la aplicacioacuten u otra informacioacuten poco compleja y acceder a ella deforma sencilla Si estamos desarrollando un juego podemos usarlos para la definicioacuten delos niveles almacenar las puntuaciones del jugador etc

521 Leyendo datos desde ficheros plist

Los ficheros plist (Property Lists) tienen un formato XML aunque XCode nosproporciona una interfaz que nos permite acceder a ellos de forma muy sencilla Paraentender el funcionamiento de este tipo de ficheros vamos a realizar un ejemplo en el queprimero crearemos un fichero plist lo completaremos con datos de ejemplo y despueacutes loleeremos para mostrarlo por pantalla

Un ejemplo de un fichero plist podriacutea ser el siguiente

Persistencia

38Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Fichero plist

Antes de nada creamos un proyecto nuevo en XCode de tipo Window-based

Application al que llamaremos sesion03-ejemplo1 Para crear un fichero plistdebemos hacer click derecho sobre el directorio Supporting Files de nuestro proyectoy seleccionamos iOS gt Resource gt Property List Lo guardaremos con el nombreconfigUsuario Seguidamente ya podemos acceder al fichero recieacuten creado haciendoclick sobre el en este momento XCode nos mostraraacute un pequentildeo editor totalmente vacioen el que iremos rellenando con datos Podemos verlo tambieacuten en formato XML sihacemos click derecho sobre el y seleccionamos Open As gt Preview

Para empezar a rellenar el fichero que acabamos de crear hacemos click derecho dentrode su contenido (ahora vacio) y seleccionamos Add row Entonces nos apareceraacute unafila vaciacutea Dentro del campo Key escribimos la clave un nombre descriptivo quedeberemos utilizar maacutes adelante para acceder a los datos En nuestro caso vamos aescribir config como clave Dentro de la columna Type seleccionamosDictionary Veremos que aparece una flecha en el lado izquierdo que indica quepueden haber subniveles dentro de nuestro diccionario

Ahora antildeadimos un nuevo item que cuelgue del diccionario que acabamos de crear paraello hacemos click sobre la flecha de la izquierda esta se giraraacute hacia abajo entonceshacemos click ahora sobre el siacutembolo + (maacutes) Nos apareceraacute una nueva liacutenea que cuelgadel diccionario

En esta nueva liacutenea dentro del campo key escribimos nombre el campo type lodejamos como de tipo String ya que va a ser una cadena de texto y en la columna deValue escribimos nuestro nombre por ejemplo Javi

Ahora vamos a antildeadir otro campo nuevo en nuestra configuracioacuten para ello hacemosclick sobre el siacutembolo + y nos apareceraacute una nueva liacutenea justo al mismo nivel que laanterior y colgando del diccionario Dentro del campo de Key escribimos ciudad yen Value escribimos Alicante El campo de Type lo dejamos como String

Persistencia

39Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora dentro de nuestro fichero plist vamos a indicar los dispositivos que dispone elusuario para ello creamos una nueva linea dentro del diccionario y le pondremos comoclave dispositivos Seleccionamos el tipo Array y seguimos los mismos pasosque al crear el diccionario para ello hacemos click sobre la flecha de la izquierda ydespueacutes sobre el siacutembolo + (maacutes) Ahora nos apareceraacute una nueva fila con clave Item 0Seleccionamos el tipo String y en el campo Value iPhone

Ahora repetimos el paso 3 veces indicando como values iPad Android y

Blackberry

Una vez seguidos todos estos pasos tendremos nuestro fichero de propiedades listo Debede quedar como se muestra a continuacioacuten

Fichero plist terminado

Este fichero se almacenaraacute dentro del directorio de bundle cuando compilemos laaplicacioacuten En el caso de que queramos escribir en el debermos almacenar el ficherodentro del directorio de Documents del binario

Ahora desde nuestro coacutedigo vamos a acceder a eacutel y a mostrarlo por pantalla para elloescribimos el siguiente coacutedigo dentro del meacutetodo didFinishLaunchingWithOptions dela clase delegada

Cargamos el fichero PLISTNSString mainBundlePath = [[NSBundle mainBundle] bundlePath]NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]NSDictionary diccionario = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionario delraiz

NSString nombre = [dicConfig objectForKeynombre]

Persistencia

40Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

[selfwindow makeKeyAndVisible]return YES

Si todo ha ido correctamente deberemos obtener la siguiente salida por la consola

Lista de dispositivos

522 Escribiendo datos en ficheros plist

Partiendo del ejemplo anterior vamos a escribir ahora en el fichero para ello primerodebemos comprobar que el fichero plist se encuentre dentro del directorio de Documents

del dispositivo en el caso de que no lo esteacute (por ejemplo si es la primera vez que arrancala aplicacioacuten) debemos copiarlo a ese directorio Una vez que tenemos el fichero dentrodel directorio de documentos ya podemos escribir en el

Para realizar todo el proceso de comprobacioacuten de la existencia del fichero dentro deldirectorio de documentos copiarlo en ese directorio y leer los datos debemos de sustituirel coacutedigo que hay dentro del meacutetodo didFinishLaunchingWithOptions de la clasedelegada por este otro

AtencioacutenPara que funcione la escritura en un fichero plist este debe de estar dentro del directorio deDocuments de nuestro dispositivo iOS no en el boundle

NSDictionary diccionarioRaiz

Ruta del directorio de documentos de nuestro dispositivoNSArray paths =

NSSearchPathForDirectoriesInDomains(NSDocumentDirectoryNSUserDomainMask YES)

if ([paths count] gt 0)

Persistencia

41Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

Primero comprobamos si existe el fichero en eldirectorio de documentos

BOOL fileExists = [[NSFileManager defaultManager]fileExistsAtPathdocumentsFilename]if (fileExists)

NSLog(Fichero encontrado en directorio de documentosOK

documentsFilename)

Si existe ya cargamos los datos desde aquidirectamente

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

else

NSLog(No se encuentra el fichero configUsuarioplisten

el directorio de documentos-gtLo creamos)

Si no existe primero cargamos el fichero PLISTdesde el Boundle

NSString mainBundlePath = [[NSBundle mainBundle]bundlePath]

NSString plistPath = [mainBundlePathstringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFileplistPath]

Y despues escribimos los datos cargados (eldiccionario completo)

a un fichero nuevo de la carpeta de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

NSLog(plist de configUsuario creado)

Cargamos el Diccionario inicial que esta en el raiz delfichero

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cargamos los campos que estan dentro del diccionariodel raiz

NSString nombre = [dicConfig objectForKeynombre]NSString ciudad = [dicConfig objectForKeyciudad]NSArray dispositivos = [dicConfig

objectForKeydispositivos]

Mostramos por consola los datos obtenidosNSLog(Nombre nombre)NSLog(Ciudad ciudad)

for (NSString dispositivo in dispositivos)NSLog(Dispositivo dispositivo)

Persistencia

42Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Por ultimo liberamos la memoria del diccionario raiz[diccionarioRaiz release]

[selfwindow makeKeyAndVisible]return YES

Para comprobar el funcionamiento arrancamos la aplicacioacuten y veremos que la primera vezse crearaacute el fichero dentro de la carpeta de Documents de nuestro dispositivo y en lassiguientes ya no se crearaacute y simplemente lo leeraacute desde la carpeta de Documents

Ahora que ya tenemos la gestioacuten de ficheros baacutesica completada vamos a escribir sobre elalguacuten dato Para ello simplemente escribimos el siguiente fragmento de coacutedigo justo antesde la liacutenea [selfwindow makeKeyAndVisible]

Cambiamos el nombre y lo guardamos en el fichero Ruta del directorio de documentos de nuestro dispositivo

if ([paths count] gt 0)

Cargamos el fichero desde el directorio dedocumentos del dispositivo

NSString documentsDirectory = [paths objectAtIndex0]NSString documentsFilename = [documentsDirectorystringByAppendingPathComponentconfigUsuarioplist]

diccionarioRaiz = [[NSDictionary alloc]initWithContentsOfFiledocumentsFilename]

NSDictionary dicConfig = [diccionarioRaizobjectForKeyconfig]

Cambiamos el valor del nombre en el diccionario[dicConfig setValueOtro nombre forKeynombre]

Escribimos todo el diccionario de nuevo al fichero del directorio de documentos[diccionarioRaiz writeToFiledocumentsFilename

atomicallyYES]

Por ultimo liberamos el diccionario de lamemoria

[diccionarioRaiz release]

Lo que hemos hecho en el coacutedigo anterior es cargar todo el diccionario de nuevo desde eldirectorio de documentos modificar los datos que queremos cambiar o incluso antildeadir ydespueacutes guardar el diccionario en el fichero

Volvemos a arrancar la aplicacioacuten y veremos que ha cambiado el campo de nombre

Nota

Persistencia

43Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

La lectura y escritura de datos en un fichero plist se debe de hacer de una soacutela vez nunca porpartes Siempre se debe de cargar o escribir un diccionario NSDictionary como elementoprincipal del fichero

53 SQLite

SQLite es una base de datos relacional ligera con la que se puede trabajar de formasencilla mediante sentencias SQL La base de datos se almacenaraacute en un fichero dentrode nuestra aplicacioacuten Este meacutetodo de persistencia puede ser el adecuado cuando los datosa almacenar esteacuten en un formato de tablas filas y columnas y se necesite que los datossean accesibles de forma raacutepida

531 Herramientas disponibles

Para explicar el funcionamiento de las librerias de SQLite de Cocoa Touch vamos a seguirun ejemplo completo Comenzamos creando la base de datos para ello podemos utilizaruno de los frontends que facilitan la gestioacuten del SQLite uno bastante sencillo yrecomendado es el SQLite Database Browser y otro es fmdb

Nosotros utilizaremos el primero (SQLite Database Browser) Abrimos la aplicacioacuten yhacemos click sobre el botoacuten de crear nueva base de datos que llamaremospersonasDBsqlite Seguidamente creamos las tablas para simplificar en nuestroejemplo crearemos una soacutela tabla llamada personas Esta tabla tendraacute las siguientescolumnas id (NUMERIC) nombre (TEXT) apellidos (TEXT) dni (TEXT) edad

(NUMERIC) y localidad (TEXT)

Persistencia

44Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Estructura de la Base de Datos

Una vez creada la tabla pasamos a insertar datos dentro de ella para ello abrimos lapestantildea de Browse Data y hacemos click en New Record Cuando tengamos al menos 3registros metidos dentro de la tabla ya podemos guardar la base de datos Hacemos clicksobre guardar le ponemos el nombre que queramos y elegimos cualquier directorio delMAC

Datos de la Base de Datos

532 Lectura de datos

Una vez que tenemos la base de datos SQLite creada ya podemos empezar a escribir elcoacutedigo para leer y mostrar los datos dentro de nuestra aplicacioacuten

Primero empezamos creando un proyecto nuevo en XCode usando la plantilla basada envistas View-based application y lo llamaremos sesion03-ejemplo2 Ahora antildeadimosel framework de sqlite a nuestro proyecto para ello hacemos click sobre el raiz delproyecto y seleccionamos la pestantildea Build Phases Desplegamos el listado de Link

Binary With Libraries y hacemos click sobre el botoacuten (+) El el listadoseleccionamos el framework libsqlite3dylib y hacemos click en Add

Persistencia

45Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Antildeadiendo el framework libsqlite3dylib

Ahora antildeadimos a nuestro proyecto la base de datos que hemos creado anteriormentepara ello arrastramos el fichero personasDBsqlite a nuestro directorio Supporting

Files Asegurate de seleccionar la opcioacuten de Copy items to destination groups

folder (if needed) para que se copie el fichero dentro de la carpeta del proyecto

Para acelerar el proceso de lectura de la base de datos y ahorrar memoria vamos a cargarprimero todos los datos en objetos para ello vamos a crear una clase que se encargue dealmacenar los datos Hacemos click derecho sobre el raiz del proyecto New file gtObjective-C class seleccionamos NSObject como clase principal y le damos a NextEscribimos como nombre de la clase Persona y hacemos click en save

Personah

imports iniciales

interface Persona NSObject int _uIdNSString _nombreNSString _apellidos

Persistencia

46Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSInteger _edadNSString _localidad

property (nonatomic assign) int uIdproperty (nonatomic copy) NSString nombreproperty (nonatomic copy) NSString apellidosproperty (nonatomic) NSInteger edadproperty (nonatomic copy) NSString localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidos edad(NSInteger)edadlocalidad(NSString )localidad

end

Personam

import Personah

implementation Persona

synthesize uId = _uIdsynthesize nombre = _nombresynthesize apellidos = _apellidossynthesize edad = _edadsynthesize localidad = _localidad

- (id)initWithUid(int)uId nombre(NSString )nombreapellidos(NSString )apellidosedad(NSInteger)edad localidad(NSString )localidad

if ((self = [super init])) selfuId = uIdselfnombre = nombreselfapellidos = apellidosselfedad = edadselflocalidad = localidad

return self

- (void) dealloc selfnombre = nilselfapellidos = nilselflocalidad = nil[super dealloc]

end

Una vez creada la clase Persona vamos a crear la clase encargada de manejar la basede datos sqlite3 Otra opcioacuten seriacutea cargar todos los datos directamente desde la clasedelegada al arrancar la aplicacioacuten pero no es recomendable ya que si en alguacuten momentotenemos que cambiar de motor de base de datos lo tendremos maacutes complicado

Persistencia

47Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creamos entonces una clase dentro de nuestro proyecto llamada BDPersistencia queherede de NSObject y escribimos el siguiente coacutedigo

BDPersistenciah

imports iniciales

interface BDPersistencia NSObject sqlite3 _database

+ (BDPersistencia)database- (NSArray )arrayPersonas

end

BDPersistenciam

import BDPersistenciahimport Personah

implementation BDPersistencia

static BDPersistencia _database

+ (BDPersistencia)database if (_database == nil)

_database = [[BDPersistencia alloc] init]return _database

- (id)init if ((self = [super init]))

NSString sqLiteDb = [[NSBundle mainBundle]pathForResourcepersonasDB

ofTypesqlite]

if (sqlite3_open([sqLiteDb UTF8String]amp_database) = SQLITE_OK)

NSLog(Fallo al abrir la BD)

return self

- (void)dealloc sqlite3_close(_database)[super dealloc]

- (NSArray )arrayPersonas

NSMutableArray arraySalida = [[[NSMutableArray alloc]init]

autorelease]

Persistencia

48Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

NSString query = SELECT id nombre apellidoslocalidad edad

FROM personas ORDER BY apellidos DESC

sqlite3_stmt statementif (sqlite3_prepare_v2(_database [query UTF8String]

-1ampstatement nil)

== SQLITE_OK) while (sqlite3_step(statement) == SQLITE_ROW)

int uniqueId = sqlite3_column_int(statement0)

char nombreChar = (char )sqlite3_column_text(statement 1)

char apellidosChar = (char )sqlite3_column_text(statement 2)

char localidadChar = (char )sqlite3_column_text(statement 3)

int edad = sqlite3_column_int(statement 4)

NSString nombre = [[NSString alloc]initWithUTF8StringnombreChar]NSString apellidos = [[NSString alloc]initWithUTF8StringapellidosChar]NSString localidad = [[NSString alloc]initWithUTF8StringlocalidadChar]Persona persona = [[Persona alloc]initWithUiduniqueId nombrenombreapellidosapellidos edadedad

localidadlocalidad]

[arraySalida addObjectpersona][nombre release][apellidos release][localidad release][persona release]

sqlite3_finalize(statement)

return arraySalida

end

Con esto ya podemos leer de nuestra base de datos las personas Para ver que funcionatodo a la perfeccioacuten escribimos el siguiente coacutedigo dentro de la clase delegada en elmeacutetodo applicationDidFinishLaunching Antes debemos incluir en la parte superiorlas clases creadas

import BDPersistenciahimport Personah

NSArray personas = [BDPersistenciadatabase]arrayPersonas

for (Persona persona in personas) NSLog(d d personauId

personanombrepersonaapellidos personalocalidad personaedad)

Persistencia

49Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Si ejecutamos el proyecto obtendremos la siguiente salida por consola

Listado de personas

533 Mostrando los datos en una tabla

Una vez que tenemos el coacutedigo para obtener todas las filas de la tabla de personas de labase de datos queda lo maacutes faacutecil mostrarlas dentro de una vista de la aplicacioacuten En estecaso utilizaremos la vista de tabla UITableView

Comenzamos creando una nueva vista en nuestro proyecto para ello hacemos clickderecho sobre el raiz y seleccionamos New File gt UIVIewController SubclassAsegurate de marcar Subclass of UITableViewController y la opcoacuten de With

XIB for user interface Hacemos click en Next y guardamos el fichero comoPersonasViewController por ejemplo

Ahora abrimos el fichero PersonasViewControllerh y antildeadimos un NSArray queseraacute el que almacene las personas de la base de datos

imports iniciales

interface PersonasViewController UITableViewController

NSArray _listadoPersonas

property (nonatomic retain) NSArray listadoPersonas

end

Ahora abrimos el fichero PersonasViewControllerm antildeadimos el syntetizeimportamos los ficheros de gestioacuten de la base de datos la clase Persona y todo el coacutedigobaacutesico para la gestioacuten de la vista de la tabla

import PersonasViewControllerhimport Personahimport BDPersistenciah

implementation PersonasViewController

synthesize listadoPersonas = _listadoPersonas

Persistencia

50Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

- (id)initWithStyle(UITableViewStyle)style

self = [super initWithStylestyle]if (self)

Custom initializationreturn self

- (void)dealloc

[super dealloc]

selflistadoPersonas = nil

- (void)didReceiveMemoryWarning

Releases the view if it doesnt have a superview[super didReceiveMemoryWarning]

Release any cached data images etc that arent inuse

pragma mark - View lifecycle

- (void)viewDidLoad

[super viewDidLoad]

selftitle = Listado de personas

selflistadoPersonas = [BDPersistenciadatabase]arrayPersonas

- (void)viewDidUnload

[super viewDidUnload] Release any retained subviews of the main view eg selfmyOutlet = nil

- (void)viewWillAppear(BOOL)animated

[super viewWillAppearanimated]

- (void)viewDidAppear(BOOL)animated

[super viewDidAppearanimated]

- (void)viewWillDisappear(BOOL)animated

[super viewWillDisappearanimated]

- (void)viewDidDisappear(BOOL)animated

[super viewDidDisappearanimated]

- (BOOL)shouldAutorotateToInterfaceOrientation

Persistencia

51Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

(UIInterfaceOrientation)interfaceOrientation

Return YES for supported orientationsreturn (interfaceOrientation ==

UIInterfaceOrientationPortrait)

pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView(UITableView)tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection

(NSInteger)section

Return the number of rows in the sectionreturn [selflistadoPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath

(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]if (cell == nil)

cell = [[[UITableViewCell alloc]initWithStyleUITableViewCellStyleSubtitle

reuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona persona = (Persona )[selflistadoPersonas

objectAtIndexindexPathrow]celltextLabeltext = [NSString stringWithFormat

(d) personanombrepersonaapellidos personaedad]celldetailTextLabeltext = personalocalidad

return cell

pragma mark - Table view delegate

- (void)tableView(UITableView )tableViewdidSelectRowAtIndexPath

(NSIndexPath )indexPath

Navigation logic may go here Create and pushanother view controller

end

Persistencia

52Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ahora soacutelo nos queda incorporar la vista que acabamos de crear dentro de nuestraaplicacioacuten Para ello vamos a llamarla desde el Delegate y a meterla dentro de unNavigation Controller Primero debemos de antildeadir un Outlet en la clasedelegada hacia un Navigation Controller

imports iniciales

interface sesion03_ejemplo1AppDelegate NSObjectltUIApplicationDelegategt

UIWindow _windowUINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet

UINavigationController navController

end

Ahora hacemos click sobre la vista MainWindowxib y arrastramos un Navigation

Controller desde el listado de la columna de la derecha hacia la columna de laizquierda donde pone Objects Nos apareceraacute el Navigation Controller dibujadoen pantalla Ahora desplegamos el Navigation Controller y hacemos click sobre elView Controller que hay dentro Nos vamos a la pestantildea de Identity Inspector

de la columna de la derecha y escribimos dentro de Class el nombre de la vista dellistado de personas PersonasViewController

Finalmente hacemos click sobre el objeto Delegate vamos a la pestantildea de Show

Connections Inspector y arrastramos desde navController hasta el objetoNavigation Controller de la columna de la izquierda Ya tenemos los controladoresy las vistas conectadas

Una vez hecho esto nos queda indicar dentro de nuestro coacutedigo que la aplicacioacuten arranquecon el Navigation Controller para ello escribimos lo siguiente al final del meacutetododidFinishLaunchingWithOptions de la implementacioacuten de la clase delegada

selfwindowrootViewController = selfnavController[selfwindow makeKeyAndVisible]return YES

Ya estaacute listo todo y preparado para compilar Si ejecutamos el proyecto veremos que nosaparece el listado de personas en la vista de tabla

Persistencia

53Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios

61 Creando y leyendo un fichero plist (15 puntos)

En este primer ejercicio vamos a crear nuestra primera aplicacioacuten usando el meacutetodo depersistencia de ficheros La aplicacioacuten mostraraacute en una vista de tabla TableView unlistado de series en la que si seleccionamos una veremos su informacioacuten maacutes importanteen otra vista Comenzaremos creando un fichero de propiedades (plist) siguiendo unaestructura determinada y posteriormente mostraremos en una vista de tabla todos susdatos iexclComenzamos

1) Crear un proyecto usando la plantilla Master-Detail application Lo llamaremosejercicio-plist-ios como identificador de empresa escribiremos esuajtech y como prefijode clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremos UseAutomatic Reference Counting para olvidarnos de la gestioacuten de memoria El resto deopciones las dejaremos sin marcar

2) Crear un archivo de propiedades (plist) con el nombre de series

3) Editar el archivo plist creando la siguiente estructura de datos (5 series miacutenimo)

Plist series

4) Rellenar el archivo plist con datos de series reales

5) Escribir el coacutedigo necesario en UAMasterViewControllerm que recorra el archivoplist que acabamos de crear y muestre por consola los titulos de las series

6) Mostrar en la tabla que hay implementada los tiacutetulos de la serie Para ello deberemosde completar todos los meacutetodos del protocolo del TableView

7) Ejecutar la aplicacioacuten y comprobar que funciona correctamente

Persistencia

54Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

62 Implementar vista de detalle de la serie (05 puntos)

Dentro del proyecto anterior (no hace falta crear uno nuevo) vamos a mostrar toda lainformacioacuten adicional de cada una de las series en una vista de detalle Cuandoseleccionemos una fila de la tabla nos deberaacute aparecer una nueva vista con el resto deinformacioacuten (antildeo director principal sinopsis y duracioacuten)

63 Uso baacutesico de una base de datos SQLite (1 punto)

En este ejercicio vamos a crear la misma aplicacioacuten que en el ejercicio anterior perousando el meacutetodo de persistencia de SQLite Para completar el ejercicio deberemos seguirlos siguientes pasos

1) Crear un proyecto usando la plantilla Master-Detail application lo llamaremosejercicio-sqlite-ios como identificador de empresa escribiremos esuajtech y comoprefijo de clase UA Por uacuteltimo seleccionaremos iPhone como dispositivo y marcaremosUse Automatic Reference Counting para olvidarnos de la gestioacuten de memoria El restode opciones las dejaremos sin marcar

2) Nos descargamos e instalamos el programa SQLite Database Browser desde estadireccioacuten

3) Disentildear la estructura de la base de datos usando SQLite Database Browser tal y comose muestra en la imagen siguiente

Sqlite series

4) Insertar al menos 5 series en la base de datos usando el programa SQLite DatabaseBrowser

5) Cargar todos los datos de la base de datos desde el meacutetododidFinishLaunchingWithOptions de la clase UAAppDelegate Mostrar por consola losdatos cargados

6) Mostrar los datos cargados en el paso anterior en la vista de tabla UITableView quehay creada en la clase UAMasterViewController

Persistencia

55Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7) Arrancar la aplicacioacuten y comprobar que se muestran las series en la tabla de formacorrecta

Persistencia

56Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

7 Persistencia de datos en iOS User Defaults y Core Data

71 Introduccioacuten

En esta sesioacuten veremos como utilizar dos nuevos meacutetodos de persistencia en iOS ambosmuy interesantes y usados muy frecuentemente

bull User Defaults Muy uacutetil para almacenar datos simples y de uso repetido dentro denuestras aplicaciones iOS

bull Core Data Meacutetodo avanzado de almacenamiento de objetos enteros dentro de lamemoria del dispositivo iOS Muy uacutetil para almacenar datos complejos conrelaciones etc

72 User Defaults

Este meacutetodo es muy uacutetil cuando queremos almacenar pequentildeas cantidades de datos comopuntuaciones informacioacuten de usuario el estado de nuestra aplicacioacuten configuracionesetc

Este meacutetodo es el maacutes raacutepido de usar ya que no requiere de una base de datos ni ficherosni ninguna otra capa intermedia Los datos se almacenan directamente en la memoria deldispositivo dentro de un objeto de la clase NSUserDefaults

Para aprender a usar este meacutetodo de almacenamiento de datos vamos a crear unaaplicacioacuten muy simple que lea un nombre y una edad de pantalla y las guarde en unobjeto NSUserDefaults Para ello seguimos los siguientes pasos

bull 1) Comenzamos creando un nuevo proyecto llamado sesion04-ejemplo1 dentrode XCode

bull 2) Abrimos la vista sesion04_ejemplo1ViewController y arrastramos dos TextField un botoacuten y dos labels quedando la vista de la siguiente manera

Persistencia

57Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista de la aplicacioacuten

bull 3) Abrimos el fichero sesion04_ejemplo1ViewControllerh y definimos el Outletdel evento onclick del botoacuten El fichero quedaraacute de la siguiente manera

imports iniciales

interface sesion04_ejemplo1ViewController UIViewController UITextField tfNombreUITextField tfEdad

property(nonatomic retain) IBOutlet UITextField tfNombreproperty(nonatomic retain) IBOutlet UITextField tfEdad

-(IBAction) guardaDatos

end

bull 4) Ahora implementamos el coacutedigo para el meacutetodo del botoacuten dentro desesion04_ejemplo1ViewControllerm

synthesize tfEdadsynthesize tfNombre

-(IBAction) guardaDatos NSLog(Guardamos los datos)

Persistencia

58Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

creamos el objeto NSUserDefaultsNSUserDefaults datos = [NSUserDefaults standardUserDefaults]

guardamos el nombre (NSString)[datos setObjecttfNombretext forKeynombre]

guardamos la edad (Integer)[datos setInteger[tfEdadtext integerValue] forKeyedad]

almacenamos los datos en la memoria[datos synchronize]

bull 5) Tenemos que enlazar el botoacuten con el evento que hemos programado en el pasoanterior para ello abrimos la vista sesion04_ejemplo1ViewControllerxib y conla tecla ctlr apretada arrastramos desde el botoacuten hasta Files Owner (columna de laizquierda parte superior) y seleccionamos el meacutetodo que hemos creado(guardaDatos) De esta forma ya tenemos conectado el botoacuten

bull 6) Ahora hacemos lo mismo con los Text Field Tenemos que conectarlos alcontrolador para ello seleccionamos el objeto Files Owner y abrimos la pestantildeade conexiones (la que estaacute maacutes a la derecha en la columna de la derecha) Hacemosclick en tfEdad y arrastramos hasta el Text Field de edad Hacemos lo mismo para eltext field de nombre Ya tenemos todos los elementos de la vista conectados

Conexioacuten de Outlets

bull 7) Arrancamos la aplicacioacuten y la probamos

Persistencia

59Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Aplicacioacuten

bull 8) Ahora vamos a hacer que la aplicacioacuten cargue los datos que se han guardado alarrancar la vista para ello simplemente tenemos que escribir el siguiente coacutedigo en elmeacutetodo (void)viewDidLoad de la clase sesion04_ejemplo1ViewControllerm

- (void)viewDidLoad

[super viewDidLoad]

NSUserDefaults datos = [NSUserDefaults standardUserDefaults]

obtenemos un NSStringNSString nombre = [datos stringForKeynombre]

obtenemos un NSIntegerNSInteger edad = [datos integerForKeyedad]

if (nombre=nil ampamp edad=0)NSLog(Nombre cargado edad dnombreedad)

else

NSLog(Sin datos)

bull 9) Ya podemos arrancar de nuevo la aplicacioacuten y veremos que si escribimos cualquier

Persistencia

60Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

texto en los Text Fields estos se almacenaraacuten dentro del objeto NSUserDefaults Alapagar la aplicacioacuten y volver a arrancarla estos datos se cargaraacuten (apareceraacuten enconsola)

Consola

NotaLos datos permaneceraacuten en nuestro dispositivo hasta que se elimine la aplicacioacuten en ese casotodos los datos guardados en NSUserDefaults se borraraacuten

bull 10) Probar eliminar la aplicacioacuten del dispositivosimulador y volver arrancarComprobamos que los datos se han borrado

Consola

73 Core Data

De todas las formas de persistencia de datos que existen en iOS Core Data es la maacutesapropiada para usar en caso de manejar datos no triviales con relaciones algo maacutescomplejas entre ellos El uso de Core Data dentro de nuestra aplicacioacuten optimiza almaacuteximo el consumo de memoria mejora el tiempo de respuesta y reduce la cantidad decoacutedigo redundante a escribir

Core Data utiliza de fondo el meacutetodo de almacenamiento SQLite visto en la sesioacuten 3 deeste moacutedulo por tanto para explicar el funcionamiento de este meacutetodo de persistenciavamos a utilizar el mismo modelo de datos que usamos en esa sesioacuten Persona -gtDetallePersona

El el siguiente ejemplo haremos la misma aplicacioacuten que hicimos con SQLite perousando este nuevo meacutetodo de persistencia

731 Creando el proyecto

Para empezar tenemos que crearnos un nuevo proyecto en XCode Para ello abrimosXCode y seleccionamos crear una nueva aplicacioacuten basada en ventanas (Window-basedapplication) Hacemos click en Next escribimos como nombre del proyectosesion04-ejemplo2 y seleccionamos Use Core Data

Persistencia

61Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Creando un proyecto nuevo

Con esto ya tenemos la estructura baacutesica para empezar a programar Antes de nadahechamos un vistazo raacutepido a toda la estructura de directorios creada y vemos que dentrodel directorio principal tenemos un archivo que se llamasesion04_ejemplo2xcdatamodeld Al hacer click sobre el archivo se nos abre uneditor especial que en principio estaacute vacio y que maacutes adelante utilizaremos para disentildearnuestro modelo de datos

Ahora hechamos un vistazo a la clase delegada sesion04_ejemplo2AppDelegatemvemos que hay unos cuantos meacutetodos nuevos que serviraacuten para configurar el Core DataVamos a explicar los maacutes importantes

bull - (NSManagedObjectModel )managedObjectModel NSManagedObjectModel esla clase que contiene la definicioacuten de cada uno de los objetos o entidades quealmacenamos en la base de datos Normalmente este meacutetodo no lo utilizaremos ya quenosotros vamos a utilizar el editor que hemos visto antes con el que podremos crearnuevas entidades crear sus atributos y relaciones

bull - (NSPersistentStoreCoordinator )persistentStoreCoordinator Aquiacute esdonde configuramos los nombres y las ubicaciones de las bases de datos que se usaraacutenpara almacenar los objetos Cuando un managed object necesite guardar algo pasaraacutepor este meacutetodo

bull - (NSManagedObjectContext )managedObjectContext Esta claseNSManagedObjectContext seraacute la maacutes usada con diferencia de las tres y por tanto la

Persistencia

62Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

maacutes importante La utilizaremos baacutesicamente para obtener objetos insertarlos oborrarlos

732 Definiendo el modelo de datos

En el ejemplo que hicimos en la sesioacuten anterior de SQLite creamos dos tablas una paralos datos baacutesicos de la persona y otra para el detalle Ahora haremos lo mismo perousando Core Data la diferencia principal es que mientras que con SQLite obteniamos soacutelolos datos que necesitaacutebamos de la base de datos aquiacute obtenemos el objeto completo

Para definir nuestro modelo de datos basado en dos objetos (Persona y DetallePersona)abrimos el editor visual haciendo click sobre el ficherosesion04_ejemplo2xcdatamodeld Empezaremos creando una nueva entidad para ellohacemos click sobre el botoacuten Add Entity (+) que se encuentra en la parte inferior dela pantalla A la nueva entidad le pondremos como nombre Persona

Dentro de la seccioacuten de Attributes hacemos click sobre (+) y creamos un nuevoatributo para nuestra entidad le llamamos nombre y le asignamos como tipo StringRealizamos este mismo paso 2 veces maacutes para antildeadir apellidos y edad este uacutetlimode tipo Integer 16

Atributos Persona

Ahora creamos la entidad de DetallePersona de la misma manera que la anterior perocreando los atributos localidad pais direccion cp y telefono Todasde tipo String

Atributos DetallePersona

Finalmente nos queda relacionar de alguacuten modo las dos entidades para ello antildeadimos una

Persistencia

63Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

relacioacuten dentro de la entidad de Persona hacia DetallePersona simplementehaciendo click sobre el botoacuten (+) de la seccioacuten de Relationships A la relacioacuten leponemos como nombre detalle seleccionamos como destino la entidadDetallePersona y seraacute No inverse Este tipo de relacioacuten es uno a uno Por dentroCore Data realizaraacute crearaacute una clave ajena en la tabla de persona que apunte aDetallePersona pero eso a nosotros no nos interesa

Vista general modelo de datos (1)

Apple recomienda que siempre que hagamos una relacioacuten de una entidad a otra hagamostambieacuten la relacioacuten inversa por lo tanto hacemos justo lo anterior pero al reveacutes (deDetallePersona a Persona) con la diferencia que en la columna de Inverse

seleccionamos datosPersona

Ya tenemos las entidades y las relaciones creadas Si hacemos click en el botoacuten deEditor Style en la parte de abajo a la derecha del editor podemos ver de una formamaacutes clara la estructura que hemos disentildeado

Persistencia

64Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Vista general modelo de datos (2)

733 Probando el modelo

Una vez que tenemos el modelo creado pasamos a probarlo para comprobar que estaacute todocorrecto para ello abrimos la clase delegada (sesion04_ejemplo2AppDelegatem) yantildeadimos el siguiente fragmento de coacutedigo arriba del meacutetodoapplicationDidFinishLaunching

NSManagedObjectContext context = [self managedObjectContext]NSManagedObject persona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

[persona setValueJuan forKeynombre][persona setValueMartinez forKeyapellidos][persona setValue[NSNumber numberWithInt28] forKeyedad]

NSManagedObject detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersonainManagedObjectContextcontext]

[detallePersona setValueCalle Falsa 123 forKeydireccion][detallePersona setValueAlicante forKeylocalidad][detallePersona setValueEspantildea forKeypais][detallePersona setValue965656565 forKeytelefono]

[detallePersona setValuepersona forKeydatosPersona][persona setValuedetallePersona forKeydetalle]

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Persistencia

65Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Con este coacutedigo hemos creado un objeto Persona enlazado con otro objetoDetallePersona iexclComo puedes ver no hemos necesitado nada de coacutedigo SQLPrimero creamos un objeto del tipo NSManagedObjectContext que almacena el conextode Core Data Despueacutes creamos el objeto Persona en siacute con NSManagedObject A esteobjeto le pondremos los valores para cada una de las propiedades definidas en el modeloHacemos lo mismo con DetallePersona Finalmente relacionamos el objetoPersona con el objeto DetallePersona

Compilamos el proyecto comprobamos que todo funciona bien y no salen errores

Para comprobar que los datos se han almacenado correctamente en nuestro dispositivo losmostramos en la consola de la siguiente manera

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

intValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(------------------)

[fetchRequest release]

El coacutedigo de arriba lo escribiremos dentro del meacutetododidFinishLaunchingWithOptions de la clase delegada Lo que hace este coacutedigo esrecorrer todos los objetos almacenados en memoria y mostrarlos por consola Siarrancamos la aplicacioacuten veremos que se muestran todos los datos de los objetosalmacenados Cada vez que arranquemos la aplicacioacuten se mostraraacuten maacutes objetos ya quetambieacuten los creamos

Para listar los objetos usamos la clase NSFetchRequest Esta clase es equivalente aejecutar una sentencia Select de SQL Al objeto NSFetchRequest le asignamos laentidad que queremos listar (SELECT FROM ) en nuestro caso seraacute PersonaEl listado de los objetos se obtiene mediante el meacutetodo executeFetchRequest de laclase NSManagedObjectContext que hemos definido en el bloque anterior de coacutedigoEste meacutetodo devolveraacute un NSArray con todos los objetos NSManagedObject

Persistencia

66Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Salida de consola

NotaSi queremos ver queacute sentencias SQL estaacute ejecutando Core Data por debajo tenemos que activarel flag de debug para ello seleccionamos la opcioacuten Product gt Edit Scheme Enla columna de la izquierda seleccionamos el esquema que estamos utilizando para Debug y en laparte de la derecha en el cuadro de Arguments Passed on Launch antildeadimos 2paraacutemetros -comappleCoreDataSQLDebug y 1

Ahora arrancamos de nuevo la aplicacioacuten y veremos que en la consola apareceraacuten lassentencias SQL utilizadas por Core Data

()

INSERT INTO ZDETALLEPERSONA(Z_PK Z_ENT Z_OPT ZDATOSPERSONA ZTELEFONOZLOCALIDAD ZDIRECCION ZPAIS)VALUES( )CoreData sql SELECT 0 t0Z_PK t0Z_OPT t0ZTELEFONO t0ZLOCALIDADt0ZDIRECCION t0ZPAIS t0ZDATOSPERSONAFROM ZDETALLEPERSONA t0 WHERE t0Z_PK =

()

734 Generando los modelos automaacuteticamente

Generar los modelos a mano directamente desde el editor no es lo maacutes recomendado yaque podemos cometer errores de escritura errores de tipo etc La mejor manera dehacerlo es creando un archivo para cada entidad Esto se puede hacer desde 0 peroXCode tiene un generador de clases que es muy faacutecil de usar y ahorremos bastantetiempo iexclVamos a usarlo

Hacemos click sobre el raiz de nuestro proyecto y seleccionamos New File En lacolumna de la izquierda seleccionamos iOS gt Core Data y en el cuadro de laderecha NSManagedObject subclass Click en next En la siguiente pantallaseleccionamos nuestro modelo de datos sesion04_ejemplo2 y click en next Ahoranos aseguramos de seleccionar las dos entidades que hemos creado anteriormentePersona y DetallePersona click en next Seleccionamos el raiz de nuestroproyecto para guardar los ficheros que se generen y aceptamos

Persistencia

67Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Ventana de seleccioacuten de entidad

Ahora veremos que xsource ha creado automaacuteticamente 4 ficheros nuevos dentro denuestro proyecto Personamh y DetallePersonamh con todos los atributosespecificados Estas nuevas clases que heredan de NSManagedObject seraacuten las queutilicemos a partir de ahora

Cambiamos el coacutedigo de la clase delegada por el siguiente

NSManagedObjectContext context = [self managedObjectContext]Persona datosPersona = [NSEntityDescription

insertNewObjectForEntityForNamePersonainManagedObjectContextcontext]

datosPersonanombre = JuandatosPersonaapellidos = MartinezdatosPersonaedad = [NSNumber numberWithInt28]

DetallePersona detallePersona = [NSEntityDescriptioninsertNewObjectForEntityForNameDetallePersona

inManagedObjectContextcontext]detallePersonadireccion = Calle Falsa 123

Persistencia

68Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

detallePersonalocalidad = AlicantedetallePersonapais = EspantildeadetallePersonatelefono = 965656565

detallePersonadatosPersona = datosPersonadatosPersonadetalle = detallePersona

NSError errorif ([context saveamperror])

NSLog(Uhh Algo ha fallado [errorlocalizedDescription])

Listamos los datosNSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContextcontext]

[fetchRequest setEntityentity]NSArray fetchedObjects = [context executeFetchRequestfetchRequest

erroramperror]for (NSManagedObject info in fetchedObjects)

NSLog(Nombre [info valueForKeynombre])NSLog(Apellidos [info valueForKeyapellidos])NSLog(Edad d (NSInteger)[[info valueForKeyedad]

integerValue])

NSManagedObject details = [info valueForKeydetalle]NSLog(Direccion [details valueForKeydireccion])NSLog(Localidad [details valueForKeylocalidad])NSLog(Pais [details valueForKeypais])NSLog(Telefono [details valueForKeytelefono])

NSLog(-----------------)[fetchRequest release]

Ejecutamos el proyecto y comprobamos que en la consola aparece lo mismo que antespero ahora tenemos el coacutedigo bastante maacutes claro

735 Creando la vista de tabla

Ahora vamos a mostrar los objetos que antes hemos listado por consola en una vista detabla UITableView Para ello creamos un UITableViewController

Click en New file gt UIViewController subclass En la siguiente pantallanos aseguramos de seleccionar Subclass of UITableViewController de esta formanos apareceraacuten ya todos los meacutetodos de la tabla creados por defecto Guardamos elarchivo con el nombre que queramos por ejemplo PersonasViewController

Abrimos ahora PersonasViewControllerh y antildeadimos 2 atributos a la clase

bull Un NSArray que se seraacute el listado de personas (objetos de la clase Persona)bull Una referencia a NSManagedObjectContext

El fichero PersonasViewControllerh quedaraacute de la siguiente manera

Persistencia

69Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

imports iniciales

interface PersonasViewController UITableViewController NSArray _listaPersonasNSManagedObjectContext _context

property (nonatomic retain) NSArray listaPersonasproperty (nonatomic retain) NSManagedObjectContext context

end

Ahora en el m importamos en el encabezado las clases Personah yDetallePersonah y en el meacutetodo viewDidLoad creamos el array listaPersonas contodos los objetos Persona de forma similar a cuando lo hicimos en el apartado anterior

import PersonasViewControllerh

import Personahimport DetallePersonah

implementation PersonasViewController

synthesize listaPersonas = _listaPersonassynthesize context = _context

En viewDidLoad

- (void)viewDidLoad

[super viewDidLoad]

NSFetchRequest fetchRequest = [[NSFetchRequest alloc] init]NSEntityDescription entity = [NSEntityDescription

entityForNamePersonainManagedObjectContext_context]

[fetchRequest setEntityentity]NSError errorselflistaPersonas = [_context executeFetchRequestfetchRequest

erroramperror]selftitle = Listado Personas[fetchRequest release]

Ahora completamos el resto de meacutetodos heredados de UITableViewController de lamisma forma que hicimos en sesiones anteriores

- (NSInteger)numberOfSectionsInTableView(UITableView )tableView

Return the number of sectionsreturn 1

- (NSInteger)tableView(UITableView )tableViewnumberOfRowsInSection(NSInteger)section

Persistencia

70Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Return the number of rows in the sectionreturn [_listaPersonas count]

- (UITableViewCell )tableView(UITableView )tableViewcellForRowAtIndexPath(NSIndexPath )indexPath

static NSString CellIdentifier = Cell

UITableViewCell cell = [tableViewdequeueReusableCellWithIdentifierCellIdentifier]

if (cell == nil) cell = [[[UITableViewCell alloc]

initWithStyleUITableViewCellStyleSubtitlereuseIdentifierCellIdentifier] autorelease]

Configure the cellPersona p = (Persona )[_listaPersonas objectAtIndexindexPathrow]celltextLabeltext = pnombrecelldetailTextLabeltext = [NSString stringWithFormatd antildeos

[pedad intValue]]

return cell

Ahora para poder mostrar la tabla dentro de nuestra aplicacioacuten debemos asignarla dentrode la clase delegada para ello abrimos la vista MainWindowxib y arrastramos unacontroladora de navegacioacuten Navigation Controller a la lista de objetos de laizquierda La desplegamos y dentro de View Controller en la pestantildea de la derechade Identity Inspector escribimos el nombre de nuestra clase que tiene la vista de latabla PersonasViewController

Pantalla del disentildeador

Ahora abrimos el fichero sesion04_ejemplo2AppDelegateh y creamos un

Persistencia

71Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

UINavigationController dentro de la declaracioacuten de variables dejando el ficherocomo sigue

imports iniciales

interface sesion04_ejemplo2AppDelegate NSObject ltUIApplicationDelegategt

UINavigationController _navController

property (nonatomic retain) IBOutlet UIWindow windowproperty (nonatomic retain) IBOutlet UINavigationControllernavController

property (nonatomic retain readonly) NSManagedObjectContextmanagedObjectContextproperty (nonatomic retain readonly) NSManagedObjectModelmanagedObjectModelproperty (nonatomic retain readonly) NSPersistentStoreCoordinatorpersistentStoreCoordinator

- (void)saveContext- (NSURL )applicationDocumentsDirectory

end

Dentro del m importamos la clase PersonasViewControllerh y completamos elsynthetize

synthesize navController = _navController

Y por uacuteltimo justo antes de la llamada al meacutetodo makeKeyAndVisible escribimos elsiguiente coacutedigo para asignar el contexto de la clase delegada alPersonasViewController

PersonasViewController root = (PersonasViewController ) [_navControllertopViewController]

rootcontext = [self managedObjectContext][selfwindow addSubview_navControllerview]

Ahora compilamos y ejecutamos nuestro proyecto y si todo ha ido bien deberiacutea de salirla tabla con los datos de las personas

Persistencia

72Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Listado de personas

736 Migraciones de bases de datos con Core Data

Una vez que tenemos disentildeada nuestra base de datos inicial iquestqueacute pasariacutea si queremosmodificarla para antildeadirle un campo maacutes a una tabla iquestQueacute haraacute Core Data para realizar lamigracioacuten de la versioacuten anterior a la actual

Siguiendo con el ejemplo anterior si despueacutes de ejecutar la aplicacioacuten en nuestrodispositivo o simulador modificamos la estructura de la base de datos antildeadiendo unatributo nuevo en la entidad DetallePersona por ejemplo codigoPostal con el tipoInteger 16 veremos que al volver a arrancar la aplicacioacuten nos apareceraacute un error entiempo de ejecucioacuten similar al siguiente

Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 Theoperationcouldnrsquot be completed(Cocoa error 134100) UserInfo=0x6b6c680 metadata=ltCFBasicHash 0x6b68380[0x1337b38]gttype = immutable dict count = 7entries =gt

2 ltCFString 0x6b70320 [0x1337b38]gtcontents =NSStoreModelVersionIdentifiers

=ltCFArray 0x6b706e0 [0x1337b38]gttype = immutable count = 1

values = (0 ltCFString 0x1332cd8 [0x1337b38]gtcontents =

)4 ltCFString 0x6b70350 [0x1337b38]gtcontents =

NSPersistenceFrameworkVersion=

Persistencia

73Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ltCFNumber 0x6b6f2c0 [0x1337b38]gtvalue = +386 type =kCFNumberSInt64Type

6 ltCFString 0x6b70380 [0x1337b38]gtcontents =NSStoreModelVersionHashes =

ltCFBasicHash 0x6b707e0 [0x1337b38]gttype = immutable dict count =2entries =gt

1 ltCFString 0x6b70700 [0x1337b38]gtcontents = Persona =ltCFData 0x6b70740

[0x1337b38]gtlength = 32 capacity = 32 bytes =0x1c50e5a379ff0ddc3cda7b82a3934c5e 75649ac3c919beea2 ltCFString 0x6b70720 [0x1337b38]gtcontents = DetallePersona

=ltCFData 0x6b70790[0x1337b38]gtlength = 32 capacity = 32 bytes =0xb7ba2eb498f9a1acdd6630d56f6cd12c e8963dd3e488ba95

7 ltCFString 0x10e3aa8 [0x1337b38]gtcontents = NSStoreUUID =ltCFString 0x6b70510[0x1337b38]gtcontents = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E8 ltCFString 0x10e3948 [0x1337b38]gtcontents = NSStoreType =ltCFString 0x10e3958[0x1337b38]gtcontents = SQLite9 ltCFString 0x6b703b0 [0x1337b38]gtcontents =

_NSAutoVacuumLevel = ltCFString0x6b70830 [0x1337b38]gtcontents = 210 ltFString 0x6b703d0 [0x1337b38]gtcontents =

NSStoreModelVersionHashesVersion=ltCFNumber 0x6b61950 [0x1337b38]gtvalue = +3 type =

kCFNumberSInt32Type reason=The model used to open the store is incompatible with the oneused tocreate the store

metadata = NSPersistenceFrameworkVersion = 386NSStoreModelVersionHashes =

DetallePersona = ltb7ba2eb4 98f9a1ac dd6630d5 6f6cd12c f890007746b16f00

e8963dd3 e488ba95gtPersona = lt1c50e5a3 79ff0ddc 3cda7b82 a3934c5e 5cb4ee4b

4ac98838 75649ac3c919beeagt

NSStoreModelVersionHashesVersion = 3NSStoreModelVersionIdentifiers = (

)NSStoreType = SQLiteNSStoreUUID = 4F80A909-DA41-48ED-B24D-DBA73EC2BB9E_NSAutoVacuumLevel = 2

reason = The model used to open the store is incompatible with the

one usedto create the store

Este error es muy comuacuten cuando estamos desarrollando aplicaciones con Core DataBaacutesicamente nos dice que el modelo de base de datos que estaacute intentando cargar dememoria es distinto al que se indica en la aplicacioacuten esto es porque no hemos indicadoninguacuten meacutetodo de migracioacuten Realmente existen dos formas de solucionar este error

Persistencia

74Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

bull Por un lado y soacutelo cuando estemos desarrollando y no tengamos ninguna versioacutende nuestra aplicacioacuten lanzada en la App Store podremos eliminar la base de datosdel dispositivo simulador simplemente borrando la aplicacioacuten de el Tambieacutenpodemos ejecutar el siguiente coacutedigo una soacutela vez [[NSFileManagerdefaultManager] removeItemAtURLstoreURL errornil]

bull En el caso de que estemos realizando una migracioacuten sobre una versioacuten ya publicadaen la App Store el meacutetodo anterior no nos serviriacutea ya que obligariacuteamos al usuario aborrar su aplicacioacuten del dispositivo con lo que todos los datos que tuviera seperderiacutean Esto no es viable por lo que necesitaremos usar otro meacutetodo para realizarla migracioacuten y solucionar el error de una forma maacutes limpia este meacutetodo es el quevamos a comentar a continuacioacuten

XCode soporta Versioning para Core Data esto es que podemos crear versiones de labase de datos de una forma sencilla si seguimos una serie de pasos

1) Antildeadimos las siguientes opciones en forma de NSDictionary a nuestro coacutedigo delpersistentStoreCoordinator

NSDictionary options = [NSDictionary dictionaryWithObjectsAndKeys[NSNumber numberWithBoolYES]NSMigratePersistentStoresAutomaticallyOption[NSNumber numberWithBoolYES]NSInferMappingModelAutomaticallyOptionnil]

Y modificamos el meacutetodo addPersistentStoreWithType pasaacutendole como paraacutemetro eldiccionario options

if ([__persistentStoreCoordinatoraddPersistentStoreWithTypeNSSQLiteStoreTypeconfigurationnil URLstoreURL optionsoptions erroramperror])

Con el coacutedigo anterior indicamos al persistentStoreCoordinator que la migracioacuten enel caso de que se deba de realizar sea de forma automaacutetica

AtencioacutenDeberemos de tener en cuenta que este tipo de migracioacuten que estamos estudiando en este puntosoacutelo es vaacutelida para cambios simples en Core Data lo que Apple llama como lightweightmigration Este tipo de migracioacuten admite una serie de cambios que se indican dentro de ladocumentacioacuten de Apple para cambios maacutes complejos deberemos realizar migracionespersonalizadas y mucho maacutes complejas El documento en donde Apple explica el tipo demigraciones que existen para Core Data y su forma de implementarlas lo podemos consultardesde esta web

2) Ahora deberemos de crear la versioacuten del esquema de Core Data dentro de XCode para

Persistencia

75Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

ello seleccionamos el fichero del esquema sesion04_ejemplo2xcdatamodeld yseleccionamos en el menuacute principal Editor gt Add Model Version A la nueva versioacuten lallamaremos sesion04_ejemplo_2 (versioacuten 2) Una vez que hayamos pulsado sobreFinish se nos habraacute creado la nueva versioacuten del esquema dentro de la principal y estaraacutemarcada con un tick la versioacuten anterior

3) Una vez que tenemos creada la nueva versioacuten deberemos modificarla antildeadiendole elnuevo atributo Seleccionamos la versioacuten recieacuten creadasesion04_ejemplo_2xcdatamodel y antildeadimos a la entidad DetallePersona elatributo codigoPostal Atencioacuten Si este atributo lo hemos antildeadido anteriormentedeberemos de borrarlo de la versioacuten anterior y antildeadirlo en la segunda versioacuten

4) Ahora nos queda indicar a Core Data que queremos usar la versioacuten 2 de la base dedatos para nuestra aplicacioacuten Para ello seleccionamos el esquema principal (el raiz)sesion04_ejemplo2xcdatamodeld y en la ventana de la derecha en la pestantildea de FileInspector seleccionamos como Versioacuten actual la versioacuten 2 esto lo hacemos dentro delapartado de Versioned Core Data Model Automaacuteticamente veremos que el siacutembolo detick cambia a la versioacuten 2

5) Ahora ya podemos volver a compilar y veremos que no nos aparece el error y funcionatodo seguacuten lo esperado

Magical RecordExisten varias librerias de coacutedigo libre que facilitan enormemente el uso de Core Data ennuestras aplicaciones iOS Una de ellas es Magical Record Mediante esta libreriacutea podremos usarCore Data sin tener que preocuparnos de todo el proceso de configuracioacuten en las controladorasde la configuracioacuten de migraciones etc Su instalacioacuten en un proyecto de XCode es muy faacutecil yaltamente recomendable ya que proporciona una API muy clara y sencilla de usar

Persistencia

76Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios

81 Usando User Defaults (1 punto)

En este ejercicio vamos a usar las variables de usuario para almacenar y mostrar los datosdel dispositivo que estamos usando y las veces que hemos arrancado la aplicacioacuten

Crear un proyecto nuevo usando la plantilla Single View Application con el nombreejercicio-userdefaults-ios Se debe de ejecutar en iPhone e iPad (Universal) Comoidentificador de empresa escribiremos esuajtech y como prefijo de clase UA Debe detener el ARC activado (desmarcar el resto de opciones)

Ayuda meacutetodos de UIDevicePara obtener los datos del dispositivo usaremos el singleton UIDevice estos son algunos de losmeacutetodos que podemos usar [[UIDevice currentDevice] name] [[UIDevicecurrentDevice] systemName] [[UIDevice currentDevice]systemVersion] [[UIDevice currentDevice] model] [[UIDevicecurrentDevice] localizedModel]

82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)

En este ejercicio disentildearemos un modelo de datos de dos tablas relacionadas entre ellasantildeadiremos datos por coacutedigo y los mostraremos en una vista de tabla La base de datosque vamos a implementar constaraacute de dos tablas por un lado una que contendraacute los tiacutetulosde series y otra que tendraacute los detalles A la primera la llamaremos Serie y la segundaDetalleSerie Para realizar correctamente el ejercicio deberemos seguir los siguientespasos

1) Crear un proyecto nuevo usando la plantilla Master-Detail Application con elnombre ejercicio-coredata-ios Se debe de ejecutar en iPhone como identificador deempresa escribiremos esuajtech y como prefijo de clase UA Debe de tener el ARCactivado y la opcioacuten de Use Core Data (desmarcar el resto de opciones)

2) Disentildear el siguiente modelo de datos

Persistencia

77Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Esquema modelo de datos

3) Revisar el fichero UAAppDelegatem y comprobar que tenemos todos los meacutetodosnecesarios para que funcione Core Data en nuestra aplicacioacuten

4) Modificar el fichero UAMasterViewControllerm para que se muestren los tiacutetulos delas series en la tabla

5) Probar la aplicacioacuten antildeadiendo unos tiacutetulos de prueba

83 Migrando datos con Core Data (05 puntos)

Una vez que hemos disentildeado la Base de Datos con Core Data vamos a modificarlaantildeadiendo un atributo maacutes a la tabla de DetalleSerie valoracion

a) iquestQueacute pasa si cambiamos el modelo de datos y arrancamos la aplicacioacuten iquestQueacute errornos da y por queacute

b) iquestCoacutemo podemos evitar este tipo de errores cuando estemos desarrollando iquestY siestamos en produccioacuten con la aplicacioacuten ya a la venta

c) Crea una nueva versioacuten del modelo

d) Modifica el coacutedigo necesario dentro de la clase UAAppDelegate para evitar este tipo deerrores cuando estemos en un entorno de produccioacuten

e) Vuelve a arrancar la aplicacioacuten y comprueba que funciona bien

Persistencia

78Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

Persistencia

79Copyright copy 2012-13 Dept Ciencia de la Computacioacuten e IA All rights reserved

  • 1 Persistencia en Android ficheros y SQLite
    • 11 Introduccioacuten
    • 12 Manejo de ficheros tradicionales en Android
      • 121 Apertura de ficheros
      • 122 Ficheros como recursos
      • 123 Operar con ficheros
      • 124 Almacenar datos en la tarjeta de memoria
        • 13 Base de datos SQLite
          • 131 Content Values
          • 132 Cursores
          • 133 Trabajar con bases de datos SQLite
          • 134 La clase SQLiteOpenHelper
          • 135 Crear una base de datos sin SQLiteHelper
          • 136 Realizar una consulta
          • 137 Extraer resultados de un cursor
          • 138 Antildeadir actualizar y borrar filas
              • 2 Ejercicios - Persistencia en Android ficheros y SQLite
                • 21 Uso de ficheros (05 puntos)
                • 22 Persistencia con ficheros (05 puntos)
                • 23 Base de datos SQLiteOpenHelper (05 puntos)
                • 24 Base de datos insercioacuten y borrado (05 puntos)
                • 25 Base de datos probar nuestro adaptador (05 puntos)
                • 26 Base de datos cambios en la base de datos (05 puntos)
                  • 3 Persistencia en Android proveedores de contenidos y SharedPreferences
                    • 31 Shared Preferences
                      • 311 Guardar Shared Preferences
                      • 312 Leer Shared Preferences
                      • 313 Interfaces para Shared Preferences
                      • 314 Definiendo una pantalla de preferencias con un layout en XML
                      • 315 Controles nativos para preferencias
                      • 316 Actividades de preferencias
                      • 317 Shared Preference Change Listeners
                        • 32 Proveedores de contenidos
                          • 321 Proveedores nativos
                          • 322 Proveedores propios crear un nuevo proveedor de contenidos
                          • 323 Proveedores propios crear la interfaz de consultas
                          • 324 Proveedores propios tipo MIME
                          • 325 Proveedores propios registrar el proveedor
                          • 326 Content Resolvers
                          • 327 Otras operaciones con Content Resolvers
                              • 4 Ejercicios - Persistencia en Android proveedores de contenidos y SharedPreferences
                                • 41 Compartir datos entre actividades con Shared Preferences (075 puntos)
                                • 42 Actividad de preferencias (075 puntos)
                                • 43 Proveedor de contenidos propio (075 puntos)
                                • 44 iquestPor queacute conviene crear proveedores de contenidos (075 puntos)
                                  • 5 Persistencia de datos en iOS Ficheros y SQLite
                                    • 51 Introduccioacuten
                                    • 52 Ficheros plist (Property Lists)
                                      • 521 Leyendo datos desde ficheros plist
                                      • 522 Escribiendo datos en ficheros plist
                                        • 53 SQLite
                                          • 531 Herramientas disponibles
                                          • 532 Lectura de datos
                                          • 533 Mostrando los datos en una tabla
                                              • 6 Persistencia de datos en iOS Ficheros y SQLite - Ejercicios
                                                • 61 Creando y leyendo un fichero plist (15 puntos)
                                                • 62 Implementar vista de detalle de la serie (05 puntos)
                                                • 63 Uso baacutesico de una base de datos SQLite (1 punto)
                                                  • 7 Persistencia de datos en iOS User Defaults y Core Data
                                                    • 71 Introduccioacuten
                                                    • 72 User Defaults
                                                    • 73 Core Data
                                                      • 731 Creando el proyecto
                                                      • 732 Definiendo el modelo de datos
                                                      • 733 Probando el modelo
                                                      • 734 Generando los modelos automaacuteticamente
                                                      • 735 Creando la vista de tabla
                                                      • 736 Migraciones de bases de datos con Core Data
                                                          • 8 Persistencia de datos en iOS User Defaults y Core Data - Ejercicios
                                                            • 81 Usando User Defaults (1 punto)
                                                            • 82 Disentildeando un modelo de datos sencillo con Core Data (15 puntos)
                                                            • 83 Migrando datos con Core Data (05 puntos)
Page 16: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 17: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 18: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 19: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 20: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 21: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 22: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 23: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 24: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 25: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 26: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 27: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 28: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 29: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 30: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 31: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 32: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 33: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 34: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 35: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 36: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 37: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 38: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 39: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 40: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 41: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 42: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 43: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 44: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 45: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 46: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 47: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 48: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 49: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 50: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 51: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 52: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 53: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 54: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 55: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 56: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 57: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 58: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 59: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 60: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 61: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 62: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 63: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 64: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 65: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 66: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 67: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 68: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 69: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 70: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 71: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 72: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 73: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 74: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 75: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 76: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 77: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 78: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de
Page 79: Experto Java - Persistenciaexpertojava.ua.es/dadm/restringido/persistencia/whole...1.2.1. Apertura de ficheros En Android podemos abrir un fichero para lectura o para escritura de