En el post de hoy veremos la persistencia de datos con
SQLite y rozaremos un poco la clase ListActivity para montar una lista en
pantalla, aunque entraré más en detalle sobre los listados en un futuro post.
El objetivo será crear una aplicación que nos permita
insertar registros en una base de datos persistente y consultar el listado de
elementos que contiene.
Dibujando el layout
Crearemos una pantalla simple, con un EditText donde introducir
el texto a guardar y un Button para realizar la acción. Por
otro lado, tendremos un TextView que nos informará de
cuantos registros hay en la tabla, así como de un listado (ListView) para ver los
elementos, y un Button para refrescar el listado.
En código hablamos de:
<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity"
>
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Introduzca
Texto a guardar:" />
<EditText
android:id="@+id/edtTexto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/textView1"
android:ems="10" >
<requestFocus />
</EditText>
<Button
android:id="@+id/btnGuardar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/edtTexto"
android:layout_alignParentRight="true"
android:layout_below="@+id/edtTexto"
android:text="Guardar" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/textView5"
android:layout_below="@+id/textView5"
android:text="Registros:
" />
<TextView
android:id="@+id/txtRegistros"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/textView3"
android:layout_alignBottom="@+id/textView3"
android:layout_toRightOf="@+id/textView3"
android:text="TextView" />
<TextView
android:id="@+id/textView5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/btnGuardar"
android:layout_below="@+id/btnGuardar"
android:layout_marginTop="37dp"
android:text="Datos
Almacenados"
android:textAppearance="?android:attr/textAppearanceLarge"
/>
<Button
android:id="@+id/btnRefrescar"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/txtRegistros"
android:layout_alignRight="@+id/btnGuardar"
android:layout_alignTop="@+id/textView5"
android:text="Refrescar"
/>
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/textView3" />
</RelativeLayout>
Ojito con el id del ListView:
respetadlo tal cual.
Controlador de base de datos (SQLiteOpenHelper)
Para acceder a la base de datos usaremos una clase genérica
como controlador que extenderá a la clase SQLiteOpenHelper. Dicha clase provee
de todo lo necesario para manejar el acceso a datos, por lo que nos ayudará,
tal y como su nombre indica, a manejar la base de datos SQLite.
A esta clase la llamaremos BDController y lo primero que debemos hacer es definir una
serie de campos estáticos:
- Nombre de la base de datos:
private static final String DATABASE_NAME = "DEMODB.db";
- Versión de la base de datos:
private static final int DATABASE_VERSION = 1;
- Nombres de las tablas: (en nuestro caso solo una)
public static final String TABLE_TEXTOS = "TEXTOS";
- Nombre de los campos: (no es necesario, pero nunca está de más)
public static final String COLUMN_ID = "ID_TEXTO";
public static final String COLUMN_TEXTO = "TEXTO";
- Script de creación de tablas:
private static final String DATABASE_CREATE = "create table " + TABLE_TEXTOS
+ "(" + COLUMN_ID + " integer primary key autoincrement, "
+ COLUMN_TEXTO + "
text not null);";
Esta clase define un contructor que recibe el contexto de la
aplicación, e invoca al constructor de su clase padre (SQLiteOpenHelper) con el
contexto, el nombre de la base de datos, una factoria (a null en nuestro caso
ya que no lo vamos a usar) y la versión.
public BDController(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
Cuando se ejecuta, si no existe la base de datos, se lanza
el evento onCreate que es capturado por nuestra clase y genera las
tablas:
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE);
}
En el caso de una actualización de la base de datos (que no
coincidan las versiones, se lanzará el evento onUpgrade.
@Override
public void
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(BDController.class.getName(),
"Upgrading database from version " + oldVersion + " to "
+ newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS " + TABLE_TEXTOS);
onCreate(db);
}
En nuestro caso tenemos una tabla simple (Textos) con dos
columnas (idtextos y texto) por lo que podríamos definir un tipo para estos
registros:
public class Texto {
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;
}
public String toString() {
return texto;
}
}
Origen de Datos (DataSource)
Con la clase BDController no es suficiente para
lo que nosotros necesitamos. Esta clase se limita a crear la base de datos.
Necesitamos una que nos administre las operaciones sobre sus tablas, el
administrador del origen de datos. A esta clase la llamaremos BDDataSource.
Por un lado, definiremos los siguientes de esta clase:
- Objeto de base de datos:
private SQLiteDatabase database;
- Controlador:
private BDController dbHelper;
- Columnas de la base de datos:
private String[] allColumns = { BDController.COLUMN_ID, BDController.COLUMN_TEXTO };
En el constructor crearemos el objeto dbHelper (el
controlador de nuestra base de datos), por lo que necesitamos recibir el
contexto de la aplicación:
public BDDataSource(Context context) {
dbHelper = new BDController(context);
}
Crearemos un método para abrir y cerrar la conexión:
public void open() throws SQLException {
database = dbHelper.getWritableDatabase();
}
public void close() {
dbHelper.close();
}
Un método para insertar registros:
public Texto createTexto(String texto) {
ContentValues values = new ContentValues();
values.put(BDController.COLUMN_TEXTO, texto);
long insertId = database.insert(BDController.TABLE_TEXTOS, null, values);
Cursor cursor = database.query(BDController.TABLE_TEXTOS, allColumns, BDController.COLUMN_ID + " = " + insertId, null, null, null, null);
cursor.moveToFirst();
Texto newTexto = cursorToTexto(cursor);
cursor.close();
return newTexto;
}
Un método para la conversión de base de datos a objetos:
private Texto cursorToTexto(Cursor cursor)
{
Texto texto = new Texto();
texto.setId(cursor.getLong(0));
texto.setTexto(cursor.getString(1));
return texto;
}
Uno para eliminar registros (que no usaremos inicialmente):
public void deleteTexto(Texto
texto) {
long id = texto.getId();
System.out.println("Texto borrado con id: " + id);
database.delete(BDController.TABLE_TEXTOS,
BDController.COLUMN_ID + " = " + id, null);
}
Y finalmente uno para obtener el listado de objetos en la
tabla:
public List<Texto> getAllTextos() {
List<Texto> lista = new ArrayList<Texto>();
Cursor cursor = database.query(BDController.TABLE_TEXTOS,
allColumns, null, null, null, null, null);
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
Texto texto = cursorToTexto(cursor);
lista.add(texto);
cursor.moveToNext();
}
// Make sure
to close the cursor
cursor.close();
return lista;
}
Esta clase ya se puede usar en cualquier aplicación que
creemos, pues tenemos toda la lógica de las operaciones que necesitamos hacer
en la base de datos y además nos simplifica bastante el trabajo con ella.
Codificando la pantalla
Antes de nada, es importante tener presente que nuestra
pantalla, al tener un ListView, heredará de la clase ListActivity en lugar de
Activity. Esta clase implementa métodos que nos facilitarán el uso de listados.
public class MainActivity extends ListActivity {
(...)
}
Nada más cargar la pantalla (onCreate) debemos lanzar
el método cargarObjetosPantalla, que vincula los elementos del layout y sus
eventos con el código de nuestra clase java:
private void
cargaObjetosPantalla()
{
edtTexto = (EditText)findViewById(R.id.edtTexto);
btnGuardar = (Button)findViewById(R.id.btnGuardar);
txtRegistros = (TextView)findViewById(R.id.txtRegistros);
btnRefrescar = (Button)findViewById(R.id.btnRefrescar);
//base de datos
cargaDatos();
//eventos
btnGuardar.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
guardar();
}
});
btnRefrescar.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
refrescar();
}
});
}
Dentro de esta función podemos ver referencias a 3 funciones
relacionadas con la base de datos que veremos en detalle:
Cargar datos
La función cargaDatos creará el datasource
e invocará a la función refrescar para
mostrar por pantalla el listado de objetos en la tabla.
private void cargaDatos()
{
//creamos el datasource
datasource = new BDDataSource(this);
//lo abrimos
datasource.open();
//cargamos
los datos en la lista
refrescar();
}
Guardar
Para la inserción de registros en la tabla, habíamos definido
un EditText
(edtTexto)
y un Button
(btnGuardar).
Al evento onClick del botón btnGuardar le hemos asociado esta
función que invoca a createTexto del datasource, insertando un
registro en la tabla con el texto del elemento edtTexto.
private void guardar()
{
Texto texto = datasource.createTexto(edtTexto.getText().toString());
refrescar();
}
Refrescar (mostrar/actualizar listado)
Finalmente, el método refrescar, que carga los registros en
el ListView:
obtiene los elementos del data adapter, crea un adaptador para
el ListView
y se lo asigna con el método setListAdapter de la clase ListActivity.
private void refrescar()
{
//obtenemos todos los registros
List<Texto> values = datasource.getAllTextos();
//creamos el adaptador para
la lista
ArrayAdapter<Texto>
adapter = new ArrayAdapter<Texto>(this,
android.R.layout.simple_list_item_1, values);
//y finalmente lo asignamos
setListAdapter(adapter);
//actualizamos el contador
txtRegistros.setText(Integer.toString(values.size()));
}
Y ya tenemos la aplicación lista para ejecutar: