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.

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:

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?

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:

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:

<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.

  • 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:

<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.

Última actualización