PDM Avanzado Datos Bases de datos

De MediaWiki
Saltar a: navegación, buscar

Introdución

Información adicional: http://developer.android.com/guide/topics/data/data-storage.html

Android permite manexar bases de datos SQLite.

Neste curso partimos de que o alumno coñece o que é unha base de datos relacional e todo o que leva consigo (claves primarias, atributos, nulos, sentenzas SQL,...)

Máis información: http://es.wikipedia.org/wiki/Base_de_datos_relacional

Creación dunha base de datos

O primeiro que temos que facer é crear unha clase que herde da clase abstracta SQLiteOpenHelper.

Nota: Podemos facelo nun paquete separado para dar maior claridade ao noso proxecto.

Ao facelo, o IDE, obrigaranos a crear un construtor e uns métodos asociados á clase abstracta SQLiteOpenHelper.

Ao final de todo teremos o seguinte código, partindo que a clase que creamos ten de nome BaseDatos:

  1. import android.content.Context;
  2. import android.database.sqlite.SQLiteDatabase;
  3. import android.database.sqlite.SQLiteDatabase.CursorFactory;
  4. import android.database.sqlite.SQLiteOpenHelper;
  5.  
  6. public class BaseDatos extends SQLiteOpenHelper{
  7.  
  8.         public BaseDatos(Context context, String name, CursorFactory factory,
  9.                         int version) {
  10.                 super(context, name, factory, version);
  11.                 // TODO Auto-generated constructor stub
  12.         }
  13.  
  14.         @Override
  15.         public void onCreate(SQLiteDatabase db) {
  16.                 // TODO Auto-generated method stub
  17.                
  18.         }
  19.  
  20.         @Override
  21.         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  22.                 // TODO Auto-generated method stub
  23.                
  24.         }
  25. }


Analicemos o construtor.

  1.         public BaseDatos(Context context, String name, CursorFactory factory,
  2.                         int version) {
  3.                 super(context, name, factory, version);
  4.                 // TODO Auto-generated constructor stub
  5.         }


Ten catro parámetros:

  • Context context: O contexto da Activity.
  • String name: O nome da base de datos a abrir.
  • CursorFactory factory: Este valor normalmente leva null xa que é utilizado cando queremos facer consultas que devolvan Cursores que nos van permitir acceder ós datos dunha consulta de forma aleatoria.
  • int version: Versión da base de datos. Verémolo despois.

Para facer máis sinxelo o construtor ímolo modificar e deixarémolo así:

  1.         public final static String NOME_BD="basedatos";
  2.         public final static int VERSION_BD=1;
  3.        
  4.         public BaseDatos(Context context) {
  5.                 super(context, NOME_BD, null, VERSION_BD);
  6.                 // TODO Auto-generated constructor stub
  7.         }

Cando dende a nosa Activity cremos un obxecto desta clase e abrimos a base de datos, chamará ós métodos onCreate ou onUpgrade segundo os casos.

  • Se a base de datos non existe, chamará ao método onCreate. Será dentro deste método onde se poñan as ordes para crear as diferentes táboas da nosa base de datos.
  • Se a base de datos xa existe, e a versión da base de datos é diferente, chamará ao método onUpgrade. Será dentro deste método onde poñamos as ordes de modificación das táboas da nosa base de datos.


Poñamos un exemplo:

  • Un usuario descarga a nosa aplicación do Market e empeza a traballar con ela. A primeira vez chamará ao método onCreate e xeraranse as táboas necesarias.
  • Cando leva un tempo traballando coa aplicación (engadiu datos ás táboas), decide actualizar a nosa aplicación. Imaxinemos que na actualización, necesitamos engadir unha táboa nova.
    • Como a base de datos xa existe, non vai pasar polo método onCreate.
    • Como facemos para engadir a nova táboa?
      • Cambiamos o número de versión da base de datos e subindo a aplicación compilada ao Market.
      • Cando o usuario descargue a aplicación actualizada executará o método onUpgrade e é nese método onde teremos a creación da nova táboa.
      • Os datos almacenados non se perden.


Nota: Veremos máis adiante que cando se crea a base de datos, gárdase no cartafol /data/data/paquete_aplicación/databases/

Normalmente a base de datos xa a teremos feita (cunha ferramenta externa) e o que facemos é copiala a dito cartafol. Cando o usuario abra a base de datos xa non vai pasar polo método onCreate.


Para que se chamen a ditos procedementos (onCreate / onUpgrade) será necesario crear un obxecto da clase que deriva de SQLiteOpenHelper e chamar a un destes métodos:


Manualmente

Debemos crear e sobrescribir o método onCreate para crear as táboas que conforman a base de datos.

Dito método (onCreate) trae como parámetro un obxecto da clase SqLiteDatabase que posúe o método execSQL para lanzar as ordes SQL necesarias.

Máis información sobre a orde Create Table: https://www.sqlite.org/lang_createtable.html

Se necesitamos modificar as táboas ou crear novas deberemos sobrescribir o método onUpgrade.


Manual: Caso práctico

Nesta práctica imos crear unha base de datos cunha táboa.

Para facelo teremos que abrir a base de datos en modo lectura ou lectura/escritura como xa comentamos. Neste exemplo vaise abrir en modo lectura/escritura.


Aviso Se o alumno está a probar a aplicación nun dispositivo real non hai opción de comprobar se e base de datos está creada a no ser que o dispositivo físico estea rooteado.



Creamos a clase que vai xestionar a BD
Se non o temos creado antes, crearemos un novo paquete de nome: Persistencia como un subpaquete do teu paquete principal.
Se non o temos creado antes, crearemos un novo paquete de nome: BasesDatos como un subpaquete do paquete anterior.


  • Neste exemplo imos crear unha clase de nome UD06_01_BaseDatos que derive da clase SQLiteOpenHelper como explicamos antes. Esta clase estará creada dentro do paquete BasesDatos


  • O nome da base de datos será UD06_01_BD_EXEMPLO
  • Faremos que no método onCreate se cre a seguinte táboa:
  • Táboa: AULAS (_id integer autonumérico clave primaria, nome varchar(50))
  • A modo de exemplo, faremos que no método onUpgrade se borren todas as táboas e se volvan crear.


Creamos a clase UD06_01_BaseDatos:

Código da clase UD06_01_BaseDatos
Obxectivo: Clase que vai executar as ordes para a creación das táboas e operacións contra a base de datos (SELECT, UPDATE, DELETE).

  1. package es.cursoandroid.cifprodolfoucha.aprendiendo.Persistencia.BasesDatos;
  2.  
  3. import android.content.Context;
  4. import android.database.sqlite.SQLiteDatabase;
  5. import android.database.sqlite.SQLiteOpenHelper;
  6.  
  7. public class UD06_01_BaseDatos extends SQLiteOpenHelper{
  8.     public final static String NOME_BD="UD06_01_BD_EXEMPLO";
  9.     public final static int VERSION_BD=1;
  10.  
  11.     private String CREAR_TABOA_AULAS ="CREATE TABLE AULAS ( " +
  12.             "_id  INTEGER PRIMARY KEY AUTOINCREMENT," +
  13.             "nome VARCHAR( 50 )  NOT NULL)";
  14.  
  15.  
  16.     public UD06_01_BaseDatos(Context context) {
  17.         super(context, NOME_BD, null, VERSION_BD);
  18.         // TODO Auto-generated constructor stub
  19.     }
  20.  
  21.     @Override
  22.     public void onCreate(SQLiteDatabase db) {
  23.         // TODO Auto-generated method stub
  24.         db.execSQL(CREAR_TABOA_AULAS);
  25.  
  26.     }
  27.  
  28.     @Override
  29.     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  30.         // TODO Auto-generated method stub
  31.  
  32.         db.execSQL("DROP TABLE IF EXISTS AULAS");
  33.         onCreate(db);
  34.     }
  35.  
  36. }


Se executamos máis veces a aplicación non vai volver a executar o método onCreate xa que a base de datos está creada.
Teríamos que desinstalar a aplicación ou cambiar o número de versión da base de datos (iría ao método onUpgrade).



Creamos a Activity Principal
  • Dentro do paquete BasesDatos (creado previamente no paso anterior) crear unha nova 'Empty Activity' de nome: UD06_01_DatosPersistentes_BD de tipo Launcher e sen compatibilidade.
Modifica o arquivo AndroidManifiest.xml e engade unha label á activity como xa vimos na creación do proxecto base.



Código xml do layout

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.    xmlns:app="http://schemas.android.com/apk/res-auto"
  4.    xmlns:tools="http://schemas.android.com/tools"
  5.    android:layout_width="match_parent"
  6.    android:layout_height="match_parent"
  7.    tools:context=".Persistencia.BasesDatos.UD06_01_A1_DatosPersistentes_BD">
  8.  
  9.     <Button
  10.        android:id="@+id/btnCrearBD_UD06_01_A1_BaseDatos"
  11.        android:layout_width="wrap_content"
  12.        android:layout_height="wrap_content"
  13.        android:layout_marginBottom="8dp"
  14.        android:layout_marginEnd="8dp"
  15.        android:layout_marginStart="8dp"
  16.        android:layout_marginTop="8dp"
  17.        android:text="CREAR BASE DE DATOS"
  18.        app:layout_constraintBottom_toBottomOf="parent"
  19.        app:layout_constraintEnd_toEndOf="parent"
  20.        app:layout_constraintStart_toStartOf="parent"
  21.        app:layout_constraintTop_toTopOf="parent" />
  22. </android.support.constraint.ConstraintLayout>


Código da clase UD06_01_DatosPersistentes_BD
Obxectivo: Crear unha base de datos.

  1. package es.cursoandroid.cifprodolfoucha.aprendiendo.Persistencia.BasesDatos;
  2.  
  3. import android.app.Activity;
  4. import android.os.Bundle;
  5. import android.view.View;
  6. import android.widget.Toast;
  7.  
  8. import es.cursoandroid.cifprodolfoucha.aprendiendo.R;
  9.  
  10. public class UD06_01_DatosPersistentes_BD extends Activity {
  11.     private UD06_01_BaseDatos baseDatos;
  12.    
  13.     private void xestionarEventos(){
  14.  
  15.         findViewById(R.id.btnCrearBD_UD06_01_A1_BaseDatos).setOnClickListener(new View.OnClickListener() {
  16.             @Override
  17.             public void onClick(View v) {
  18.  
  19.                 baseDatos = new UD06_01_BaseDatos(getApplicationContext());
  20.                 baseDatos.getWritableDatabase();
  21.  
  22.                 Toast.makeText(getApplicationContext(), "BASE DE DATOS CREADA", Toast.LENGTH_LONG).show();
  23.  
  24.             }
  25.         });
  26.  
  27.     }
  28.  
  29.     @Override
  30.     protected void onCreate(Bundle savedInstanceState) {
  31.         super.onCreate(savedInstanceState);
  32.         setContentView(R.layout.activity_ud06_01__datos_persistentes__bd);
  33.  
  34.         xestionarEventos();
  35.     }
  36. }
  • Liña 11: Creamos unha propiedade que vai servir para poder realizar operacións contra a base de datos.
  • Liña 19: Instanciamos a propiedade.
  • Liña 20: Abrimos a base de datos para operacións de escritura / lectura. É agora cando, se a base de datos non existe, chamará o método onCreate.

Ferramenta externa: SqliteStudio

Normalmente, cando creamos a nosa aplicación e vai utilizar unha base de datos, non imos creala no método onCreate mediante ordes CREATE TABLE.

O lóxico será tela creada cuns datos iniciais.

Esta base de datos se atopará nun cartafol (por exemplo /assets/) e copiaremos a base de datos dende /assets/ ao cartafol onde Android busca a base de datos.


Para crear a base de datos temos moitas ferramentas gratuítas para facelo.

Por exemplo: http://sqlitestudio.pl/


Nota: Se estamos a utilizar Linux deberemos darlle permiso de execución ao arquivo baixado coa orde:

         chmod 755 sqlitestudio-2.1.4.bin

e despois executala:

         ./sqlitestudio-2.1.4.bin


  • Unha vez no programa podemos crear unha base de datos nova indo o menú Base de datos => Engadir Base de datos.

PDM Avanzada DatosPersistentes 24.jpg

Aquí temos que indicar o sitio físico onde se vai gardar / abrir a base de datos e o nome da mesma.
A versión poñeremos sqlite3.
Debemos premer o botón de 'Ok'.


  • Agora debemos de escoller a opción de Tablas e unha vez escollida premer a opción Nueva Tabla da parte superior.

PDM Avanzada DatosPersistentes 25.jpg


  • Esta pantalla é bastante intuitiva.
  • Damos un nome á táboa (no exemplo AULAS) e prememos o botón 'Añadir Columna'.

PDM Avanzada DatosPersistentes 26.jpg


  • Agora só temos que engadir as columnas co tipo de dato adecuado:

PDM Avanzada DatosPersistentes 27.jpg

Se prememos na opción configurar podemos facer varias cousas dependendo do que teñamos escollido.
Por exemplo:
  • Na clave primaria: podemos facer que o campo sexa autonumérico. Quere isto dicir que cando engadimos unha nova fila non é necesario darlle un valor a dita columna xa que vai xerar automaticamente un valor.
  • No valor por defecto: podemos especificar o valor que terá dita columna na fila se non enviamos ningún.
Unha vez temos todos os campo debemos premer o botón de Crear. Aparecerá unha nova ventá coa orde SQL para crear a táboa. Poderíamos copiala e gardala xa que podemos necesitar ter as ordes para crear a base de datos:

PDM Avanzada bd 5.jpg


  • Unha vez creada a táboa xa podemos engadir filas ou modificar os campos premendo sobre a pestana de datos:

PDM Avanzada DatosPersistentes 29.jpg


  • Temos que premer sobre o botón Máis, engadir os datos e ao rematar de engadir as filas, premer sobre o botón Commit:

PDM Avanzada DatosPersistentes 30.jpg


Copiando a base de datos dende /assets/ a .../databases/

Como comentamos antes, a base de datos podemos tela xa creada e con datos preparada para ser utilizada pola nosa aplicación.

Para que esta poida utilizala teremos que copiala ao cartafol /data/data/paquete_aplicación/databases/

A forma de copiala xa a vimos cando vimos o apartado de xestión de arquivos.

Normalmente a copia debería facerse nun fío separado do fío principal. O uso dos fíos xa verémolo na unidade 7.


O único que temos que ter coidado é que se copiamos a base de datos, o cartafol .../databases/ non está creado e teremos que crealo nos.

Fisicamente as bases de datos se crean no cartafol /data/data/nome do paquete/databases/NOME BD.

Se queremos ter unha referencia ao obxecto File que apunte á nosa base de datos podemos usar a chamada:

getDatabasePath(NOME_BD)

ou ben ter unha referencia ao seu path e partires del obter un obxecto da clase File:

String pathbd = "/data/data/" + getApplicationContext().getPackageName()+"/databases/";


Como comentamos antes o normal é ter a base de datos creada no cartafol /assets/ e copiala o cartafol /databases/.

  • Se o facemos así será necesario crear previamente dito cartafol /databases/:
  1. String pathbd = "/data/data/" + contexto.getPackageName()+"/databases/";
  2. File ruta = new File(pathbd);
  3. ruta.mkdirs();


  • Unha vez creado o cartafol xa podemos copiar a base de datos. Neste exemplo non se usa un fío de execución.
Comprobamos se existe a base de datos previamente, xa que se non, cada vez que iniciáramos a aplicación borraríamos a base de datos.
  1.                 String bddestino = "/data/data/" + getPackageName() + "/databases/"
  2.                                 + UD5_05_BASEDATOS.NOME_BD;
  3.                 File file = new File(bddestino);
  4.                 if (file.exists())
  5.                         return; // XA EXISTE A BASE DE DATOS

Copiamos a base de datos.

  1.                 String pathbd = "/data/data/" + getPackageName()
  2.                                 + "/databases/";
  3.                 File filepathdb = new File(pathbd);
  4.                 filepathdb.mkdirs();
  5.  
  6.                 InputStream inputstream;
  7.                 try {
  8.                         inputstream = getAssets().open(UD5_05_BASEDATOS.NOME_BD);
  9.                         OutputStream outputstream = new FileOutputStream(bddestino);
  10.  
  11.                         int tamread;
  12.                         byte[] buffer = new byte[2048];
  13.  
  14.                         while ((tamread = inputstream.read(buffer)) > 0) {
  15.                                 outputstream.write(buffer, 0, tamread);
  16.                         }
  17.  
  18.                         inputstream.close();
  19.                         outputstream.flush();
  20.                         outputstream.close();
  21.                 } catch (IOException e) {
  22.                         // TODO Auto-generated catch block
  23.                         e.printStackTrace();
  24.                 }



Caso práctico

Imos copiar unha base de datos feita coa ferramenta anterior dende o cartafol /assets/ ao cartafol /databases/.



Preparación

Hai que descomprimir e copiar o seguinte arquivo ao cartafol /assets/ do voso proxecto.

Media:UD06_02_BD_FILE.zip

O cartafol /assets/ xa o deberíamos ter creado dunha práctica anterior.


Nota: O alumno pode utilizar a súa propia base de datos xerada coa ferramenta anterior.

Creamos a Activity
  • Dentro do paquete BasesDatos (creado previamente no paso anterior) crear unha nova 'Empty Activity' de nome: UD06_02_DatosPersistentes_BD de tipo Launcher e sen compatibilidade.
Modifica o arquivo AndroidManifiest.xml e engade unha label á activity como xa vimos na creación do proxecto base.



Código da clase UD06_02_BaseDatos

Esta clase é a que vai xestionar a base de datos. É igual ao código anterior pero cambiando o nome da base de datos.
Como neste exercicio o que imos facer é copiar unha base de datos con táboas poderíamos non poñer o código no método onCreate e método onUpgrade, pero lembrar que se fixéramos unha actualización da nosa aplicación, teríamos que poñer neses método o código necesario para adaptar a base de datos aos novos requerimentos (podería ser necesario borrar toda a base de datos e volver a creala)


  • Como podemos ter varias referencias a esta clase (que fagan uso dela diferentes fragments, por exemplo), imos asegurarnos que soamente existe unha instancia da mesma, mediante o uso de Singleton.
Isto se implementa da forma seguinte:
  1. public class MiClase {
  2.     private static MiClase sInstance;
  3.  
  4.  
  5.  
  6.     public static synchronized MiClase getInstance() {
  7.         if (sInstance == null) {
  8.             sInstance = new MiClase ();
  9.         }
  10.         return sInstance;
  11.     }
  12.  
  13. }


Agora se quero facer uso desta clase non fago: MiClase miClase = new MiClase();
Terei que poñer: MiClase miClase = MiClase.getInstance();
Se o fago á vez dende diferentes clase sempre estarei empregando a mesma instancia.
Da outra forma teríamos un obxecto diferente.


  • Aplicado ao noso caso:
  1. package es.cursoandroid.cifprodolfoucha.aprendiendo.Persistencia.BasesDatos;
  2.  
  3. import android.content.Context;
  4. import android.database.sqlite.SQLiteDatabase;
  5. import android.database.sqlite.SQLiteOpenHelper;
  6.  
  7. public class UD06_02_BaseDatos extends SQLiteOpenHelper{
  8.     public final static String NOME_BD="UD06_02_BD_FILE.db";
  9.     public final static int VERSION_BD=1;
  10.     private static UD06_02_BaseDatos sInstance;
  11.  
  12.     public static synchronized UD06_02_BaseDatos getInstance(Context context) {
  13.         // Use the application context, which will ensure that you
  14.         // don't accidentally leak an Activity's context.
  15.         // See this article for more information: http://bit.ly/6LRzfx
  16.         if (sInstance == null) {
  17.             sInstance = new UD06_02_BaseDatos(context.getApplicationContext());
  18.         }
  19.         return sInstance;
  20.     }
  21.  
  22.     private String CREAR_TABOA_AULAS ="CREATE TABLE AULAS ( " +
  23.             "_id  INTEGER PRIMARY KEY AUTOINCREMENT," +
  24.             "nome VARCHAR( 50 )  NOT NULL)";
  25.  
  26.  
  27.     public UD06_02_BaseDatos(Context context) {
  28.         super(context, NOME_BD, null, VERSION_BD);
  29.         // TODO Auto-generated constructor stub
  30.     }
  31.  
  32.     @Override
  33.     public void onCreate(SQLiteDatabase db) {
  34.         // TODO Auto-generated method stub
  35.        // db.execSQL(CREAR_TABOA_AULAS);
  36.  
  37.     }
  38.  
  39.     @Override
  40.     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  41.         // TODO Auto-generated method stub
  42.  
  43.         // db.execSQL("DROP TABLE IF EXISTS AULAS");
  44.         // onCreate(db);
  45.     }
  46.  
  47. }


Código do layout xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.    xmlns:app="http://schemas.android.com/apk/res-auto"
  4.    xmlns:tools="http://schemas.android.com/tools"
  5.    android:layout_width="match_parent"
  6.    android:layout_height="match_parent"
  7.    tools:context=".Persistencia.BasesDatos.UD06_02_DatosPersistentes_BD">
  8.  
  9.     <Button
  10.        android:id="@+id/btnAbrirBD_UD06_02_BaseDatos"
  11.        android:layout_width="wrap_content"
  12.        android:layout_height="wrap_content"
  13.        android:layout_marginBottom="8dp"
  14.        android:layout_marginEnd="8dp"
  15.        android:layout_marginStart="8dp"
  16.        android:layout_marginTop="8dp"
  17.        android:text="ABRIR BASE DE DATOS"
  18.        app:layout_constraintBottom_toBottomOf="parent"
  19.        app:layout_constraintEnd_toEndOf="parent"
  20.        app:layout_constraintStart_toStartOf="parent"
  21.        app:layout_constraintTop_toTopOf="parent" />
  22. </android.support.constraint.ConstraintLayout>


Código da clase UD06_02_DatosPersistentes_BD
Obxectivo: Copiar unha base de datos dende /assets/ ao cartafol /databases/.

  1. package es.cursoandroid.cifprodolfoucha.aprendiendo.Persistencia.BasesDatos;
  2.  
  3. import android.app.Activity;
  4. import android.os.Bundle;
  5. import android.view.View;
  6. import android.widget.Toast;
  7.  
  8. import java.io.File;
  9. import java.io.FileOutputStream;
  10. import java.io.IOException;
  11. import java.io.InputStream;
  12. import java.io.OutputStream;
  13.  
  14. import es.cursoandroid.cifprodolfoucha.aprendiendo.R;
  15.  
  16. public class UD06_02_DatosPersistentes_BD extends Activity {
  17.     private UD06_02_BaseDatos baseDatos;
  18.  
  19.     private void copiarBD() {
  20.         String bddestino = "/data/data/" + getPackageName() + "/databases/"
  21.                 + UD06_02_BaseDatos.NOME_BD;
  22.         File file = new File(bddestino);
  23.         if (file.exists()) {
  24.             Toast.makeText(getApplicationContext(), "A BD NON SE VAI COPIAR. XA EXISTE", Toast.LENGTH_LONG).show();
  25.             return; // XA EXISTE A BASE DE DATOS
  26.         }
  27.  
  28.         // En caso de que non exista creamos o cartafol /databases/
  29.         String pathbd = "/data/data/" + getPackageName()
  30.                 + "/databases";
  31.         File filepathdb = new File(pathbd);
  32.         if (!filepathdb.exists()) {
  33.             filepathdb.mkdirs();
  34.         }
  35.  
  36.         InputStream inputstream;
  37.         try {
  38.             inputstream = getAssets().open(UD06_02_BaseDatos.NOME_BD);
  39.             OutputStream outputstream = new FileOutputStream(bddestino);
  40.  
  41.             int tamread;
  42.             byte[] buffer = new byte[2048];
  43.  
  44.             while ((tamread = inputstream.read(buffer)) > 0) {
  45.                 outputstream.write(buffer, 0, tamread);
  46.             }
  47.  
  48.             inputstream.close();
  49.             outputstream.flush();
  50.             outputstream.close();
  51.             Toast.makeText(getApplicationContext(), "BASE DE DATOS COPIADA", Toast.LENGTH_LONG).show();
  52.         } catch (IOException e) {
  53.             // TODO Auto-generated catch block
  54.             e.printStackTrace();
  55.         }
  56.  
  57.     }
  58.  
  59.     private void xestionarEventos(){
  60.  
  61.         findViewById(R.id.btnAbrirBD_UD06_02_BaseDatos).setOnClickListener(new View.OnClickListener() {
  62.             @Override
  63.             public void onClick(View v) {
  64.                 baseDatos = UD06_02_BaseDatos.getInstance(getApplicationContext());
  65.                 baseDatos.getWritableDatabase();
  66.  
  67.                 Toast.makeText(getApplicationContext(), "BASE DE DATOS ABERTA", Toast.LENGTH_LONG).show();
  68.             }
  69.         });
  70.     }
  71.  
  72.     @Override
  73.     protected void onCreate(Bundle savedInstanceState) {
  74.         super.onCreate(savedInstanceState);
  75.         setContentView(R.layout.activity_ud06_02__datos_persistentes__bd);
  76.  
  77.         xestionarEventos();
  78.         copiarBD();
  79.     }
  80. }
  • Liñas 23-26: Miramos se a base de datos existe. Neste exemplo non a copiamos se existe. Nunha aplicación real poderíamos enviar un parámetro para indicar se queremos sobreesribila xa que pode ser necesario se queremos 'inicializar' a aplicación.
  • Liñas 59-70: Xestionamos o evento de click sobre o botón de abrir. Como a base de datos xa está copiada podemos utilizala.
  • Liña 64: Fixarse como empregamos o patrón 'Singleton' para obter unha instancia da clase que xestiona a base de datos.
  • Liña 65: Fixarse que abrimos a base de datos para ler/escribir información nela.

Xestionar a base de datos dende consola

Se estamos a utilizar o emulador ou podemos ser root no dispositivo real, podemos conectarnos á base de datos dende consola.

O proceso é o seguinte:

  • Abrimos un terminal (linux) ou consola (windows).
  • Facemos un cd do caratafol onde está instalado o SDK e dentro deste ir ao cartafol /sdk/plataform-tools/.
Se listades os arquivo vos debe aparecer un executable de nome 'adb'.
  • Dende a consola deberedes escribir:
  • adb shell (WINDOWS)
  • ./adb shell (LINUX)
  • Unha vez no shell, conectamos coa base de datos escribindo:
  • sqlite3 /data/data/o_voso_paquete/databases/NOME_BASEDATOS
  • Agora estamos conectados a SQLIte e podemos executar ordes SQL (deben acabar en ; ).
Por exemplo:
  • .tables : lista as táboas.
  • SELECT * FROM NOME_TABOA; (amosa os datos)
  • Para saír do sqlite poñeremos:
.exit
  • Para saír do adb shell:
exit



Nota: Coidado cas maiúsculas / minúsculas.

Operacións sobre unha base de datos

Introdución

As operacións que imos facer sobre a base de datos son:

  • SELECT: Selección.
  • UPDATE: Actualización.
  • DELETE: Borrado.
  • INSERT: Engadir.

Nota: Neste curso consideramos que os alumnos teñen coñecementos para entender as ordes SQL que dan soporte a estas operacións.


Para poder realizar as operacións anteriormente comentadas imos necesitar facer uso dun obxecto da clase SQLiteDatabase.

Podemos obtelo de dúas formas:

  • Dito obxecto o temos como parámetro no método onCreate e no método onUpgrade da clase que deriva de SQLiteOpenHelper:
  1.         @Override
  2.         public void onCreate(SQLiteDatabase db) {
  3.                 // TODO Auto-generated method stub
  4.  
  5.         }
  6.  
  7.         @Override
  8.         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  9.                 // TODO Auto-generated method stub
  10.                
  11.         }
Polo tanto podemos facer uso do obxecto db para realizar as operacións SQL. Nestes métodos normalmente utilizaranse cando non se fai o proceso de copia da base de datos e estamos a crear a base de datos manualmente con ordes 'create table'.
  • Cando abrimos a base de datos para escribir ou para ler:
  1. SQLiteDatabase sqlLiteDB = ud4_05_baseDatos.getWritableDatabase();
Nota: Código baseado a partires do exemplo anterior.

Agora con dito obxecto podemos facer as operacións SQL.

INSERT

Engadir unha nova fila.

Máis información en: http://www.w3schools.com/sql/sql_insert.asp

Temos dúas formas de engadir datos:

  • Utilizando a orden INSERT de SQL:
  1. sqlLiteDB.execSQL("INSERT INTO NOME_TABOA (campo1,campo2,....) VALUES ('valor1','valor2',....)");

Nota: Os valores numéricos non levan comillas.


  • A segunda forma é utilizando un obxecto da clase ContentValues, o cal garda pares da forma clave-valor, e no noso caso, serán pares nome_columna-valor.

Chamaremos despois aos métodos insert(), update() e delete() da clase SQLiteDatabase.

Por exemplo:

  1.                 ContentValues datosexemplo = new ContentValues();
  2.                 datosexemplo.put("NOME_COL1", "Valor col1_a");
  3.                 datosexemplo.put("NOME_COL2", "Valor_col2_a");
  4.                 long idFila1 = sqlLiteDB.insert(NOME_TABOA, null, datosexemplo);
  5.                
  6.                 datosexemplo.clear();
  7.                 datosexemplo.put("NOME_COL1", "Valor col1_b");
  8.                 datosexemplo.put("NOME_COL2", "Valor_col2_b");
  9.                 long idFila2 = sqlLiteDB.insert(NOME_TABOA, null, datosexemplo);


  • O segundo parámetro leva de valor null, xa que só se usa en contadas ocasións, coma por exemplo, para engadir rexistros baleiros.
  • Neste exemplo supoñemos que a táboa onde se van engadir os rexistros ten de clave primaria un campo autonumérico.
  • O método insert devolve o id da fila engadida (a clave principal é autonumérica) ou -1 se hai un erro.

UPDATE

Actualiza datos.

Máis información en: http://www.w3schools.com/sql/sql_update.asp

O método update() leva como terceiro parámetro a condición que teñen que cumprir as filas.

  1. ContentValues datosexemplo = new ContentValues();
  2. datosexemplo.put("COLUMNA_MODIFICAR","TEXTO NOVO");
  3. String condicionwhere = "COL1=?";
  4. String[] parametros = new String[]{'Texto a buscar'};
  5. int rexistrosafectados = sqlLiteDB.update(NOME_TABOA,datosexemplo,condicionwhere,parametros);

Se queremos unha condición composta (por exemplo con AND):

  1. ContentValues datosexemplo = new ContentValues();
  2. datosexemplo.put("COLUMNA_MODIFICAR","TEXTO NOVO");
  3. String condicionwhere = "COL1=? AND COL2=?";
  4. String[] parametros = new String[]{'Texto1','Texto2'};
  5. int rexistrosafectados = sqlLiteDB.update(NOME_TABOA,datosexemplo,condicionwhere,parametros);

Nun exemplo concreto:

TÁBOA AULAS (_id, nome):

Esta consulta sql:

  1. UPDATE AULAS
  2. SET nome='Novo nome'
  3. WHERE _id=5;

Pasará a ser:

  1. ContentValues datos = new ContentValues();
  2. datos.put("nome","Novo nome");
  3. String condicionwhere = "_id=?";
  4. String[] parametros = new String[]{String.valueOf(5)};
  5. int rexistrosafectados = sqlLiteDB.update("AULAS",datos,condicionwhere,parametros);


Aquí hai que sinalar varios aspectos:

  • Se queremos poñer unha condición a un campo de tipo texto NON teremos que poñer o parámetro entre comiñas:
    • NOME + "=?" (Correcto)
    • NOME + "='?'" (Incorrecto)
  • Se o facemos directamente na orde SQL entón si que as levaría:
String CONSULTA="SELECT id,nome FROM CLASES WHERE nome = 'AULA 1'";
  • Cada '?' se corresponde cun dato do array de parámetros. No noso exemplo enviamos o ID que teremos que converter a String:
String[] parametros = new String[]{String.valueOf(5)};
  • O método update devolve o número de filas afectadas ou -1 en caso de erro.

DELETE

Borra filas.

Máis información: http://www.w3schools.com/sql/sql_delete.asp

O método delete() é parecido ao anterior.

Pásase como primeiro parámetro a táboa e como segundo a condición que teñen que cumprir as filas para ser borradas:

  1. String condicionwhere = "COL1=?";
  2. String[] parametros = new String[]{'Valor a buscar'};
  3. int rexistrosafectados = sqlLiteDB.delete(NOME_TABOA,condicionwhere,parametros);


  • O método delete devolve o número de filas afectadas ou -1 en caso de erro.
  • Os conceptos da cláusula Where son os mesmos que no caso do Update.


Nun exemplo concreto:

TÁBOA AULAS (_id, nome):

Esta consulta sql:

  1. DELETE FROM AULAS
  2. WHERE _id=5;

Pasará a ser:

  1. String condicionwhere = "_id=?";
  2. String[] parametros = new String[]{String.valueOf(5)};
  3. int rexistrosafectados = sqlLiteDB.delete("AULAS",condicionwhere,parametros);

SELECT

A selección de filas leva consigo que o resultado vai poder traer de volta moitas filas.

Veremos máis adiante como devolver esas filas como se foran un Array de obxectos.


Para facer consultas á base de datos podemos utilizar dous métodos da clase SQLiteQueryBuilder : rawQuery e query

  • rawQuery
A diferenza vai estar na forma de facer a chamada, xa que rawQuery deixa enviar a orde SQL e query utiliza outra estrutura de chamada.
Por exemplo:
  1.   Cursor cursor = sqlLiteDB.rawQuery("select _id,nome from AULAS where _id = ?", new String[] { String.valueOf(5) });
  • O segundo parámetro é un array de Strings no caso de que a consulta leve parámetros (símbolo ?).
  • query
  1. Cursor cursor = sqlLiteDB.query(DATABASE_TABLE,
  2.   new String[] { col1,col2,... },
  3.   null, null, null, null, null);
Os parámetros en query son:
  • DATABASE_TABLE: Nome da táboa.
  • Lista de columnas, nun array de String. Se poñemos null devolve todas.
  • Cláusula where
  • Array de string no caso de que a cláusula where leve parámetros da forma ‘id=?’, é dicir, co caracter ?.
  • Array de String onde irán os nomes das columnas para facer group by.
  • Array de String onde irán os nomes das columnas para facer having.
  • Array de String onde irán os nomes das columnas para facer order by.
Por exemplo, a orde SQL:
SELECT nome FROM AULAS
sería:
  1. Cursor cursor = sqlLiteDB.query("AULAS",
  2.   new String[] { nome },
  3.   null, null, null, null, null);

Nos imos a usar a primeira opción.


Os dous tipos de consultas (rawquery e query) devolven un obxecto Cursor. Para saber o nº de filas devoltas temos o método getCount() e dispoñemos de diferentes métodos para movernos polas filas do cursor.

O proceso normalmente será o de percorrer todo o cursor e devolver un ArrayList dun tipo determinado (verase despois).

  1.   Cursor cursor = sqlLiteDB.rawQuery("select _id,nome from AULAS", null);
  2.   if (cursor.moveToFirst()) {                // Se non ten datos xa non entra
  3.         while (!cursor.isAfterLast()) {     // Quédase no bucle ata que remata de percorrer o cursor. Fixarse que leva un ! (not) diante
  4.                 long id =cursor.getLong(0);
  5.                 String nome = cursor.getString(1);
  6.                 cursor.moveToNext();
  7.         }
  8.   }
Dentro do bucle temos acceso ós datos de cada fila.
Cada columna da consulta está referenciada por un número, empezando en 0 (a columna _id correspóndese coa número 0 e a columna nome coa número 1). Isto é asi porque no select están nesa orde.


MOI IMPORTANTE: Cando rematemos de procesar a fila teremos que pasar á seguinte dentro do cursor e temos que chamar ó método moveToNext(). Se non o facemos quedaremos nun bucle infinito.
Neste exemplo non estamos a facer nada con cada fila devolta. Poderíamos ter una Array e ir engadindo a dito Array os valores que devolve a consulta, pero veremos a continuación que é mellor facelo utilizando obxectos.





Aclaración: Cando creamos a táboa AULAS puxemos como clave primaria _id. Por que utilizar un guión baixo e ese nome ?
Porque unha consulta á base de datos pode devolver directamente o Cursor con todos os datos e 'cargar' ditos datos nun adaptador especial denominado SimpleCursorAdapter] e asociar dito Adaptador a un elemento gráfico como unha lista.
Por exemplo:
  1. SimpleCursorAdapter mAdapter = new SimpleCursorAdapter(this,R.layout.list_layout_creado, cursor, new String[] { "nome" },new int[] { android.R.id.text1});
Sendo:
  • 'list_layout_creado': O layout que vai amosar a ListView.
  • cursor: Os datos devoltos pola base de datos de todas as aulas.
  • nome: O nome da columna que queremos amosar da táboa da base de datos.
  • android.R.id.text1: Un TextView definido dentro do layout 'list_layout_creado'.
  • Para que funcione ten que existir unha columna '_id' na táboa onde se fai a consulta.
Lembrar que xa vimos o uso de adaptadores na Unidade 4 desta Wiki.



Onde facer as operacións

Neste curso imos facer as operacións contra a base de datos na clase que deriva da clase abstracta SQLiteOpenHelper (onde se atopan os métodos onCreate e onUpgrade).

Poderíamos facelo na activity a partires de obter o obxecto sqlLiteDB, pero desta forma quedará moito máis claro.


Polo tanto imos ter que gardar o obxecto sqlLiteDB na propia clase para poder ter acceso a el e poder facer as operacións.

Unha forma de facelo é a seguinte:

  • Definimos unha propiedade public dentro da clase SQLiteOpenHelper.
public class UD6_03_BaseDatos extends SQLiteOpenHelper{

        public SQLiteDatabase sqlLiteDB;
        ...............


  • Creamos un método abrirBD() y pecharBD():
  1. public class UD6_03_BaseDatos extends SQLiteOpenHelper{
  2.     public final static String NOME_BD="UD06_03_BD_FILE.db";
  3.     public final static int VERSION_BD=1;
  4.     private static UD06_03_BaseDatos sInstance;
  5.  
  6.     private SQLiteDatabase db;
  7.  
  8.     public static synchronized UD06_03_BaseDatos getInstance(Context context) {
  9.         // Use the application context, which will ensure that you
  10.         // don't accidentally leak an Activity's context.
  11.         // See this article for more information: http://bit.ly/6LRzfx
  12.         if (sInstance == null) {
  13.             sInstance = new UD06_03_BaseDatos(context.getApplicationContext());
  14.         }
  15.         return sInstance;
  16.     }
  17.         ...............
  18.     public void abrirBD(){
  19.         if (sqlLiteDB==null || !sqlLiteDB.isOpen()){
  20.           sqlLiteDB = sInstance.getWritableDatabase();
  21.         }
  22.     }
  23.  
  24.     public void pecharBD(){
  25.         if (sqlLiteDB!=null || sqlLiteDB.isOpen()){
  26.           sqlLiteDB.close();
  27.         }
  28.     }


  • Agora definimos os método que necesitemos para dar soporte á nosa aplicación.

Xuntando todo cos Modelos

Normalmente os datos dunha base de datos os imos 'modelizar' en forma de clases.

Así, se se ten unha táboa AULAS poderase modelizar utilizando unha clase AULAS que teña como propiedades as columnas da táboa.


Non pretendemos entrar a explicar o UML e o Diagrama de Clases.

Tampouco imos traballar con capas intermedias. Atoparedes manuais nos que se crea unha clase 'Adaptadora' que é a que traballa con ContentValues' e Cursors. Por enriba desta clase atoparíase unha clase 'de xestión' que sería a que exporía os métodos para facer operacións dende a interface do usuario e que convertía un obxecto da clase Aula a ContentValues, para ser engadida á base de datos (por exemplo) e tamén o proceso contrario, pasar de, por exemplo, un Cursor a un array de obxectos da clase Aulas.



Imos velo cun exemplo concreto:

  • TÁBOA AULAS: (_id, nome)
  • Definimos a clase que vai gardar esta información: Aulas
Creamos esta clase dentro do paquete 'BaseDatos' (nas prácticas teredes que crear un paquete cun nome concreto)
  1. public class Aulas {
  2.  
  3.         private long _id;
  4.         private String nome;
  5.        
  6.         public Aulas (long id, String nome){
  7.                 this._id=id;
  8.                 this.nome=nome;
  9.         }
  10.        
  11.         public long get_id() {
  12.                 return _id;
  13.         }
  14.         public void set_id(long _id) {
  15.                 this._id = _id;
  16.         }
  17.         public String getNome() {
  18.                 return nome;
  19.         }
  20.         public void setNome(String nome) {
  21.                 this.nome = nome;
  22.         }
  23.        
  24.         public String toString(){
  25.                 return nome;
  26.         }
  27. }

NOTA IMPORTANTE: Definimos o método toString() xa que imos 'cargar' ós elementos gráficos (como as listas=> ListView) con array de obxectos. Neses casos o elemento gráfico (a lista) amosa o que devolva o método toString().

NOTA:: Dentro de Android hai artigos que indican que facer métodos get e set para acceder as propiedades degradan o rendemento da aplicación e que sería conveniente acceder directamente ás propiedades (facéndoas public). En todas as aplicacións que levo feitas non atopei ese 'rendemento inferior' polo que supoño que só será apreciable en aquelas aplicacións que fagan un uso moi intensivo de datos.

  • Agora que temos definida a clase podemos definir os métodos que imos necesitar.

Nota: Lembrar que as operacións contra a base de datos se definirán na clase que deriva de SQLiteOpenHelper (onde se atopan os métodos onCreate e onUpgrade).

Por exemplo:
  • Método: engadirAula(Aulas aula_engadir).
Engade unha aula á base de datos.
Leva como parámetro a aula a engadir.
  1.         public long engadirAula(Aulas aula_engadir){
  2.                 ContentValues valores = new ContentValues();
  3.                 valores.put("nome", aula_engadir.getNome());
  4.                 long id = sqlLiteDB.insert("AULAS",null,valores);
  5.                
  6.                 return id;
  7.         }
Como vemos devolve o id da aula engadida. O id é xerado automaticamente pola BD cando engadimos unha fila (lembrar que o ID é autonumérico e non se envía).
Nota:Poderíamos devolver un obxecto da clase Aula co novo id xa posto.


  • Método: ArrayList<Aulas> obterAulas().
Devolve as aulas da táboa AULAS.
A forma de devolver moitos obxectos dunha clase é utilizando un ArrayList.
  1.         private final String CONSULTAR_AULAS ="SELECT _id,nome FROM AULAS order by nome";
  2.  
  3.         public ArrayList<Aulas> obterAulas() {
  4.                 ArrayList<Aulas> aulas_devolver = new ArrayList<Aulas>();
  5.  
  6.                 Cursor datosConsulta = sqlLiteDB.rawQuery(CONSULTAR_AULAS, null);
  7.                 if (datosConsulta.moveToFirst()) {
  8.                         Aulas aula;
  9.                         while (!datosConsulta.isAfterLast()) {
  10.                                 aula = new Aulas(datosConsulta.getLong(0),
  11.                                                 datosConsulta.getString(1));
  12.                                 aulas_devolver.add(aula);
  13.                                 datosConsulta.moveToNext();
  14.                         }
  15.                 }
  16.                 return aulas_devolver;
  17.         }

Caso práctico

Neste exemplo imos copiar unha base de datos dende /assets/ ao cartafol .../databases/.

Dita BD consta dunha única táboa de nome AULAS cos seguintes campos:

  • _id: clave autonumérica.
  • nome: nome da aula.

A aplicación consta dunha lista (ListView) unha caixa de texto (EditText) e tres botóns para realizar operacións de Alta-Baixa-Modificación sobre as aulas.

Cabe sinalar que por motivos de tempo non están contempladas todas as condicións que teríamos que ter en conta para facer as operacións.

Así, por exemplo, cando damos de alta unha aula, non se comproba que tiñamos escrito algo na caixa de texto.

Preparación

  • Deberemos de ter no cartafol /assets/ a base de datos. Descomprimir o arquivo e copiar a base de datos a dito cartafol:

Media:UD06_03_BD_FILE.zip

Creamos a clase que xestiona a base de datos

Nome da clase: UD06_03_BaseDatos

Código da clase que xestiona a base de datos.

  1. package es.cursoandroid.cifprodolfoucha.aprendiendo.Persistencia.BasesDatos;
  2.  
  3. import android.content.ContentValues;
  4. import android.content.Context;
  5. import android.database.Cursor;
  6. import android.database.sqlite.SQLiteDatabase;
  7. import android.database.sqlite.SQLiteOpenHelper;
  8.  
  9. import java.util.ArrayList;
  10.  
  11. public class UD06_03_BaseDatos extends SQLiteOpenHelper{
  12.     public final static String NOME_BD="UD06_03_BD_FILE.db";
  13.     public final static int VERSION_BD=1;
  14.     private static UD06_03_BaseDatos sInstance;
  15.     private SQLiteDatabase sqlLiteDB;
  16.  
  17.     /* SENTENZAS SQL */
  18.     private final String TABOA_AULAS="AULAS";
  19.     private final String CONSULTAR_AULAS ="SELECT _id,nome FROM AULAS order by nome";
  20.  
  21.  
  22.     public static synchronized UD06_03_BaseDatos getInstance(Context context) {
  23.         // Use the application context, which will ensure that you
  24.         // don't accidentally leak an Activity's context.
  25.         // See this article for more information: http://bit.ly/6LRzfx
  26.         if (sInstance == null) {
  27.             sInstance = new UD06_03_BaseDatos(context.getApplicationContext());
  28.         }
  29.         return sInstance;
  30.     }
  31.  
  32.     private String CREAR_TABOA_AULAS ="CREATE TABLE AULAS ( " +
  33.             "_id  INTEGER PRIMARY KEY AUTOINCREMENT," +
  34.             "nome VARCHAR( 50 )  NOT NULL)";
  35.  
  36.     public long engadirAula(Aulas aula_engadir){
  37.         ContentValues valores = new ContentValues();
  38.         valores.put("nome", aula_engadir.getNome());
  39.         long id = sqlLiteDB.insert("AULAS",null,valores);
  40.  
  41.         return id;
  42.     }
  43.  
  44.     public int borrarAula(Aulas aula){
  45.         String condicionwhere = "_id=?";
  46.         String[] parametros = new String[]{String.valueOf(aula.get_id())};
  47.         int rexistrosafectados = sqlLiteDB.delete(TABOA_AULAS,condicionwhere,parametros);
  48.  
  49.         return rexistrosafectados;
  50.  
  51.     }
  52.  
  53.     public int modificarAula(Aulas aula_modificar){
  54.         ContentValues datos = new ContentValues();
  55.         datos.put("nome", aula_modificar.getNome());
  56.  
  57.         String where = "_id=?";
  58.         String[] params = new String[]{String.valueOf(aula_modificar.get_id())};
  59.  
  60.         int rexistrosModificados = sqlLiteDB.update(TABOA_AULAS, datos, where, params);
  61.  
  62.         return rexistrosModificados;
  63.     }
  64.  
  65.     public ArrayList<Aulas> obterAulas() {
  66.         ArrayList<Aulas> aulas_devolver = new ArrayList<Aulas>();
  67.  
  68.         Cursor datosConsulta = sqlLiteDB.rawQuery(CONSULTAR_AULAS, null);
  69.         if (datosConsulta.moveToFirst()) {
  70.             Aulas aula;
  71.             while (!datosConsulta.isAfterLast()) {
  72.                 aula = new Aulas(datosConsulta.getLong(0),
  73.                         datosConsulta.getString(1));
  74.                 aulas_devolver.add(aula);
  75.                 datosConsulta.moveToNext();
  76.             }
  77.         }
  78.         return aulas_devolver;
  79.     }
  80.  
  81.     public UD06_03_BaseDatos(Context context) {
  82.         super(context, NOME_BD, null, VERSION_BD);
  83.         // TODO Auto-generated constructor stub
  84.     }
  85.  
  86.     public void abrirBD(){
  87.         if (sqlLiteDB==null || !sqlLiteDB.isOpen()){
  88.           sqlLiteDB = sInstance.getWritableDatabase();
  89.         }
  90.     }
  91.  
  92.     public void pecharBD(){
  93.         if (sqlLiteDB!=null && sqlLiteDB.isOpen()){
  94.           sqlLiteDB.close();
  95.         }
  96.     }
  97.  
  98.     @Override
  99.     public void onCreate(SQLiteDatabase db) {
  100.         // TODO Auto-generated method stub
  101.        // db.execSQL(CREAR_TABOA_AULAS);
  102.  
  103.     }
  104.  
  105.     @Override
  106.     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  107.         // TODO Auto-generated method stub
  108.  
  109.         //db.execSQL("DROP TABLE IF EXISTS AULAS");
  110.         //onCreate(db);
  111.     }
  112.  
  113. }




Creamos a activity

