# Ejercicios

## Escribir en un archivo de texto

Vamos a leer y escribir en un archivo de texto. La escritura la haremos añadiendo al final del archivo, mientras que la lectura la haremos leyendo línea a línea.

* Crear una nueva aplicación llamada "Fichero" cuya única actividad, la principal, mostrará un `EditText` cuyo texto será recogido cada vez que se pulse un `Button` que habrá debajo. Dicho texto será añadido a un archivo de texto llamado `mytextfile.txt`. Para añadir texto a un archivo primero lo abriremos con\
  `openFileOutput(FILENAME,Context.MODE_APPEND))` y después utilizaremos el método `append(...)` de `OutputStreamWriter`.
* ¿Cuándo abrimos el archivo? Recordemos que la actividad puede pasar a inactiva en cualquier momento y que puede no volver a recuperarse. ¿Cuándo nos conviene cerrar el archivo?
* Debajo del campo de texto y del botón, vamos a añadir un `TextEdit` que ocupe el resto de la pantalla. Lo haremos pulsable con el método `setClickable(true)` y cada vez que se haga click sobre él, leeremos el archivo línea a línea y lo mostraremos entero.

![](https://888516061-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LwrBJxp0xTANGM036-G%2F-LwrBLLK_CMs3zo9_Yuv%2F-LwrBU_BltyYAOcsq1xM%2Fs14_1_textfile.png?generation=1577179049153920\&alt=media)

## Crear y utilizar un DataHelper para SQLite

En la teoría de esta sesión tenemos el esqueleto básico de un DataHelper para trabajar con SQLite. Se trata de un patrón de diseño que nos ayuda a acceder a nuestros datos encapsulando todo el manejo de la base de datos en el `DataHelper`.

El `DataHelper` utiliza a su vez otro patrón de diseño, el `SQLiteOpenHelper`. Éste nos obliga a definir qué ocurre cuando la base de datos todavía no existe y debe ser creada, y qué ocurre si ya existe pero debe ser actualizada porque ha cambiado de versión. Así el `SQLiteOpenHelper` que implementemos, en este caso `MiOpenHelper`, nos devolverá siempre una base de datos separándonos de la lógica encargada de comprobar si la base de datos existe o no.

Crea una nueva clase llamada `DataHelper` y copia el contenido del DataHelper de la teoría. Revisa el contenido de cada uno de sus métodos:

* Comprueba el código de la clase interna `MiOpenHelper`, en concreto al método `onCreate(...)` y `onUpgrade`. Idealmente el método `onUpgrade` debería portar las tablas de la versión antigua a la versión nueva, copiando todos los datos. En este caso se elimina directamente la tabla que tenemos con la sentencia SQL `"DROP TABLE IF EXISTS " + TABLE_NAME` y se vuelve a crear.
* En el constructor del `DataHelper` revisa como se obtiene el campo `db` que acceso de escritura a la base de datos utilizando `MiOpenHelper`. ¿Qué más cosas se hacen en este constructor?
* ¿Cómo funciona el método insert y qué devuelve como retorno?.
* ¿Qué realiza la función de borrado y qué devuelve como retorno? ¿para qué sirve el segundo y tercer parámetro de la función `db.delete`?

Ahora podemos utilizar el `DataHelper` para leer y escribir en base de datos de forma transparente desde cualquier actividad.

* En el método `onCreate` de la actividad principal prueba el borrado, inserción y listado de datos.
* Comprueba por línea de comandos que la base de datos está creada. Te serán útiles los siguientes comandos:

```bash
adb -e shell
#cd /data/data/es.ua.jtech.daa/databases
#sqlite3 misusuarios.db
sqlite> .schema
sqlite> .tables
sqlite> select * from usuarios;
```

*   Ahora vamos a cambiar en el `DataHandler` el nombre de

    la segunda columna, en lugar de nombre, se va a llamar nombres.

    Ejecutamos la aplicación y comprobamos que sale con una excepción.

    Se pide comprobar en el Log cuál ha sido el error. ¿Cómo lo podemos

    solucionar?&#x20;

> Pista: según hemos programado el `DataHandler` y siguiendo el patrón de diseño de `SQLiteOpenHelper`, podemos arreglar el problema tocando sólo una tecla.

Opcional:

* Añade al helper la función para eliminar por nombre de usuario.
* Añade al layout un spinner para que se pueda seleccionar qué usuario eliminar, y un botón para eliminarlo. Añade también un campo de texto para la introducción de nuevos usuarios, y un botón de inserción de nuevo usuario.

## Proveedor de contenidos propio

Vamos a implementar otra forma de acceder a la misma base de datos, siguiento esta vez el patrón de diseño `ContentProvider` de Android. Seguiremos trabajando con el proyecto del ejercicio anterior.

* Creamos una nueva clase llamada `UsuariosProvider` que herede de `ContentProvider`. Nos obligará a sobrecargar una serie de métodos abstractos. Antes de implementar la `query` vamos a configurar el provider:
* Añadimos algunos campos típicos de los content provider:

```java
public static final Uri CONTENT_URI = 
    Uri.parse("content://es.ua.jtech.daa/usuarios");
private static final int ALLROWS = 1;
private static final int SINGLE_ROW = 2;
private static final UriMatcher uriMatcher;
static{
    uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    uriMatcher.addURI("es.ua.jtech.daa", "usuarios", ALLROWS);
    uriMatcher.addURI("es.ua.jtech.daa", "usuarios/#", SINGLE_ROW);
}
```

* Vamos a acceder a la misma base de datos que el ejercicio anterior, pero no vamos a hacerlo a través del helper que tuvimos que implementar, sino que vamos a copiar de él el código que nos haga falta. Copia los los campos que definen el nombre de la base de datos, de la tabla, de las columnas, la versión, así como la referencia al contexto y a la base de datos. La sentencia compilada del insert ya no va a hacer falta. Inicializa los valores que haga falta en el constructor. Para inicializar la referencia a la base de datos vamos a utilizar, una vez más, `MiOpenHelper`. Podemos copiarlo del helper del ejercicio anterior al `UsuariosProvider`.
* Implementa de forma apropiada el `getType` para devolver un tipo MIME diferente según si se trata de una URI de una fila o de todas las filas. Para ello ayúdate del `uriMatcher`.
* Implementa el método `query`. Simplemente se trata de devolver el cursor que obtenemos al hacer una query a la base de datos SQLite. Algunos de los parámetros que le pasaremos los recibimos como parámetros del método del provider. Los que no tengamos irán con valor `null`.
* Aunque no tenemos todos los métodos del `UsuariosProvider` implementados, podemos probarlo. Para ello debemos registarlo en el `AndroidManifest.xml`:

```markup
<manifest>
    ...
    <provider android:name="UsuariosProvider"
        android:authorities="es.ua.jtech.daa"/>
  </application>
  <uses-sdk android:minSdkVersion="8" />
</manifest>
```

*   En `Main` del ejercicio anterior se insertan una serie de valores con

    el helper y se muestran en el campo de texto. Manteniendo este código, vamos a añadir

    al campo de texto el resultado obtenido con la `query` del `UsuariosProvider`

    para comprobar que tanto el helper como el provider nos devuelven el mismo resultado.

**¿Por qué conviene crear proveedores de contenidos?**

* Porque es la forma estándar que establece Android de acceder a contenidos.&#x20;
* Nos permite publicar los datos de nuestra aplicación para que los utilicen otras aplicaciones.
* Nos permite leer y utilizar los datos que otras aplicaciones publican.
* Posibilita notificar al `ContentResolver` de los cambios ocurridos, por lo que componentes en la pantalla pueden refrescarse de forma automática.

## Proveedores nativos

Entre los proveedores de contenidos nativos nos encontramos el `Browser`, `CallLog`, `ContactsContract`, `MediaStore`, `Settings`, `UserDictionary`. En este ejercicio vamos a acceder a los contactos. Para reutilizar la tabla, vamos a copiar el proyecto anterior, `ProveedorContenidos` y lo vamos a pegar con nuevo nombre, `ProveedoresPropios`. Para cambiar el nombre de la aplicación tenemos que editar el recurso `strings.xml`.

* Necesitamos permisos para acceder a los contactos. Se añaden en el `AndroidManifest.xml`:

```markup
<manifest>
    ...
    <uses-sdk android:minSdkVersion="8" />
    <uses-permission android:name="android.permission.READ_CONTACTS"/>
</manifest>
```

* Elimina las acciones de los eventos de inserción y borrado.
* Cambia la query para que acceda a ContactsContract.Contacts.CONTENT\_URI, así como la uri de notificación del cursor.
* En el `adapter` mapea los mismos campos de texto del anterior ejercicio (Id y Nombre) a las columnas `new String[]{ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME}`.
* Para comprobar que funciona correctamente deberás introducir algunos números de teléfono desde la agenda de Android.
* Opcional: Una vez que tengas el ID de un contacto, puedes acceder a sus números de teléfono que se encuentran en otra tabla, `ContactsContract.Data` y necesitarías otro cursor diferente para recorrerlos.

> Una forma mucho más directa de acceder desde una aplicación a los contactos es lanzando la actividad de los contactos nativa de Android.
