You are on page 1of 17

Guia Android: Avanzando con la base de datos

En el primer artculo sobre Android hicimos el tpico Hello World, explicando la estructura de Android, cmo funcionaba ese bsico ejemplo ahora vamos a construir un bloc de notas para nuestro telfono Android. Debido a que son ms archivos y ms lneas de cdigo que en el primer ejercicio os he comprimido los archivos iniciales y finales en un archivo zip (Descargar). Asi siempre tenis cdigo dnde comparar por si no os funciona algo. Pero como siempre, es mejor que vayis siguiendo el tutorial y veis la evolucin del cdigo en vez de mirar directamente la solucin.

Conexin a la base de datos


Una vez que habis creado el proyecto Android seleccionando un proyecto existente (Notas), podis ver la estructura de archivos. Hay ya algunos archivos creados pero el nico que he dejado con informacin es /src/com.android.demo.notepad1/NotasDbAdapter.java. Este archivo se va a encargar de conectarse a la base de datos para que no tengamos que estar lidiando con esto en otras partes del cdigo. Recordad que estamos en un esquema MVC. Este va a ser nuestro primer modelo. Miremos el cdigo en general:
?

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

public class NotasDbAdapter { // variables varias ... private static class DatabaseHelper extends SQLiteOpenHelper { DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) {...}

@Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {. } /** * Constructor - pasa el contexto para poder abrir o crear la DB */ public NotasDbAdapter(Context ctx) {...}

/** * Abre la base de datos de notas. Si no puede abrirla, la crea. Si no se pue * lanza una excepcion */ public NotasDbAdapter open() throws SQLException {...} public void close() {...}

/** * Inserta una nueva nota con el ttulo y el texto dados. Si se crea correcta

29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58

* devuelve el rowId, en caso contrario, devuelve -1 para indicar que ha habi * un error.. */ public long createNote(String title, String body) {...} /** * Borra la nota con el rowId dado */ public boolean deleteNote(long rowId) {...} /** * Devuelve un Cursor apuntando a la lista de todas las notas */ public Cursor fetchAllNotes() {...} /** * Devuelve un Cursor apuntando a la nota con el rowId dado */ public Cursor fetchNote(long rowId) throws SQLException {...} /** * Actualiza la nota con los detalles dados. */ public boolean updateNote(long rowId, String title, String body) {...} }

Tenemos mtodos para crear, actualizar, abrir y cerrar la base de datos. Y luego los mtodos CRUD para crear una nueva nota, devolver su informacin (o la de varias notas), actualizarla y borrarla. Esta clase es muy importante ya que estar en todas vuestras apps de una forma u otra asi que miremos mtodo a mtodo.
?

01 02 03 04 05 06 07 08 09 10 11 12

private static class DatabaseHelper extends SQLiteOpenHelper { DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(DATABASE_CREATE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w(TAG, "Upgrading database from version " + oldVersion + " to "

13 14 15 16 17 18 19

+ newVersion + ", which will destroy all old data"); // sentencia drop onCreate(db); } }

Esta clase auxiliar nos permitir abrir la base de datos, crearla si no existe y actualizarla al lanzar una nueva versin. La sentencia SQL para crear es: // sentencia create
?

private static final String DATABASE_CREATE = "create table notes (_id integer prim

Cmo podis haber observado ya antes, estamos utilizando la base de datos SQLite, es parecida a MySQL pero tiene pequeos detalles diferentes. Con esto ya tenemos creada la base de datos. Veamos el mtodo para insertar notas:
?

1 2 3 4 5 6 7

public long createNote(String title, String body) { ContentValues initialValues = new ContentValues(); initialValues.put(KEY_TITLE, title); initialValues.put(KEY_BODY, body); return mDb.insert(DATABASE_TABLE, null, initialValues); }

A travs de la clase ContentValues guardamos la informacin con el mtodo put y la insertamos. Para actualizar es casi igual pero usamos el mtodo update en vez de insert. Hay que acordarse de indicar el rowId:
?

1 2 3 4 5 6 7

public boolean updateNote(long rowId, String title, String body) { ContentValues args = new ContentValues(); args.put(KEY_TITLE, title); args.put(KEY_BODY, body);

return mDb.update(DATABASE_TABLE, args, KEY_ROWID + "=" + rowId, null) > }

El mtodo borrar es muy obvio asi que pasamos a recuperar una nota de la base de datos:
?

01 02

public Cursor fetchNote(long rowId) throws SQLException {

03 04 05 06 07 08 09 10 11 12 13

Cursor mCursor = mDb.query(true, DATABASE_TABLE, new String[] {KEY_ROWID, KEY_TITLE, KEY_BODY}, KEY_ROWID + "=" + rowId, null, null, null, null, null); if (mCursor != null) { mCursor.moveToFirst(); } return mCursor; }

Hacemos una query indicando que queremos un array de tres strings con los tres campos que queremos, y con la condicin de rowId. El resto de los parmetros se usan si queremos hacer groupBy, having u orderBy. Despus comprobamos que no est vaco y si es as movemos el cursor a la primera posicin que apunta a la nota que queremos.

Insertando notas
Vamos a modificar el layout de la actividad inicial /res/layout/notepad_list.xml
?

01 02 03 04 05 06 07 08 09 10 11 12 13 14

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content"> <ListView android:id="@android:id/list" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@android:id/empty" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/no_notes"/> </LinearLayout>

Tenemos dos elementos: una lista y un campo de texto. El TextView slo se muestra si la lista est vaca. Android sabe esto gracias a empty, que est unido al valor de list. A travs deandroid:id="@android:id/list" indicamos que queremos que esa lista se rellene con los valores de la variable list de la actividad. Para mostrar la informacin que aparece en cada fila de la lista necesitamos crear un nuevo template. Creamos un nuevo archivo /src/layout/notes_row.xml
?

1 2 3 4 5

<?xml version="1.0" encoding="utf-8"?> <TextView android:id="@+id/text1" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content"/>

En el campo de texto (TextView) vamos a mostrar solo el contenido de la variable text1 de la actividad. Antes de ir a la actividad mirad la clase R.java que os dije en el anterior tutorial que Android la manejaba automticamente. Podis ver cmo ha aadido el archivo nuevo y la variable text1. Vayamos ahora con Notas.java, miremos su estructura:
?

1 2 3 4 5 6 7 8

public class Notas extends Activity { public void onCreate(Bundle savedInstanceState) {...} public boolean onCreateOptionsMenu(Menu menu) {...} public boolean onOptionsItemSelected(MenuItem item) {...} }

onCreate() es el mtodo principal de la actividad donde ejecutaremos todo nuestro cdigo.onCreateOptionsMenu() crear el men de opciones y onOptionsItemSelected() estar atento a qu botn del men presionamos para ejecutar la accin correspondiente. Repito que es slo sobre acciones sobre el men, las acciones de otros botones o enlaces van en otros mtodos, exactamente con Listeners, pero eso ya lo veremos ms adelante. Lo primero que vamos a hacer es convertir a Notas en una actividad de lista, para ellos hacemos que extienda a ListActivity:
?

public class Notas extends ListActivity

Una combinacin de teclas muy til para arreglar los imports de los paquetes es ctrl-shift-O en Windows o Linux, o cmd-shift-O en el Mac. Queremos que en la pantalla principal de Notas muestre la lista de notas, para ello tiene que conectarse a la base de datos, coger la informacin y mostrarla en el layout que hemos hecho antes:
?

01 02 03 04 05 06 07 08 09 10

private NotasDbAdapter mDbHelper; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.notepad_list); // new notas mDbHelper.open(); fillData(); }

Declaramos el objeto fuera para que est disponible a todos los mtodos, fijamos que el layout sea notepad_list (fijaros como referenciamos aqu al layout, no usamos urls o directorios, si no referencia mediante la clase R.java). Vamos a coger la informacin en un mtodo privado para que quede todo ms ordenado:
?

01 02 03 04 05 06 07 08 09 10 11 12 13

private void fillData() { // Recuperamos todas las notas de la DB y creamos una lista Cursor c = mDbHelper.fetchAllNotes(); startManagingCursor(c); String[] from = new String[] { NotasDbAdapter.KEY_TITLE }; int[] to = new int[] { R.id.text1 }; / Y creamos un adaptador array para que vaya mostrando las filas SimpleCursorAdapter notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to); setListAdapter(notes); }

Ya empezamos a ver ms mtodos internos de Android, os animo a entrar a la API de Android e ir viendo lo que hacen exactamente, es la mejor forma de aprender. En fillData()recuperamos todas las notas de la DB con el mtodo fetchAllNotes que hicimos al principio, y luego creamos un adaptador que relaciona el campo del objeto que queremos (KEY_TITLE) con la variable text1 (otra vez referenciada por R.java) e indica que queremos que use el layout notes_row.xml que hemos creado antes. Por ltimo avisa a ListActivity que notes va a ser la lista que queremos mostrar. Con estas modificaciones ya podramos ver las notas pero no tenemos ninguna asi que vamos a crear la actividad en un momento para que podamos probarlo ms a fondo. EnNotas.java aadamos el botn de aadir nota. Basta con llamar a la clase superior para crear el menu y aadir el botn de Add Item en la primera posicin.
?

1 2 3 4 5 6 7 8

public static final int INSERT_ID = Menu.FIRST; @Override public boolean onCreateOptionsMenu(Menu menu) { boolean result = super.onCreateOptionsMenu(menu); menu.add(0, INSERT_ID, 0, R.string.menu_insert); return result; }

Y aadimos otra variable de texto a strings.xml


?

<string name="menu_insert">Add Item</string>

Perfecto, ahora hagamos que al hacer click en este nuevo botn ejecute la nueva actividad para mostrar el formulario. Para eso modificamos el mtodo onOptionsItemSelected() tambin de Notas.java
?

01 02 03 04 05 06 07 08 09 10

@Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case INSERT_ID: createNote(); return true; } return super.onOptionsItemSelected(item); }