  • Dentro do paquete BasesDatos (creado previamente no paso anterior) crear unha nova 'Empty Activity' de nome: UD06_03_DatosPersistentes_BD de tipo Launcher e sen compatibilidade.
Modifica o arquivo AndroidManifiest.xml e engade unha label á activity como xa vimos na creación do proxecto base.


Código do layout xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.    xmlns:app="http://schemas.android.com/apk/res-auto"
  4.    xmlns:tools="http://schemas.android.com/tools"
  5.    android:layout_width="match_parent"
  6.    android:layout_height="match_parent"
  7.    tools:context=".Persistencia.BasesDatos.UD06_03_DatosPersistentes_BD">
  8.  
  9.     <ListView
  10.        android:id="@+id/lstAulas_UD06_03_BaseDatos"
  11.        android:layout_width="0dp"
  12.        android:layout_height="0dp"
  13.        android:layout_marginBottom="8dp"
  14.        android:layout_marginEnd="8dp"
  15.        android:layout_marginStart="8dp"
  16.        android:layout_marginTop="8dp"
  17.        app:layout_constraintBottom_toTopOf="@+id/guideline3"
  18.        app:layout_constraintEnd_toEndOf="parent"
  19.        app:layout_constraintStart_toStartOf="parent"
  20.        app:layout_constraintTop_toTopOf="parent" />
  21.  
  22.     <android.support.constraint.Guideline
  23.        android:id="@+id/guideline3"
  24.        android:layout_width="wrap_content"
  25.        android:layout_height="wrap_content"
  26.        android:orientation="horizontal"
  27.        app:layout_constraintGuide_percent=".45" />
  28.  
  29.     <EditText
  30.        android:id="@+id/etAula_UD06_03_BaseDatos"
  31.        android:layout_width="0dp"
  32.        android:layout_height="wrap_content"
  33.        android:layout_marginEnd="8dp"
  34.        android:layout_marginStart="8dp"
  35.        android:layout_marginTop="8dp"
  36.        android:ems="10"
  37.        android:hint="Introduce un aula"
  38.        android:inputType="textCapCharacters"
  39.        app:layout_constraintEnd_toEndOf="parent"
  40.        app:layout_constraintStart_toStartOf="parent"
  41.        app:layout_constraintTop_toTopOf="@+id/guideline3" />
  42.  
  43.     <Button
  44.        android:id="@+id/btnAlta_UD06_03_BaseDatos"
  45.        android:layout_width="100sp"
  46.        android:layout_height="wrap_content"
  47.        android:layout_marginBottom="8dp"
  48.        android:layout_marginStart="8dp"
  49.        android:text="Alta"
  50.        app:layout_constraintBottom_toBottomOf="parent"
  51.        app:layout_constraintStart_toStartOf="parent" />
  52.  
  53.     <Button
  54.        android:id="@+id/btnModificar_UD06_03_BaseDatos"
  55.        android:layout_width="110sp"
  56.        android:layout_height="wrap_content"
  57.        android:layout_marginBottom="8dp"
  58.        android:layout_marginEnd="8dp"
  59.        android:layout_marginStart="8dp"
  60.        android:text="Modificar"
  61.        app:layout_constraintBottom_toBottomOf="parent"
  62.        app:layout_constraintEnd_toStartOf="@+id/btnBaixa_UD06_03_BaseDatos"
  63.        app:layout_constraintStart_toEndOf="@+id/btnAlta_UD06_03_BaseDatos" />
  64.  
  65.     <Button
  66.        android:id="@+id/btnBaixa_UD06_03_BaseDatos"
  67.        android:layout_width="100dp"
  68.        android:layout_height="wrap_content"
  69.        android:layout_marginBottom="8dp"
  70.        android:layout_marginEnd="8dp"
  71.        android:text="Baixa"
  72.        app:layout_constraintBottom_toBottomOf="parent"
  73.        app:layout_constraintEnd_toEndOf="parent" />
  74. </android.support.constraint.ConstraintLayout>



Código da clase UD6_03_DatosPersistentes_BD
Obxectivo: Realizar operacións contra unha base de datos.

