Buenas tardes, en esta entrada vamos a ver otro de los tipos de Almacenamiento que podemos tener en nuestras aplicaciones. En este caso veremos como podemos implementar todo lo necesario para usar una base de datos SQLite.
SQLite es una base de datos la cual incorpora Android, y es muy fácil de implementar, como veremos a continuación.
Vamos a ponernos manos a la obra:
-
Creación del Proyecto
Para comenzar vamos a crear un nuevo proyecto en Android, por lo que nos dirigiremos a File->New->Android Application Project. Si por algún casual no encontramos la opción, pulsaremos en Other, donde una nueva ventana nos aparecerá con una lista. En la lista buscaremos Android Application Project, aunque podemos filtrar los resultados si en la caja de texto introducimos Android.
Una vez lo tengamos localizado, nos aparecerá el asistente de creación del proyecto. En la primera ventana pondremos el nombre de nuestro proyecto, en mi caso le he puesto EjemploSQLite, al igual que a la aplicación. Lo demás podéis dejarlo igual.
Según avancemos en el asistente, nos aparecerá una ventana en la que podremos establecer el nombre de nuestra primera Activity, en mi caso la he llamado NotasActivity, aunque es lo de menos y podéis poner el nombre que queráis.
Una vez terminado el asistente, podremos comenzar a construir nuestra aplicación.
-
SQLiteOpenHelper y DataSource
Para poder implementar todo el funcionamiento de nuestra aplicación, nos vamos a apoyar en 2 nuevas clases que vamos a crear:
-
MySQLiteOpenHelper
Vamos a empezar creando esta clase en nuestro proyecto, la cual heredará de SQLiteOpenHelper. La funcionalidad de esta clase será la de crear la base de datos, y mediante 2 métodos, onCreate que es usado para crear las tablas y onUpgrade que se encarga de actualizar la base de datos. Vemos su código a continuación:
package sekth.droid.sqlite.ddbb; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class MySQLiteOpenHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "Notas"; private static final int DATABASE_VERSION = 1; public static class TablaNotas{ public static String TABLA_NOTAS = "notas"; public static String COLUMNA_ID = "_id"; public static String COLUMNA_TEXTO = "texto"; } private static final String DATABASE_CREATE = "create table " + TablaNotas.TABLA_NOTAS + "(" + TablaNotas.COLUMNA_ID + " integer primary key autoincrement, " + TablaNotas.COLUMNA_TEXTO + " text not null);"; public MySQLiteOpenHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); // TODO Auto-generated constructor stub } @Override public void onCreate(SQLiteDatabase db) { // TODO Auto-generated method stub db.execSQL(DATABASE_CREATE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO Auto-generated method stub db.execSQL("delete table if exists " + TablaNotas.TABLA_NOTAS); onCreate(db); } }
Como podemos ver tenemos 2 variables static que se encargan de guardar el nombre de nuestra base de datos, y de la versión.
A continuación tenemos una clase static que guardará lo referente a nuestra tabla, la cuál se encargará de una tabla que almacenará 2 campos, el _id y el texto de una que queramos.
Luego vemos una variable static llamada DATABASE_CREATE la cual se encarga de guardar la sentencia SQL que creará la tabla en la base de datos.
En el constructor lo único que haremos es llamar al constructor de la superclase, al cuál le pasamos el Context de nuestra aplicación, DATABASE_NAME, null y finalmente DATABASE_VERSION.
public MySQLiteOpenHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); // TODO Auto-generated constructor stub }
El siguiente método esta sustituido, y se encargará de crear la tabla mediante el método db.execSQL, el cuál tiene como parámetro nuestra sentencia de creación de la tabla:
public void onCreate(SQLiteDatabase db) { // TODO Auto-generated method stub db.execSQL(DATABASE_CREATE); }
El método onUpgrade en este caso se encargará de borrar la tabla y volver a crearla, aunque este método en aplicaciones mas complejas se ocupan de migrar datos o realizar operaciones mas complejas, pero para lo que vamos a hacer, no necesitamos hacer mucho más:
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO Auto-generated method stub db.execSQL("delete table if exists " + TablaNotas.TABLA_NOTAS); onCreate(db); }
-
Nota
Antes de continuar, vamos a crear al clase Nota, que será la que usemos para crear instancias de notas. No sería necesario crearla, ya que lo único que almacena es un texto y el id, pero aún así vamos a hacerlo bien. Su código no tiene nada del otro mundo, y podemos verlo a continuación:
package sekth.droid.sqlite.clases; public class Nota { private long id; private String texto; public long getId() { return id; } public void setId(long id) { this.id = id; } public String gettexto() { return texto; } public void setTexto(String texto) { this.texto = texto; } @Override public String toString(){ return texto; } }
Como vemos tenemos 2 variables de instancia, una llamada id que guardará el id de la Nota, y el texto que guardará el texto de la nota. El resto es bien conocido, con sus métodos setters y getters para acceder a estos valores.
-
NotasDataSource
Esta clase será la que usaremos para acceder al contenido de nuestra base de datos, se denomina también DAO (Data Access Object) y será la clase en la que nos apoyaremos para extraer datos, borrarlos, o crearlos. También se encarga de mantener la conexión abierta y cerrarla. A continuación vemos su código:
package sekth.droid.sqlite.ddbb; import java.util.ArrayList; import java.util.List; import sekth.droid.sqlite.clases.Nota; import sekth.droid.sqlite.ddbb.MySQLiteOpenHelper.TablaNotas; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; public class NotasDataSource { private SQLiteDatabase db; private MySQLiteOpenHelper dbHelper; private String[] columnas = { TablaNotas.COLUMNA_ID, TablaNotas.COLUMNA_TEXTO }; public NotasDataSource(Context context) { dbHelper = new MySQLiteOpenHelper(context); } public void open() { db = dbHelper.getWritableDatabase(); } public void close() { dbHelper.close(); } public void crearNota(String nota) { ContentValues values = new ContentValues(); values.put(TablaNotas.COLUMNA_TEXTO, nota); db.insert(TablaNotas.TABLA_NOTAS, null, values); } public List<Nota> getAllNotas() { List<Nota> listaNotas = new ArrayList<Nota>(); Cursor cursor = db.query(TablaNotas.TABLA_NOTAS, columnas, null, null, null, null, null); cursor.moveToFirst(); while (!cursor.isAfterLast()) { Nota nuevaNota = cursorToNota(cursor); listaNotas.add(nuevaNota); cursor.moveToNext(); } cursor.close(); return listaNotas; } public void borrarNota(Nota nota) { long id = nota.getId(); db.delete(TablaNotas.TABLA_NOTAS, TablaNotas.COLUMNA_ID + " = " + id, null); } private Nota cursorToNota(Cursor cursor) { Nota nota = new Nota(); nota.setId(cursor.getLong(0)); nota.setTexto(cursor.getString(1)); return nota; } }
Tenemos varios métodos interesantes:
-
public void open()
Este método se encargará de obtener acceso a la base de datos en forma de escritura:
public void open() { db = dbHelper.getWritableDatabase(); }
-
public void close()
Este método se encarga de cerrar la conexión con la base de datos:
public void close() { dbHelper.close(); }
-
public void crearNota(String nota)
En este método implementaremos lo necesario para crear un nuevo registro en la base da datos. Tiene como argumentos una variable de tipo String que contiene el texto de la nota:
public void crearNota(String nota) { ContentValues values = new ContentValues(); values.put(TablaNotas.COLUMNA_TEXTO, nota); db.insert(TablaNotas.TABLA_NOTAS, null, values); }
Como vemos, hemos instanciado un objeto de la clase ContentValues el cual almacenará los datos que introduciremos. A continuación mediante el método values.put() pasamos como parámetros el nombre de la columna, y el valor. A continuación, llamamos al método db.insert() el cual tiene como parámetros el nombre de la tabla, null y el objeto ContentValues.
-
public List getAllNotas()
Este método será el encargado de obtener todas las notas que tengamos en la tabla “notas”:
public List<Nota> getAllNotas() { List<Nota> listaNotas = new ArrayList<Nota>(); Cursor cursor = db.query(TablaNotas.TABLA_NOTAS, columnas, null, null, null, null, null); cursor.moveToFirst(); while (!cursor.isAfterLast()) { Nota nuevaNota = cursorToNota(cursor); listaNotas.add(nuevaNota); cursor.moveToNext(); } cursor.close(); return listaNotas; }
Como vemos, estamos haciendo uso de la clase Cursor la cual se encarga de realizar la consulta a la base de datos y obtener una lista de los elementos, para luego recorrer el cursor e ir creando instancias de Nota que iremos cargando en la lista que mas tarde retornaremos.
Una vez creado el cursor, lo primero que hay que hacer es usar el método cursor.moveToFirst() para posicionarnos en el primer elemento. A continuación mediante un bucle en el que usamos como condición isAfterLast(), que retorna true si el cursor está apuntando al elemento que va después de la última fila. Por ello, mientras este método retorne false, estaremos recorriendo filas y creando elementos de la clase Nota.
-
private Nota cursorToNota(Cursor cursor)
Este método será el encargado de recibir un argumento de la case Cursor que está apuntando a una fila en concreto, y creará una nueva instancia de la clase Nota la cual retornará para ser agregada a una lista:
private Nota cursorToNota(Cursor cursor) { Nota nota = new Nota(); nota.setId(cursor.getLong(0)); nota.setTexto(cursor.getString(1)); return nota; }
-
public void borrarNota(Nota nota)
Este método será el encargado de borrar una nota de la base de datos:
public void borrarNota(Nota nota) { long id = nota.getId(); db.delete(TablaNotas.TABLA_NOTAS, TablaNotas.COLUMNA_ID + " = " + id, null); }
Como vemos obtenemos el id de la nota con el cuál crearemos una sentencia que borrará la nota con ese id en la base de datos.
-
-
-
NotasActivity
En esta Activity nos encargaremos de mostrar una lista con las notas que tenemos disponibles. Es la Activity que se nos creó junto con el proyecto y por tanto vamos a crear una UI sencilla para poder trabajar con ello:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".NotasActivity" > <Button android:id="@+id/btnNuevaNota" android:layout_width="fill_parent" android:layout_height="wrap_content" android:onClick="agregarNota" android:text="Agregar nueva nota" /> <ListView android:id="@+id/lvNotas" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout>
Como vemos, en la interfaz gráfica solo disponemos de un botón, el cual usaremos para crear nuevas notas, y un ListView el cuál nos mostrará todas las notas que tenemos almacenadas en la base de datos.
A continuación vemos el código de nuestra Activity:
package sekth.droid.sqlite.Activities; import java.util.List; import sekth.droid.sqlite.R; import sekth.droid.sqlite.clases.Nota; import sekth.droid.sqlite.ddbb.NotasDataSource; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.ListView; public class NotasActivity extends Activity implements OnItemClickListener { private int requestCode = 1; private ListView lvNotas; private NotasDataSource dataSource; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_notas); // Instanciamos NotasDataSource para // poder realizar acciones con la base de datos dataSource = new NotasDataSource(this); dataSource.open(); // Instanciamos los elementos lvNotas = (ListView) findViewById(R.id.lvNotas); // Cargamos la lista de notas disponibles List<Nota> listaNotas = dataSource.getAllNotas(); ArrayAdapter<Nota> adapter = new ArrayAdapter<Nota>(this, android.R.layout.simple_list_item_1, listaNotas); // Establecemos el adapter lvNotas.setAdapter(adapter); // Establecemos un Listener para el evento de pulsación lvNotas.setOnItemClickListener(this); } public void agregarNota(View v) { Intent i = new Intent(this, NuevaNotaActivity.class); startActivityForResult(i, requestCode); } @Override public void onItemClick(final AdapterView<?> adapterView, View view, final int position, long id) { // TODO Auto-generated method stub AlertDialog.Builder builder = new AlertDialog.Builder(this) .setTitle("Borrar Nota") .setMessage("¿Desea borrar esta nota?") .setPositiveButton("Aceptar", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface arg0, int arg1) { // TODO Auto-generated method stub Nota nota = (Nota) adapterView .getItemAtPosition(position); dataSource.borrarNota(nota); // Refrescamos la lista refrescarLista(); } }) .setNegativeButton("Cancelar", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub return; } }); builder.show(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // TODO Auto-generated method stub super.onActivityResult(requestCode, resultCode, data); if (requestCode == this.requestCode && resultCode == RESULT_OK) { // Actualizar el Adapter dataSource.open(); refrescarLista(); } } private void refrescarLista() { List<Nota> listaNotas = dataSource.getAllNotas(); ArrayAdapter<Nota> adapter = new ArrayAdapter<Nota>(this, android.R.layout.simple_list_item_1, listaNotas); lvNotas.setAdapter(adapter); } @Override protected void onPause() { // TODO Auto-generated method stub dataSource.close(); super.onPause(); } @Override protected void onResume() { // TODO Auto-generated method stub dataSource.open(); super.onResume(); } }
Vamos a analizar los métodos que hemos usado:
-
onCreate()
En este método vamos a instanciar nuestra clase NotasDataSource, la cual nos servirá para trabajar con los datos de la base de datos, también instanciaremos la ListView, y crearemos el ArrayAdapter que cargará la ListView al construirse la Activity:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_notas); // Instanciamos NotasDataSource para // poder realizar acciones con la base de datos dataSource = new NotasDataSource(this); dataSource.open(); // Instanciamos los elementos lvNotas = (ListView) findViewById(R.id.lvNotas); // Cargamos la lista de notas disponibles List<Nota> listaNotas = dataSource.getAllNotas(); ArrayAdapter<Nota> adapter = new ArrayAdapter<Nota>(this, android.R.layout.simple_list_item_1, listaNotas); // Establecemos el adapter lvNotas.setAdapter(adapter); // Establecemos un Listener para el evento de pulsación lvNotas.setOnItemClickListener(this); }
-
public void agregarNota()
Este método será ejecutado cuando pulsemos el Button que definimos en el XML de la UI. Se encargará de ejecutar el método startActivityForResult(), ya que según lo que retorne la actividad que llame actualizaremos el ListView o no:
public void agregarNota(View v) { Intent i = new Intent(this, NuevaNotaActivity.class); startActivityForResult(i, requestCode); }
-
public void onItemClick()
Este es el Listener que usaremos para capturar el evento de click sobre un elemento de la ListView:
public void onItemClick(final AdapterView<?> adapterView, View view, final int position, long id) { // TODO Auto-generated method stub AlertDialog.Builder builder = new AlertDialog.Builder(this) .setTitle("Borrar Nota") .setMessage("¿Desea borrar esta nota?") .setPositiveButton("Aceptar", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface arg0, int arg1) { // TODO Auto-generated method stub Nota nota = (Nota) adapterView .getItemAtPosition(position); dataSource.borrarNota(nota); // Refrescamos la lista refrescarLista(); } }) .setNegativeButton("Cancelar", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub return; } }); builder.show(); }
Como vemos, creamos un AlertDialog para preguntar al usuario si desea borrar el elemento seleccionado.
-
protected void onActivityResult()
En este método comprobaremos si la operación en la segunda Activity que crearemos a continuación ha tenido éxito. Si ha tenido éxito, refrescamos la lista de elementos:
protected void onActivityResult(int requestCode, int resultCode, Intent data) { // TODO Auto-generated method stub super.onActivityResult(requestCode, resultCode, data); if (requestCode == this.requestCode && resultCode == RESULT_OK) { // Actualizar el Adapter dataSource.open() refrescarLista(); } }
-
private void refrescarLista()
Este método se encarga de realizar una consulta a la base de datos para cargar un objeto List, crear un ArrayAdapter nuevo y establecerlo.
private void refrescarLista() { List<Nota> listaNotas = dataSource.getAllNotas(); ArrayAdapter<Nota> adapter = new ArrayAdapter<Nota>(this, android.R.layout.simple_list_item_1, listaNotas); lvNotas.setAdapter(adapter); }
-
protected void onPause()
Si la aplicación se pausa por alguna razón, cerramos la conexión con la base de datos:
protected void onPause() { // TODO Auto-generated method stub dataSource.close(); super.onPause(); }
-
protected void onResume()
Si se resume la aplicación, volvemos a abrir la conexión con la base de datos:
protected void onResume() { // TODO Auto-generated method stub dataSource.open(); super.onResume(); }
-
-
NuevaNotaActivity
Para crear nuevas Notas, vamos a crear una nueva Activity, la cual tendrá un funcionamiento bastante básico. Vamos a comenzar difiniendo su UI:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".NuevaNotaActivity" > <TextView android:id="@+id/lblTexto" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:padding="10dp" android:text="Introduzca un texto" android:textSize="20sp" /> <EditText android:id="@+id/txtNota" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Introduzca el texto aqui..." android:lines="5" android:maxLines="5" android:padding="10dp" /> <Button android:id="@+id/btnAgregar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right" android:padding="10dp" android:text="Añadir" /> </LinearLayout>
Tenemos poca cosa, un TextView con un texto informativo, un EditText que guardará el texto que introduzcamos, y un Button el cual se encargará de crear un nuevo registro en la base de datos, y de finalizar la Activity. A continuación vemos el código de la Activity:
package sekth.droid.sqlite.Activities; import sekth.droid.sqlite.R; import sekth.droid.sqlite.ddbb.NotasDataSource; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; public class NuevaNotaActivity extends Activity { public static int resultCode = 10; private Button btnAgregar; private EditText txtNota; private NotasDataSource dataSource; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_nueva_nota); dataSource = new NotasDataSource(this); dataSource.open(); txtNota = (EditText) findViewById(R.id.txtNota); btnAgregar = (Button) findViewById(R.id.btnAgregar); btnAgregar.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub String textoNota = txtNota.getText().toString(); if (textoNota.length() != 0) { dataSource.crearNota(textoNota); setResult(RESULT_OK); finish(); } else { Toast.makeText(getApplicationContext(), "No ha introducido texto", Toast.LENGTH_SHORT) .show(); } } }); } }
Como vemos, creamos una instancia de nuestra clase NotasDataSource para poder trabajar con nuestra base de datos.
A continuación declaramos un listener en el Button en el cual verificaremos si el texto introducido tiene una longitud de 0, para no introducir Notas con un texto vació en la base de datos. Si por el contrario ha introducido texto, establecemos RESULT_OK en el método setResult(), comunicando así a la Activity anterior que la operación ha tenido éxito, y finalizamos la actividad mediante el método finish().
Nuestra aplicación es ahora funcional y podemos probarla en el emulador o en un dispositivo. Vemos a continuación unas imágenes y un vídeo de la aplicación ejecutándose:
Capturas de pantalla de la aplicación:
Video de la aplicación en funcionamiento:
Con esta entrada hemos podido ver como implementar el uso de Bases de datos SQLite en nuestra aplicación, para almacenar datos que se generen o que podamos ver necesarios. Es un ejemplo muy básico, y siempre se puede mejorar, pero aquí podemos ver perfectamente como implementarlos, y ya luego es cuestión de mejorarlo para adaptarlo a nuestras necesidades.
El código de ejemplo puedes descargarlo de aquí.
Sin más, cualquier aporte o corrección es bienvenido.
Saludos!!!