Capturamos el Id de la opcin del men y si es la primera ejecutamos el mtodo createNote(). En un primer paso vamos a hacer que inserte una nota con un ttulo y texto fijos. En el siguiente paso ya crearemos el formulario:
?

1 2 3 4 5

private void createNote() { String noteName = "Note " + mNoteNumber++; mDbHelper.createNote(noteName, ""); fillData(); }

La razn de llamar a fillData() de nuevo es para que recargue la lista una vez que se ha terminado de ejecutar el evento de hacer click en la opcin del men Add Item. Es hora de ver en accin lo que estamos viendo. Comprueba que no hay errores en ningn archivo, arregla los imports y ejecuta como un proyecto de Android. Si das a menu podrs aadir una nota e irs viendo como se crea una nota nueva cada vez que le das. Enhorabuena, hemos conseguido comunicarnos correctamente con la base de datos.

Creando la nota con el formulario


Lo que queremos ahora es que cuando hacemos click en Add Item vayamos a otra pantalla donde podamos escribir el ttulo y el texto de la nota. Demos a guardar y aparezca en la lista de notas. Para eso vamos a tener que crear otra actividad (pantalla-controlador). Pensad que por cada pantalla que queramos tener vamos a tener que tener una nueva actividad. Antes de eso vamos a permitir borrar notas, para ello vamos a Notas.java. Aadimos la siguiente lnea al final del mtodo onCreate()
?

registerForContextMenu(getListView());

Queremos que al hacer click en un elemento de lista nos salga un men desde donde podamos borrarlo. Aadimos un nuevo mtodo que sobreescribe a uno de Android:
?

1 2 3 4 5

@Override

public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) super.onCreateContextMenu(menu, v, menuInfo); menu.add(0, DELETE_ID, 0, R.string.menu_delete); }

Hace falta insertar ms variables en Notas.java


?

1 2 3 4 5

private static final int ACTIVITY_CREATE=0; private static final int ACTIVITY_EDIT=1;

private static final int INSERT_ID = Menu.FIRST; private static final int DELETE_ID = Menu.FIRST + 1;

y en strings.xml
?

1 2 3 4 5

<string name="menu_delete">Delete Note</string> <string name="title">Title</string> <string name="body">Body</string> <string name="confirm">Confirm</string> <string name="edit_note">Edit Note</string>

Ahora que ya podemos seleccionar la nota a borrar, vamos a crear el mtodo que efectivamente lo borra:
?

01

@Override

02 03 04 05 06

public boolean onContextItemSelected(MenuItem item) { switch(item.getItemId()) { case DELETE_ID:

AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo() mDbHelper.deleteNote(info.id); fillData();

07 08 09 10 11
} }

return true;

return super.onContextItemSelected(item);

Volvemos a hacer un switch para comprobar la posicin del botn del men y si es la segunda borramos la nota y recargamos la lista. Conseguimos el ID de la nota gracias agetMenuInfo() y al mtodo registerForContextMenu() que hemos insertado antes al final deonCreate(). Volvamos ahora al mtodo auxiliar para crear nuevas notas, borramos lo que tenamos y vamos a cargar una nueva actividad:
?

1 2 3 4

private void createNote() { Intent i = new Intent(this, NoteEdit.class); startActivityForResult(i, ACTIVITY_CREATE); }