  1. package es.cursoandroid.cifprodolfoucha.aprendiendo.Persistencia.BasesDatos;
  2.  
  3. import android.app.Activity;
  4. import android.os.Bundle;
  5. import android.view.View;
  6. import android.widget.AdapterView;
  7. import android.widget.ArrayAdapter;
  8. import android.widget.Button;
  9. import android.widget.EditText;
  10. import android.widget.ListView;
  11. import android.widget.Toast;
  12.  
  13. import java.io.File;
  14. import java.io.FileOutputStream;
  15. import java.io.IOException;
  16. import java.io.InputStream;
  17. import java.io.OutputStream;
  18. import java.util.ArrayList;
  19.  
  20. import es.cursoandroid.cifprodolfoucha.aprendiendo.R;
  21.  
  22. public class UD06_03_DatosPersistentes_BD extends Activity {
  23.     private UD06_03_BaseDatos baseDatos;
  24.     private Aulas aula_seleccionada=null;   // Garda a aula seleccionada da lista
  25.  
  26.     private void copiarBD() {
  27.         String bddestino = "/data/data/" + getPackageName() + "/databases/"
  28.                 + UD06_03_BaseDatos.NOME_BD;
  29.         File file = new File(bddestino);
  30.         if (file.exists()) {
  31.             Toast.makeText(getApplicationContext(), "A BD NON SE VAI COPIAR. XA EXISTE", Toast.LENGTH_LONG).show();
  32.             return; // XA EXISTE A BASE DE DATOS
  33.         }
  34.  
  35.         String pathbd = "/data/data/" + getPackageName()
  36.                 + "/databases/";
  37.         File filepathdb = new File(pathbd);
  38.         filepathdb.mkdirs();
  39.  
  40.         InputStream inputstream;
  41.         try {
  42.             inputstream = getAssets().open(UD06_03_BaseDatos.NOME_BD);
  43.             OutputStream outputstream = new FileOutputStream(bddestino);
  44.  
  45.             int tamread;
  46.             byte[] buffer = new byte[2048];
  47.  
  48.             while ((tamread = inputstream.read(buffer)) > 0) {
  49.                 outputstream.write(buffer, 0, tamread);
  50.             }
  51.  
  52.             inputstream.close();
  53.             outputstream.flush();
  54.             outputstream.close();
  55.             Toast.makeText(getApplicationContext(), "BASE DE DATOS COPIADA", Toast.LENGTH_LONG).show();
  56.         } catch (IOException e) {
  57.             // TODO Auto-generated catch block
  58.             e.printStackTrace();
  59.         }
  60.  
  61.     }
  62.  
  63.     private void xestionarEventos(){
  64.  
  65.         Button btnAltaAula = (Button)findViewById(R.id.btnAlta_UD06_03_BaseDatos);
  66.         btnAltaAula.setOnClickListener(new View.OnClickListener() {
  67.  
  68.             @Override
  69.             public void onClick(View v) {
  70.                 // TODO Auto-generated method stub
  71.                 EditText editAula = (EditText) findViewById(R.id.etAula_UD06_03_BaseDatos);
  72.                 // Habería que comprobar se hai algún dato na caixa de texto, pero neste exemplo centrámonos no funcionamento da base de datos.
  73.  
  74.                 Aulas aula = new Aulas(0, editAula.getText().toString());
  75.                 long id = baseDatos.engadirAula(aula); // Obtemos o id. Neste exemplo non faremos nada con el.
  76.                 aula.set_id(id);
  77.  
  78.                 // Poderíamos engadir a nova aula ó adaptador pero neste exemplo
  79.                 // recargamos a lista
  80.                 cargarLista();
  81.                 editAula.setText("");
  82.  
  83.                 Toast.makeText(getApplicationContext(), "AULA ENGADIDA",Toast.LENGTH_LONG).show();
  84.             }
  85.         });
  86.  
  87.         Button btnBaixaAula = (Button)findViewById(R.id.btnBaixa_UD06_03_BaseDatos);
  88.         btnBaixaAula.setOnClickListener(new View.OnClickListener() {
  89.  
  90.             @Override
  91.             public void onClick(View v) {
  92.                 // TODO Auto-generated method stub
  93.  
  94.                 baseDatos.borrarAula(aula_seleccionada);
  95.                 EditText editAula = (EditText)findViewById(R.id.etAula_UD06_03_BaseDatos);
  96.                 editAula.setText("");
  97.  
  98.                 // Poderíamos borrar a aula do adaptador e non cargar a lista novamente
  99.                 cargarLista();
  100.                 Toast.makeText(getApplicationContext(), "AULA BORRADA",Toast.LENGTH_LONG).show();
  101.             }
  102.         });
  103.  
  104.         Button btnModificaraAula = (Button)findViewById(R.id.btnModificar_UD06_03_BaseDatos);
  105.         btnModificaraAula.setOnClickListener(new View.OnClickListener() {
  106.  
  107.             @Override
  108.             public void onClick(View v) {
  109.                 // TODO Auto-generated method stub
  110.  
  111.                 EditText editAula = (EditText)findViewById(R.id.etAula_UD06_03_BaseDatos);
  112.  
  113.                 Aulas aula_modificar = new Aulas(aula_seleccionada.get_id(),editAula.getText().toString());
  114.                 baseDatos.modificarAula(aula_modificar);
  115.  
  116.                 // Poderíamos borrar a aula do adaptador e non cargar a lista novamente
  117.                 cargarLista();
  118.                 Toast.makeText(getApplicationContext(), "AULA MODIFICADA",Toast.LENGTH_LONG).show();
  119.             }
  120.         });
  121.  
  122.  
  123.  
  124.         ListView lista = (ListView)findViewById(R.id.lstAulas_UD06_03_BaseDatos);
  125.         lista.setOnItemClickListener(new AdapterView.OnItemClickListener() {
  126.  
  127.             @Override
  128.             public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
  129.                                     long arg3) {
  130.                 // TODO Auto-generated method stub
  131.                 // Obtemos o obxecto aula seleccionado
  132.                 ArrayAdapter<Aulas> adaptador = (ArrayAdapter<Aulas>) arg0.getAdapter();
  133.                 aula_seleccionada = adaptador.getItem(arg2);
  134.                 EditText editAula = (EditText)findViewById(R.id.etAula_UD06_03_BaseDatos);
  135.                 editAula.setText(aula_seleccionada.getNome());
  136.  
  137.             }
  138.         });
  139.     }
  140.  
  141.     /**
  142.      * Accede á base de datos para obter as aulas e as asina á lista.
  143.      */
  144.     private void cargarLista(){
  145.  
  146.         ListView lista = (ListView)findViewById(R.id.lstAulas_UD06_03_BaseDatos);
  147.  
  148.         ArrayList<Aulas> aulas = baseDatos.obterAulas();
  149.         ArrayAdapter<Aulas> adaptador = new ArrayAdapter<Aulas>(getApplicationContext(),
  150.                 android.R.layout.simple_list_item_1,aulas);
  151.         lista.setAdapter(adaptador);
  152.  
  153.     }
  154.  
  155.  
  156.     @Override
  157.     public void onStart(){
  158.         super.onStart();
  159.  
  160.         if (baseDatos==null) {   // Abrimos a base de datos para escritura
  161.             baseDatos = UD06_03_BaseDatos.getInstance(getApplicationContext());
  162.             baseDatos.abrirBD();
  163.  
  164.             cargarLista();
  165.         }
  166.     }
  167.  
  168.     @Override
  169.     public void onStop(){
  170.         super.onStop();
  171.  
  172.         if (baseDatos!=null){    // Pechamos a base de datos.
  173.             baseDatos.pecharBD();
  174.             baseDatos=null;
  175.         }
  176.  
  177.     }
  178.  
  179.     @Override
  180.     protected void onCreate(Bundle savedInstanceState) {
  181.         super.onCreate(savedInstanceState);
  182.         setContentView(R.layout.activity_ud06_03__datos_persistentes__bd);
  183.  
  184.         copiarBD();
  185.         xestionarEventos();
  186.     }
  187. }
  • Liñas 66-85: Xestionamos o evento Click sobre o botón de alta.
  • Liña 74: Creamos un obxecto da clase Aula co contido do EditText. O id non vai utilizarse xa que cando damos de alta o id non se utiliza.
  • Liña 75: Chamamos á base de datos có aula a dar de alta. Devolve o id (autonumérico).
  • Liña 80: Chamamos a o método que volve chamar á base de datos para cargar as aulas novamente.
  • Liñas 87-102: Xestionamos o evento Click sobre o botón de baixa.
  • Liña 94: Chamamos á base de datos para dar de baixa a aula seleccionada. Máis adiante está explicado que cando prememos sobre a lista gardamos en aula_seleccionada a aula seleccionada da lista.
  • Liña 99: Chamamos a o método que volve chamar á base de datos para cargar as aulas novamente.
  • Liñas 104-120: Xestionamos o evento Click sobre o botón de modificar.
  • Liña 113: Creamos o obxecto aula que vai a enviarse á base de datos. O id é o id da aula seleccionada na lista e o texto é o do EditText.
  • Liña 114: Chamamos á base de datos para modificar o nome da aula seleccionada.
  • Liña 117: Chamamos a o método que volve chamar á base de datos para cargar as aulas novamente.
  • Liñas 125-138: Xestionamos o evento Click sobre a lista.
  • Liña 133: Gardamos na propiedade 'aula_seleccionada' a aula seleccionada.
  • Liña 135: Cambiamos o texto do EditText polo seleccionado.
  • Liñas 144-153: Cargamos as aulas dende a base de datos á lista.
  • Liñas 161-164: Cando a aplicación empeza abrimos a base de datos e cargamos a lista coas aulas.
  • Liñas 173-174: Liberamos os recursos.





Aclaración sobre el patrón Singlenton

  • No exemplo anterior empregamos o patrón Singleton como forma para obter unha única referencia ao obxecto da clase que se vai encargar de conectar á base de datos e facer as operacións.
  1. public class MiClase {
  2.     private static MiClase sInstance;
  3.  
  4.  
  5.  
  6.     public static synchronized MiClase getInstance() {
  7.         if (sInstance == null) {
  8.             sInstance = new MiClase ();
  9.         }
  10.         return sInstance;
  11.     }
  12.  
  13. }


  • Se empregamos esta forma temos que ter coidado xa que non imos poder 'abrir' á base de datos no método onStart() das activities.
Isto é debido a un problema que teremos no seguinte exemplo:
  • Imaxinemos que temos dúas activities que fan uso da base de datos e as dúas teñen o código que abre a base de datos no método onStart().
  • A primeira Activity se carga e pasa por dito método obtendo unha referencia á clase que xestiona a Base de Datos.
  • Agora esta primeira Activity chama á segunda Activity. Ao facelo, primeiro pasa polo método onStart da segunda Activity e depois polo método onStop da primeira Activity.
  • Polo tanto a segunda Activity vai ter unha referencia ao obxecto coa base de datos pechada xa que a pechou a primeira Activity ao pasar polo método onStop.


  • Para resolvelo poderíamos:
  • Facer sempre a comprobación de que a base de datos estea aberta antes de facer unha operación e abrila se non o está.
  • Abrir a base de datos ao iniciar a activity principal (no método onCreate) e pechala ao saír da aplicación (no método onDestroy).
  • Non empregar dito patrón e crear unha instancia da clase en cada Activity que necesite acceder á base de datos, abrindo a base de datos no método onStart() e pechando no método onStop()





-- Ángel D. Fernández González e Carlos Carrión Álvarez -- (2014).