Lo que hacemos es crear un nuevo Intent, una nueva actividad, en este caso NoteEdit que todava no hemos creado. Ejecuta su comienzo y espera el resultado. Con el resultado luego llamar a la funcin onActivityResult() que implementaremos ms tarde. Si no nos interesa saber el resultado basta con llamar a la funcin startActivity() Vamos a hacer algo parecido para editar las notas que ya tenemos. Pero antes de nada vamos a permitir utilizar el Cursor en toda la clase, para eso aadimos la variable de clase:
?

private Cursor mNotesCursor;

refactorizamos onCreate() que quedara ahora as:


?

1 2 3 4 5 6 7 8

@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.notepad_list); mDbHelper = new NotasDbAdapter(this); mDbHelper.open(); fillData(); registerForContextMenu(getListView()); }

9
y nuestro mtodo privado fillData()
?

01
private void fillData() {

02 03 04 05 06 07 08 09 10 11 12 13 14
}

// Recupera todas las notas de la DB y las guarda en el cursor mNotesCursor = mDbHelper.fetchAllNotes(); startManagingCursor(mNotesCursor);

// Array con los campos que queremos mostrar en la lista String[] from = new String[]{NotasDbAdapter.KEY_TITLE};

// array con las variables asociadas para esos campos int[] to = new int[]{R.id.text1};

SimpleCursorAdapter notes =

new SimpleCursorAdapter(this, R.layout.notes_row, mNotesCursor, from, setListAdapter(notes);

15

Ahora ya podemos fijarnos en el mtodo que se ejecute al hacer click sobre un elemento de la lista. Se llama onListItemClick() y lo sobreescribimos:
?

01
@Override

02
protected void onListItemClick(ListView l, View v, int position, long id) {

03 04 05 06 07 08 09 10 11

super.onListItemClick(l, v, position, id); Cursor c = mNotesCursor; c.moveToPosition(position); Intent i = new Intent(this, NoteEdit.class); i.putExtra(NotasDbAdapter.KEY_ROWID, id); i.putExtra(NotasDbAdapter.KEY_TITLE, c.getString( c.getColumnIndexOrThrow(NotasDbAdapter.KEY_TITLE))); i.putExtra(NotasDbAdapter.KEY_BODY, c.getString( c.getColumnIndexOrThrow(NotasDbAdapter.KEY_BODY))); startActivityForResult(i, ACTIVITY_EDIT);

12 13

Lo que estamos haciendo es mover el Cursor a la posicin en la que estamos, crear el Intent y pasarle como argumentos el Id, ttulo y texto. Para pasar como argumentos utilizamos la funcin putExtra(). Otra funcin muy til. Por ltimo lo ejecutamos esperando el resultado. Va siendo hora de implementar el mtodo onActivityResult() que es el que se ejecuta cuando recibimos el resultado de haber creado o editado una nota con startActivityForResult()
?

01 02 03

@Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); Bundle extras = intent.getExtras();

04 05 06 07
switch(requestCode) { case ACTIVITY_CREATE: String title = extras.getString(NotasDbAdapter.KEY_TITLE);

08 09 10 11 12

String body = extras.getString(NotasDbAdapter.KEY_BODY); mDbHelper.createNote(title, body); fillData(); break; case ACTIVITY_EDIT: Long mRowId = extras.getLong(NotasDbAdapter.KEY_ROWID);

13 14 15 16 17 18 19 20
} }

if (mRowId != null) { String editTitle = extras.getString(NotasDbAdapter.KEY_TITLE); String editBody = extras.getString(NotasDbAdapter.KEY_BODY); mDbHelper.updateNote(mRowId, editTitle, editBody); } fillData(); break;

21 22 23

Si estamos creando la nota, la guarda en la base de datos. Si la estamos editando, la actualiza y en ambos casos recarga la lista de notas. Ya tenemos la lgica ms o menos, vamos ahora a crear el layout del formulario. Para eso creamos el archivo /res/layout/note_edit.xml y lo rellenamos con:
?

01 02

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

03
android:orientation="vertical" android:layout_width="fill_parent"

04 05 06

android:layout_height="fill_parent">

<LinearLayout android:orientation="horizontal"

07 08 09 10 11 12

android:layout_width="fill_parent" android:layout_height="wrap_content">

<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/title" /> <EditText android:id="@+id/title"

13 14 15 16 17 18 19 20 21 22

android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1"/> </LinearLayout>

<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/body" /> <EditText android:id="@+id/body" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="1"

23 24 25 26 27 28 29 30 31 32 33

android:scrollbars="vertical" />

<Button android:id="@+id/confirm" android:text="@string/confirm" android:layout_width="wrap_content" android:layout_height="wrap_content" />

</LinearLayout>

Este es un layout ya ms complicado. Lo que hace es crear dos campos de texto, uno para el ttulo y otro para el texto; y el botn que guarda lo que hemos escrito. Utilizando una mezcla de configuraciones de alturas conseguimos la apariencia que queremos. En el prximo tutorial entraremos ms en detalle en los layouts. Vamos a crear la clase NoteEdit.java ahora que ya tenemos su layout. Voy a copiar el cdigo final comentado y explico detalles al final:
?

01 02 03 04

/* * Copyright (C) 2008 Google Inc. */

package com.android.demo.notepad1;

05 06 07 08 09 10 11 12 13 14 15 16
private Long mRowId; private EditText mTitleText; private EditText mBodyText; public class NoteEdit extends Activity { import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText;

17 18 19 20 21
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

22 23 24 25 26 27

// Fijamos el layout de esta actividad setContentView(R.layout.note_edit);

// Objetos que referencian a los campos editables del layout mTitleText = (EditText) findViewById(R.id.title); mBodyText = (EditText) findViewById(R.id.body); Button confirmButton = (Button) findViewById(R.id.confirm);

28 29 30 31 32 33 34 35 36 37 38
mTitleText.setText(title); // Insertamos los valores actuales de la nota en los campos if (title != null) { // Si hay argumentos, los cogemos mRowId = null; Bundle extras = getIntent().getExtras(); if (extras != null) { String title = extras.getString(NotasDbAdapter.KEY_TITLE); String body = extras.getString(NotasDbAdapter.KEY_BODY); mRowId = extras.getLong(NotasDbAdapter.KEY_ROWID);

39 40 41 42 43 44 45 46 47 48
}

} if (body != null) { mBodyText.setText(body); }

// Listener para el botn de confirmar confirmButton.setOnClickListener(new View.OnClickListener() {

public void onClick(View view) {

49 50 51 52 53 54

Bundle bundle = new Bundle();

// Guardamos los nuevos valores en un Bundle

bundle.putString(NotasDbAdapter.KEY_TITLE, mTitleText.getText().t

bundle.putString(NotasDbAdapter.KEY_BODY, mBodyText.getText().toS if (mRowId != null) { bundle.putLong(NotasDbAdapter.KEY_ROWID, mRowId);

55 56 57 58 59 60 61 62 63 64
} }); }

// y los mandamos de vuelta al mtodo que los est esperando Intent mIntent = new Intent(); mIntent.putExtras(bundle); setResult(RESULT_OK, mIntent); finish();

65
}

66 67 68 69 70 71

Hay que fijarse en cmo cogemos la referencia al campo de texto (mediante la clase R.java por supuesto) mTitleText = (EditText) findViewById(R.id.title); En cmo cogemos los argumentos que hemos pasado antes con el mtodo putExtra()

y cmo asignamos los valores actuales mTitleText.setText(title); El ltimo paso tras crear una nueva actividad es declararla en el archivo AndroidManifest.xml. Recordarlo bien porque es un tpico fallo que nos hace perder muchas horas.
?

<activity android:name=".NoteEdit"></activity>

Tambin se puede introducir la nueva actividad con los paneles de control en vez de directamente sobre el cdigo (Application -> Add Application Node -> Activity y rellenamos el nombre) Es hora de ejecutar el programa de nuevo para ir viendo cmo evoluciona. Ya podemos editar las notas que creamos antes y crear nuevas con la opcin Add Item del men. Probad a dar hacia atrs cuando estis en el formulario. Veris cmo salta una excepcin.

Resumen
Hoy hemos tocado muchos temas y muy variados:

Hemos creado nuestro primer modelo con su conexin a la base de datos (adaptador de por medio para facilitarnos la vida).

Hemos visto ms layouts para mostrar listas y formularios. Hemos creado una actividad de lista que os puede servir de modelo para vuestras apps. Permite crear nuevas notas, editarlas y borrarlas. Aunque mejor esperad a la semana que viene para tomarla como modelo ya que haremos algunos cambios importantes.

Hemos creado una segunda actividad por lo que ya podemos decir que nuestra app es medianamente compleja, al menos tiene 2 pantallas!

El prximo da arreglaremos los pequeos fallos que nos hemos dejado, mejoraremos el cdigo para hacerlo ms ptimo, y aprenderemos muchas ms vistas en otros ejemplos, como por ejemplo cmo mostrar tu posicin en el mapa. Por hoy creo que es suficiente. Repasad el tutorial, ved la API de Android y si tenis alguna duda preguntad en Foros del Web o en los comentarios de esta entrada e intentar responder.

You might also like