Diferencia entre revisiones de «PDM Avanzado Datos Bases de datos»

De MediaWiki
Ir a la navegación Ir a la búsqueda
 
(No se muestran 38 ediciones intermedias de 2 usuarios)
Línea 114: Línea 114:
 
Dito método ('''onCreate''') trae como parámetro un obxecto da [http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html clase '''SqLiteDatabase'''] que posúe o método '''execSQL''' para lanzar as ordes SQL necesarias.
 
Dito método ('''onCreate''') trae como parámetro un obxecto da [http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html 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
+
 
 +
*'''<u>Nota:</u>'''
 +
:* Máis información sobre a orde '''Create Table''': https://www.sqlite.org/lang_createtable.html
 +
:* Máis información sobre os tipos de datos en sqlite: https://www.sqlite.org/datatype3.html
 +
 
  
 
Se necesitamos modificar as táboas ou crear novas deberemos sobrescribir o método '''onUpgrade'''.
 
Se necesitamos modificar as táboas ou crear novas deberemos sobrescribir o método '''onUpgrade'''.
Línea 143: Línea 147:
  
  
* Neste exemplo imos crear unha clase de nome '''UD06_01_BaseDatos''' que derive da clase SQLiteOpenHelper como explicamos antes. Esta clase <u>estará creada dentro do paquete BasesDatos</u>
+
* Neste exemplo imos crear unha clase de nome '''UD05_01_BaseDatos''' que derive da clase SQLiteOpenHelper como explicamos antes. Esta clase <u>estará creada dentro do paquete BasesDatos</u>
  
  
  
* O nome da base de datos será '''UD06_01_BD_EXEMPLO'''
+
* O nome da base de datos será '''UD05_01_BD_EXEMPLO'''
 
* Faremos que no método '''onCreate''' se cre a seguinte táboa:
 
* Faremos que no método '''onCreate''' se cre a seguinte táboa:
  
Línea 155: Línea 159:
  
  
Creamos a clase UD06_01_BaseDatos:
+
Creamos a clase UD05_01_BaseDatos:
  
'''Código da clase UD06_01_BaseDatos'''<br/>
+
'''Código da clase UD05_01_BaseDatos'''<br/>
 
'''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).
 
'''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).
 
<syntaxhighlight lang="java" line enclose="div" highlight="" >
 
<syntaxhighlight lang="java" line enclose="div" highlight="" >
Línea 166: Línea 170:
 
import android.database.sqlite.SQLiteOpenHelper;
 
import android.database.sqlite.SQLiteOpenHelper;
  
public class UD06_01_BaseDatos extends SQLiteOpenHelper{
+
public class UD05_01_BaseDatos extends SQLiteOpenHelper{
     public final static String NOME_BD="UD06_01_BD_EXEMPLO";
+
     public final static String NOME_BD="UD05_01_BD_EXEMPLO";
 
     public final static int VERSION_BD=1;
 
     public final static int VERSION_BD=1;
  
Línea 175: Línea 179:
  
  
     public UD06_01_BaseDatos(Context context) {
+
     public UD05_01_BaseDatos(Context context) {
 
         super(context, NOME_BD, null, VERSION_BD);
 
         super(context, NOME_BD, null, VERSION_BD);
 
         // TODO Auto-generated constructor stub
 
         // TODO Auto-generated constructor stub
Línea 208: Línea 212:
 
=====Creamos a Activity Principal=====
 
=====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.
+
* Dentro do paquete '''BasesDatos''' (creado previamente no paso anterior) crear unha nova 'Empty Activity' de nome: '''UD05_01_DatosPersistentes_BD''' de tipo Launcher e sen compatibilidade.
 
: Modifica o arquivo '''AndroidManifiest.xml''' e engade unha label á activity como [http://wiki.cifprodolfoucha.es/index.php?title=PDM_Creando_proxecto_base xa vimos na creación do proxecto base].
 
: Modifica o arquivo '''AndroidManifiest.xml''' e engade unha label á activity como [http://wiki.cifprodolfoucha.es/index.php?title=PDM_Creando_proxecto_base xa vimos na creación do proxecto base].
  
Línea 222: Línea 226:
 
     android:layout_width="match_parent"
 
     android:layout_width="match_parent"
 
     android:layout_height="match_parent"
 
     android:layout_height="match_parent"
     tools:context=".Persistencia.BasesDatos.UD06_01_A1_DatosPersistentes_BD">
+
     tools:context=".Persistencia.BasesDatos.UD05_01_A1_DatosPersistentes_BD">
  
 
     <Button
 
     <Button
         android:id="@+id/btnCrearBD_UD06_01_A1_BaseDatos"
+
         android:id="@+id/btnCrearBD_UD05_01_A1_BaseDatos"
 
         android:layout_width="wrap_content"
 
         android:layout_width="wrap_content"
 
         android:layout_height="wrap_content"
 
         android:layout_height="wrap_content"
Línea 237: Línea 241:
 
         app:layout_constraintStart_toStartOf="parent"
 
         app:layout_constraintStart_toStartOf="parent"
 
         app:layout_constraintTop_toTopOf="parent" />
 
         app:layout_constraintTop_toTopOf="parent" />
 +
    <Button
 +
        android:id="@+id/btnPecharBD_UD05_01_A1_BaseDatos"
 +
        android:layout_width="wrap_content"
 +
        android:layout_height="wrap_content"
 +
        android:layout_marginStart="8dp"
 +
        android:layout_marginTop="8dp"
 +
        android:layout_marginEnd="8dp"
 +
        android:layout_marginBottom="8dp"
 +
        android:text="PECHAR BASE DE DATOS"
 +
        app:layout_constraintBottom_toBottomOf="parent"
 +
        app:layout_constraintEnd_toEndOf="parent"
 +
        app:layout_constraintHorizontal_bias="0.502"
 +
        app:layout_constraintStart_toStartOf="parent"
 +
        app:layout_constraintTop_toTopOf="parent"
 +
        app:layout_constraintVertical_bias="0.347" />
 +
 
</android.support.constraint.ConstraintLayout>
 
</android.support.constraint.ConstraintLayout>
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
  
'''Código da clase UD06_01_DatosPersistentes_BD'''<br/>
+
'''Código da clase UD05_01_DatosPersistentes_BD'''<br/>
 
'''Obxectivo:''' Crear unha base de datos.
 
'''Obxectivo:''' Crear unha base de datos.
<syntaxhighlight lang="java" line enclose="div" highlight="11,19,20" >
+
<syntaxhighlight lang="java" line enclose="div" highlight="11,19,20,32-33" >
 
package es.cursoandroid.cifprodolfoucha.aprendiendo.Persistencia.BasesDatos;
 
package es.cursoandroid.cifprodolfoucha.aprendiendo.Persistencia.BasesDatos;
  
Línea 253: Línea 273:
 
import es.cursoandroid.cifprodolfoucha.aprendiendo.R;
 
import es.cursoandroid.cifprodolfoucha.aprendiendo.R;
  
public class UD06_01_DatosPersistentes_BD extends Activity {
+
public class UD05_01_DatosPersistentes_BD extends Activity {
     private UD06_01_BaseDatos baseDatos;
+
     private UD05_01_BaseDatos baseDatos;
 
      
 
      
 
     private void xestionarEventos(){
 
     private void xestionarEventos(){
  
         findViewById(R.id.btnCrearBD_UD06_01_A1_BaseDatos).setOnClickListener(new View.OnClickListener() {
+
         findViewById(R.id.btnCrearBD_UD05_01_A1_BaseDatos).setOnClickListener(new View.OnClickListener() {
 
             @Override
 
             @Override
 
             public void onClick(View v) {
 
             public void onClick(View v) {
 +
                if (baseDatos==null){
 +
                    baseDatos = new UD05_01_BaseDatos(getApplicationContext());
 +
                    baseDatos.getWritableDatabase();
  
                baseDatos = new UD06_01_BaseDatos(getApplicationContext());
+
                    Toast.makeText(getApplicationContext(), "BD ABERTA PARA LECTURA/ESCRITURA", Toast.LENGTH_LONG).show();
                baseDatos.getWritableDatabase();
+
                }
 
 
                Toast.makeText(getApplicationContext(), "BASE DE DATOS CREADA", Toast.LENGTH_LONG).show();
 
  
 
             }
 
             }
 
         });
 
         });
 +
        findViewById(R.id.btnPecharBD_UD05_01_A1_BaseDatos)
 +
                .setOnClickListener(new View.OnClickListener() {
 +
                    @Override
 +
                    public void onClick(View v) {
 +
                        if (baseDatos!=null){
 +
                            baseDatos.close();
 +
                            baseDatos = null;
 +
                            Toast.makeText(getApplicationContext(),"BD PECHADA",Toast.LENGTH_SHORT).show();
 +
                        }
 +
                    }
 +
                });
 +
  
 
     }
 
     }
Línea 275: Línea 308:
 
     protected void onCreate(Bundle savedInstanceState) {
 
     protected void onCreate(Bundle savedInstanceState) {
 
         super.onCreate(savedInstanceState);
 
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_ud06_01__datos_persistentes__bd);
+
         setContentView(R.layout.activity_UD05_01__datos_persistentes__bd);
  
 
         xestionarEventos();
 
         xestionarEventos();
Línea 284: Línea 317:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
* Liña 11: Creamos unha propiedade que vai servir para poder realizar operacións contra a base de datos.
+
:* 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 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.
+
:* 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.
 +
:* Liña 32: Pechamos a base de datos ao premer no botón.
  
===Ferramenta externa: SqliteStudio===
 
  
 +
* '''NOTA IMPORTANTE:''' Fixarse que aínda que premamos moitas veces no botón de crear base de datos, o método onCreate soamente se chama no caso de que non exista a base de datos. Se xa existe non se chama.
  
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.
+
* Podedes probar a poñer un número de versión diferente á base de datos e comprobar cun Log como pasa polo método onUpgrade.
  
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.
+
<br />
 +
* Agora temos que resolver o 'problema' de onde se van a realizar as operacións contra a base de datos...
 +
: Se o fixéramos dende a Activity (é dicir, na activity irían as ordes SQL (INSERT, UPDATE, DELETE, SELECT) ) soamente teríamos que gardar a referencia ao obxecto SQLiteDatabase que devolve o método getWritableDatabase() ou o método getReadableDatabase() da seguinte forma:
  
 +
<syntaxhighlight lang="java" line enclose="div" highlight="4,13" >
 +
public class UD05_01_BD_EXEMPLO extends AppCompatActivity {
  
Para crear a base de datos temos moitas ferramentas gratuítas para facelo.
+
    private UD05_01_BaseDatos baseDatos;
 +
    private SQLiteDatabase operacionsBD;
  
Por exemplo: http://sqlitestudio.pl/
+
    private void xestionarEventos(){
 +
        findViewById(R.id.btnCrearBD_UD05_01_A1_BaseDatos)
 +
                .setOnClickListener(new View.OnClickListener() {
 +
                    @Override
 +
                    public void onClick(View v) {
 +
                        if (baseDatos==null){
 +
                            baseDatos = new UD05_01_BaseDatos(getApplicationContext());
 +
                            operacionsBD = baseDatos.getReadableDatabase();
 +
                            Toast.makeText(getApplicationContext(),"BD ABERTA PARA LECTURA/ESCRITURA",Toast.LENGTH_SHORT).show();
 +
                        }
 +
                    }
 +
                });
 +
    ...........
 +
</syntaxhighlight>
  
 +
: A partires de entón, cando se queira facer unha operación contra a base de datos teríamos que facer:
 +
<syntaxhighlight lang="java" line enclose="div" highlight="" >
 +
...
 +
operacionsBD.rawQuery("select _id,nome from AULAS", null);
 +
...
 +
</syntaxhighlight>
 +
 +
: Veremos máis adainte o que fai dita orden. O que tedes que ter claro é que calquera operación SQL se ten que facer empregando o obxeto SQLiteDatabase que devolve o método getReadableDatabase() ou getWritableDatabase().
  
<u>Nota:</u> Se estamos a utilizar Linux deberemos darlle permiso de execución ao arquivo baixado coa orde:
 
<syntaxhighlight lang="java" enclose="div" highlight="" >
 
chmod 755 sqlitestudio-2.1.4.bin
 
</syntaxhighlight>
 
  
e despois executala:
+
* No noso caso, imos facer todas as operación dentro da clase UD05_01_BaseDatos (a que derive de SQLiteOpenHelper) e polo tanto necesitamos gardar dentro de dita clase unha referencia ao obxecto operacionsBD.
<syntaxhighlight lang="java" enclose="div" highlight="" >
+
: A forma 'óptima' de facelo é empregando o [https://es.wikipedia.org/wiki/Singleton patrón Singleton] que veremos máis adiante, neste exemplo imos ver unha solución sinxela...
./sqlitestudio-2.1.4.bin
+
: O único que temos que facer é modificar a '''clase UD05_01_BaseDatos''':
</syntaxhighlight>
+
<syntaxhighlight lang="java" line enclose="div" highlight="6,16-18" >
 +
public class UD05_01_BaseDatos extends SQLiteOpenHelper {
  
 +
    public final static String NOME_BD = "basedatos.db";
 +
    public final static int VERSION_BD=2;
  
* Unha vez no programa podemos crear unha base de datos nova indo o menú '''Base de datos => Engadir Base de datos'''.
+
    private SQLiteDatabase operacionsBD;
  
[[Imagen:PDM_Avanzada_DatosPersistentes_24.jpg|400px]]
+
    private String CREAR_TABOA_AULAS ="CREATE TABLE AULAS ( " +
 +
            "_id  INTEGER PRIMARY KEY AUTOINCREMENT," +
 +
            "nome VARCHAR( 50 )  NOT NULL)";
  
:Aquí temos que indicar o sitio físico onde se vai gardar / abrir a base de datos e o nome da mesma.
+
    public UD05_01_BaseDatos(Context context) {
:A versión poñeremos sqlite3.
+
        super(context, NOME_BD, null, VERSION_BD);
:Debemos premer o botón de 'Ok'.
+
    }
  
 +
    public void asigarSQLiteDatabase(SQLiteDatabase operacionsBD){
 +
        this.operacionsBD = operacionsBD;
 +
    }
  
* Agora debemos de escoller a opción de '''Tablas''' e unha vez escollida premer a opción '''Nueva Tabla''' da parte superior.
+
    .....
  
[[Imagen:PDM_Avanzada_DatosPersistentes_25.jpg|400px]]
+
</syntaxhighlight>
  
 +
* E agora dende a AppCompactActivity '''UD05_01_BD_EXEMPLO''' gardamos a referencia:
 +
<syntaxhighlight lang="java" line enclose="div" highlight="9,10" >
 +
    ...........
 +
    private void xestionarEventos(){
 +
        findViewById(R.id.btnCrearBD_UD05_01_A1_BaseDatos)
 +
                .setOnClickListener(new View.OnClickListener() {
 +
                    @Override
 +
                    public void onClick(View v) {
 +
                        if (baseDatos==null){
 +
                            baseDatos = new UD05_01_BaseDatos(getApplicationContext());
 +
                            SQLiteDatabase operacionsBD = baseDatos.getWritableDatabase();
 +
                            baseDatos.asigarSQLiteDatabase(operacionsBD);
 +
                            Toast.makeText(getApplicationContext(),"BD ABERTA PARA LECTURA/ESCRITURA",Toast.LENGTH_SHORT).show();
 +
                        }
 +
                    }
 +
                });
  
* Esta pantalla é bastante intuitiva.  
+
    .............
:*Damos un nome á táboa (no exemplo AULAS) e prememos o botón 'Añadir Columna'.
+
</syntaxhighlight>
[[Imagen:PDM_Avanzada_DatosPersistentes_26.jpg|400px]]
 
  
 +
* Agora imos poder facer todas as operacións dentro da clase UD05_01_BaseDatos facendo uso da referencia ao obxecto operacionsBD.
  
* Agora só temos que engadir as columnas co tipo de dato adecuado:
 
  
[[Imagen:PDM_Avanzada_DatosPersistentes_27.jpg|400px]]
+
* <u>'''IMPORTANTE:'''</u> Loxicamente nunha aplicación 'normal' abriremos, nas activities necearias, a base de datos no método onCreate e a pecharemos no método onDestroy.
  
: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:
 
  
[[Imagen:PDM_Avanzada_bd_5.jpg|400px]]
 
  
  
* Unha vez creada a táboa xa podemos engadir filas ou modificar os campos premendo sobre a pestana de datos:
+
<br />
  
[[Imagen:PDM_Avanzada_DatosPersistentes_29.jpg|400px]]
+
===Ferramenta interna: Database Inspector===
  
 +
* A partires do '''Android Studio versión 4.1.1''' é posible acceder e visualizar os datos dunha base de datos asociada a unha aplicación mentres se está a executar.
 +
: Para iso <u>é necesario que a versión do emulador sexa maior ou igual á API 26</u>.
  
  
* 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''':
+
<gallery caption="Accedendo ao Database Inspector" widths="350" heights="350px" perrow="2">
 +
Image:PDM_Avanzada_bd_databaseinspector_1.JPG| Executamos a aplicación que abre a base de datos e vai facer operacións sobre ela.
 +
Image:PDM_Avanzada_bd_databaseinspector_2.JPG| Abrimos a ferramenta Database Inspector.
 +
Image:PDM_Avanzada_bd_databaseinspector_3.JPG| Na parte baixa aparecerá outra lapela có nome 'Database inspector'. Debemos asegurarnos que estean seleccionados o emulador e o proceso da aplicación que está correndo no emulador. Ao premer dúas veces sobre unha táboa, aparecerá en forma de grid os datos da mesma.
 +
Image:PDM_Avanzada_bd_databaseinspector_4.JPG| Se prememos dúas veces sobre un dato, podemos editalo e modificar o seu valor.
 +
Image:PDM_Avanzada_bd_databaseinspector_5.JPG| Por defecto, os datos non está sincronizados coa aplicación, polo que se modificamos os datos a través da aplicación será necesario refrescar os datos da táboa premendo no botón '''Refresh'''.
 +
Image:PDM_Avanzada_bd_databaseinspector_6.JPG| Se queremos que estean sincronizados e calquera modificación dos datos a través do emulador se reflictan automaticamente no Database Inspector, debemos marcar a opción '''Live Update'''. Ao facelo xa non podemos modificar os datos dende o Android Studio.
 +
Image:PDM_Avanzada_bd_databaseinspector_7.JPG| Podemos crear as nosas propias consultas premendo na opción '''Open New Query Tap'''. Abrirase unha nova lapela onde podemos escribir <u>calquera sentenza SQL (INSERT, UPDATE, DELETE, SELECT)</u>. Ao escribila debemos premer no botón '''RUN''', aparecendo os resultados na parte inferior.
 +
Image:PDM_Avanzada_bd_databaseinspector_8.JPG| Se marcamos a opción '''Keep Database Connection Open''' poderemos acceder á base de datos aínda que se peche a conexión dende a aplicación do emulador.
 +
</gallery>
  
[[Imagen:PDM_Avanzada_DatosPersistentes_30.jpg|400px]]
 
  
  
====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.
+
<br />
  
Para que esta poida utilizala teremos que copiala ao cartafol '''/data/data/paquete_aplicación/databases/
+
===Ferramenta externa: SqliteStudio===
  
A forma de copiala xa a vimos cando vimos o [http://wiki.cifprodolfoucha.es/index.php?title=PDM_Avanzado_Datos_Persistentes_Arquivos 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 [http://wiki.cifprodolfoucha.es/index.php?title=Programaci%C3%B3n_de_dispositivos_m%C3%B3biles#UNIDADE_7:_Threads_e_AsyncTask unidade 7].
+
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.
  
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.
+
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/
  
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:
+
<u>Nota:</u> Se estamos a utilizar Linux deberemos darlle permiso de execución ao arquivo baixado coa orde:
 
<syntaxhighlight lang="java" enclose="div" highlight="" >
 
<syntaxhighlight lang="java" enclose="div" highlight="" >
getDatabasePath(NOME_BD)
+
chmod 755 sqlitestudio-2.1.4.bin
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
ou ben ter unha referencia ao seu path e partires del obter un obxecto da clase File:
+
e despois executala:
 
<syntaxhighlight lang="java" enclose="div" highlight="" >
 
<syntaxhighlight lang="java" enclose="div" highlight="" >
String pathbd = "/data/data/" + getApplicationContext().getPackageName()+"/databases/";
+
./sqlitestudio-2.1.4.bin
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
  
 +
* Unha vez no programa podemos crear unha base de datos nova indo o menú '''Base de datos => Engadir Base de datos'''.
  
Como comentamos antes o normal é ter a base de datos creada no cartafol /assets/ e copiala o cartafol /databases/.
+
[[Imagen:PDM_Avanzada_DatosPersistentes_24.jpg|400px]]
 +
 
 +
: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'.
  
* Se o facemos así será necesario crear previamente dito cartafol /databases/:
 
:<syntaxhighlight lang="java" line enclose="div" highlight="" >
 
String pathbd = "/data/data/" + contexto.getPackageName()+"/databases/";
 
File ruta = new File(pathbd);
 
ruta.mkdirs();
 
</syntaxhighlight>
 
  
 +
* Agora debemos de escoller a opción de '''Tablas''' e unha vez escollida premer a opción '''Nueva Tabla''' da parte superior.
  
* Unha vez creado o cartafol xa podemos copiar a base de datos. Neste exemplo non se usa un fío de execución.
+
[[Imagen:PDM_Avanzada_DatosPersistentes_25.jpg|400px]]
: 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.
 
:<syntaxhighlight lang="java" line enclose="div" highlight="" >
 
String bddestino = "/data/data/" + getPackageName() + "/databases/"
 
+ UD5_05_BASEDATOS.NOME_BD;
 
File file = new File(bddestino);
 
if (file.exists())
 
return; // XA EXISTE A BASE DE DATOS
 
</syntaxhighlight>
 
  
Copiamos a base de datos.
 
:<syntaxhighlight lang="java" line enclose="div" highlight="" >
 
String pathbd = "/data/data/" + getPackageName()
 
+ "/databases/";
 
File filepathdb = new File(pathbd);
 
filepathdb.mkdirs();
 
  
InputStream inputstream;
+
* Esta pantalla é bastante intuitiva.
try {
+
:*Damos un nome á táboa (no exemplo AULAS) e prememos o botón 'Añadir Columna'.
inputstream = getAssets().open(UD5_05_BASEDATOS.NOME_BD);
+
[[Imagen:PDM_Avanzada_DatosPersistentes_26.jpg|400px]]
OutputStream outputstream = new FileOutputStream(bddestino);
 
  
int tamread;
 
byte[] buffer = new byte[2048];
 
  
while ((tamread = inputstream.read(buffer)) > 0) {
+
* Agora só temos que engadir as columnas co tipo de dato adecuado:
outputstream.write(buffer, 0, tamread);
 
}
 
  
inputstream.close();
+
[[Imagen:PDM_Avanzada_DatosPersistentes_27.jpg|400px]]
outputstream.flush();
 
outputstream.close();
 
} catch (IOException e) {
 
// TODO Auto-generated catch block
 
e.printStackTrace();
 
}
 
</syntaxhighlight>
 
  
 +
: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:
  
 +
[[Imagen:PDM_Avanzada_bd_5.jpg|400px]]
  
=====Caso práctico=====
 
  
Imos copiar unha base de datos feita coa ferramenta anterior dende o cartafol /assets/ ao cartafol /databases/.
+
* Unha vez creada a táboa xa podemos engadir filas ou modificar os campos premendo sobre a pestana de datos:
  
<gallery caption="Copiando unha base de datos existente" widths="350" heights="350px" perrow="2">
+
[[Imagen:PDM_Avanzada_DatosPersistentes_29.jpg|400px]]
Image:PDM_Avanzada_bd_7.jpg| Temos a base de datos a copia no cartafol /assets/
 
Image:PDM_Avanzada_bd_6.jpg| Podemos comprobar como a base de datos deste exercicio (a número 02) non existe.
 
Image:PDM_Avanzada_bd_8.jpg| Executamos a aplicación e como a base de datos non está copiada, a copia dende /assets/ ao cartafol 'databases/'.
 
Image:PDM_Avanzada_bd_9.jpg| Agora podemos comprobar como a base de datos xa está copiada.
 
Image:PDM_Avanzada_bd_10.jpg| Podemos premer o botón de abrir. Se a base de datos non existira, crearía unha baleira segundo o establecido no método onCreate.
 
Image:PDM_Avanzada_bd_11.jpg| Se volvemos a executar a aplicación ou rotamos a pantalla, como a base de datos xa foi copiada, non a volver a copiar.
 
</gallery>
 
  
  
<br />
 
======Preparación======
 
  
Hai que descomprimir e copiar o seguinte arquivo ao cartafol /assets/ do voso proxecto.
+
* 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''':
  
[[Media:UD06_02_BD_FILE.zip]]
+
[[Imagen:PDM_Avanzada_DatosPersistentes_30.jpg|400px]]
  
O cartafol /assets/ xa o deberíamos ter creado [http://wiki.cifprodolfoucha.es/index.php?title=PDM_Avanzado_Datos_Persistentes_Arquivos#Preparaci.C3.B3n dunha práctica anterior].
 
  
 +
====Copiando a base de datos dende /assets/ a .../databases/====
  
<u>Nota:</u> O alumno pode utilizar a súa propia base de datos xerada coa ferramenta anterior.
+
Como comentamos antes, a base de datos podemos tela xa creada e con datos preparada para ser utilizada pola nosa aplicación.
  
======Creamos a Activity======
+
Para que esta poida utilizala teremos que copiala ao cartafol '''/data/data/paquete_aplicación/databases/
  
* 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.
+
A forma de copiala xa a vimos cando vimos o [http://wiki.cifprodolfoucha.es/index.php?title=PDM_Avanzado_Datos_Persistentes_Arquivos apartado de xestión de arquivos].
: Modifica o arquivo '''AndroidManifiest.xml''' e engade unha label á activity como [http://wiki.cifprodolfoucha.es/index.php?title=PDM_Creando_proxecto_base xa vimos na creación do proxecto base].
 
  
+
Normalmente a copia debería facerse nun fío separado do fío principal. O uso dos fíos xa verémolo na [http://wiki.cifprodolfoucha.es/index.php?title=Programaci%C3%B3n_de_dispositivos_m%C3%B3biles#UNIDADE_7:_Threads_e_AsyncTask unidade 7].
<br />
 
'''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 [https://es.wikipedia.org/wiki/Singleton Singleton].
+
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.
: Isto se implementa da forma seguinte:
 
<syntaxhighlight lang="xml" line enclose="div" highlight="" >
 
public class MiClase {
 
    private static MiClase sInstance;
 
  
 +
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:
 +
<syntaxhighlight lang="java" enclose="div" highlight="" >
 +
getDatabasePath(NOME_BD)
 +
</syntaxhighlight>
  
    public static synchronized MiClase getInstance() {
+
ou ben ter unha referencia ao seu path e partires del obter un obxecto da clase File:
        if (sInstance == null) {
+
<syntaxhighlight lang="java" enclose="div" highlight="" >
            sInstance = new MiClase ();
+
String pathbd = "/data/data/" + getApplicationContext().getPackageName()+"/databases/";
        }
+
</syntaxhighlight>
        return sInstance;
 
    }
 
  
}
 
</syntaxhighlight>
 
  
  
: Agora se quero facer uso desta clase non fago: MiClase miClase = new MiClase();
+
Como comentamos antes o normal é ter a base de datos creada no cartafol /assets/ e copiala o cartafol /databases/.
: Terei que poñer: '''MiClase miClase = MiClase.getInstance();'''
 
  
: Se o fago á vez dende diferentes clase sempre estarei empregando a mesma instancia.  
+
* Se o facemos así será necesario crear previamente dito cartafol /databases/:
: Da outra forma teríamos un obxecto diferente.
+
:<syntaxhighlight lang="java" line enclose="div" highlight="" >
 +
String pathbd = "/data/data/" + contexto.getPackageName()+"/databases/";
 +
File ruta = new File(pathbd);
 +
ruta.mkdirs();
 +
</syntaxhighlight>
  
  
 +
* 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.
 +
:<syntaxhighlight lang="java" line enclose="div" highlight="" >
 +
String bddestino = "/data/data/" + getPackageName() + "/databases/"
 +
+ UD5_05_BASEDATOS.NOME_BD;
 +
File file = new File(bddestino);
 +
if (file.exists())
 +
return; // XA EXISTE A BASE DE DATOS
 +
</syntaxhighlight>
  
* Aplicado ao noso caso:
+
Copiamos a base de datos.
<syntaxhighlight lang="xml" line enclose="div" highlight="10-20,35,43-44" >
+
:<syntaxhighlight lang="java" line enclose="div" highlight="" >
package es.cursoandroid.cifprodolfoucha.aprendiendo.Persistencia.BasesDatos;
+
String pathbd = "/data/data/" + getPackageName()
 +
+ "/databases/";
 +
File filepathdb = new File(pathbd);
 +
filepathdb.mkdirs();
  
import android.content.Context;
+
InputStream inputstream;
import android.database.sqlite.SQLiteDatabase;
+
try {
import android.database.sqlite.SQLiteOpenHelper;
+
inputstream = getAssets().open(UD5_05_BASEDATOS.NOME_BD);
 +
OutputStream outputstream = new FileOutputStream(bddestino);
  
public class UD06_02_BaseDatos extends SQLiteOpenHelper{
+
int tamread;
    public final static String NOME_BD="UD06_02_BD_FILE.db";
+
byte[] buffer = new byte[2048];
    public final static int VERSION_BD=1;
 
    private static UD06_02_BaseDatos sInstance;
 
  
    public static synchronized UD06_02_BaseDatos getInstance(Context context) {
+
while ((tamread = inputstream.read(buffer)) > 0) {
        // Use the application context, which will ensure that you
+
outputstream.write(buffer, 0, tamread);
        // don't accidentally leak an Activity's context.
+
}
        // See this article for more information: http://bit.ly/6LRzfx
 
        if (sInstance == null) {
 
            sInstance = new UD06_02_BaseDatos(context.getApplicationContext());
 
        }
 
        return sInstance;
 
    }
 
 
 
    private String CREAR_TABOA_AULAS ="CREATE TABLE AULAS ( " +
 
            "_id  INTEGER PRIMARY KEY AUTOINCREMENT," +
 
            "nome VARCHAR( 50 )  NOT NULL)";
 
  
 +
inputstream.close();
 +
outputstream.flush();
 +
outputstream.close();
 +
} catch (IOException e) {
 +
// TODO Auto-generated catch block
 +
e.printStackTrace();
 +
}
 +
</syntaxhighlight>
  
    public UD06_02_BaseDatos(Context context) {
 
        super(context, NOME_BD, null, VERSION_BD);
 
        // TODO Auto-generated constructor stub
 
    }
 
  
    @Override
 
    public void onCreate(SQLiteDatabase db) {
 
        // TODO Auto-generated method stub
 
      // db.execSQL(CREAR_TABOA_AULAS);
 
 
    }
 
  
    @Override
 
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
 
        // TODO Auto-generated method stub
 
  
        // db.execSQL("DROP TABLE IF EXISTS AULAS");
 
        // onCreate(db);
 
    }
 
  
}
+
=====Caso práctico=====
</syntaxhighlight>
 
  
 +
Imos copiar unha base de datos feita coa ferramenta anterior dende o cartafol /assets/ ao cartafol /databases/.
  
'''Código do layout xml'''
+
<gallery caption="Copiando unha base de datos existente" widths="350" heights="350px" perrow="2">
<syntaxhighlight lang="xml" line enclose="div" highlight="" >
+
Image:PDM_Avanzada_bd_7.jpg| Temos a base de datos a copia no cartafol /assets/
<?xml version="1.0" encoding="utf-8"?>
+
Image:PDM_Avanzada_bd_6.jpg| Podemos comprobar como a base de datos deste exercicio (a número 02) non existe.
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+
Image:PDM_Avanzada_bd_8.jpg| Executamos a aplicación e como a base de datos non está copiada, a copia dende /assets/ ao cartafol 'databases/'.
    xmlns:app="http://schemas.android.com/apk/res-auto"
+
Image:PDM_Avanzada_bd_9.jpg| Agora podemos comprobar como a base de datos xa está copiada.
    xmlns:tools="http://schemas.android.com/tools"
+
Image:PDM_Avanzada_bd_10.jpg| Podemos premer o botón de abrir. Se a base de datos non existira, crearía unha baleira segundo o establecido no método onCreate.
    android:layout_width="match_parent"
+
Image:PDM_Avanzada_bd_11.jpg| Se volvemos a executar a aplicación ou rotamos a pantalla, como a base de datos xa foi copiada, non a volver a copiar.
    android:layout_height="match_parent"
+
</gallery>
    tools:context=".Persistencia.BasesDatos.UD06_02_DatosPersistentes_BD">
+
 
 +
 
 +
<br />
 +
======Preparación======
 +
 
 +
Hai que descomprimir e copiar o seguinte arquivo ao cartafol /assets/ do voso proxecto.
 +
 
 +
[[Media:EX_02_BD_FILE.zip]]
  
    <Button
+
O cartafol /assets/ xa o deberíamos ter creado [http://wiki.cifprodolfoucha.es/index.php?title=PDM_Avanzado_Datos_Persistentes_Arquivos#Preparaci.C3.B3n dunha práctica anterior].
        android:id="@+id/btnAbrirBD_UD06_02_BaseDatos"
 
        android:layout_width="wrap_content"
 
        android:layout_height="wrap_content"
 
        android:layout_marginBottom="8dp"
 
        android:layout_marginEnd="8dp"
 
        android:layout_marginStart="8dp"
 
        android:layout_marginTop="8dp"
 
        android:text="ABRIR BASE DE DATOS"
 
        app:layout_constraintBottom_toBottomOf="parent"
 
        app:layout_constraintEnd_toEndOf="parent"
 
        app:layout_constraintStart_toStartOf="parent"
 
        app:layout_constraintTop_toTopOf="parent" />
 
</android.support.constraint.ConstraintLayout>
 
</syntaxhighlight>
 
  
  
'''Código da clase UD06_02_DatosPersistentes_BD'''<br/>
+
<u>Nota:</u> O alumno pode utilizar a súa propia base de datos xerada coa ferramenta anterior.
'''Obxectivo:''' Copiar unha base de datos dende /assets/ ao cartafol /databases/.<br/>
 
<syntaxhighlight lang="java" line enclose="div" highlight="19-55,59-70" >
 
package es.cursoandroid.cifprodolfoucha.aprendiendo.Persistencia.BasesDatos;
 
  
import android.app.Activity;
+
======Creamos a Activity======
import android.os.Bundle;
 
import android.view.View;
 
import android.widget.Toast;
 
  
import java.io.File;
+
* Dentro do paquete '''BasesDatos''' (creado previamente no paso anterior) crear unha nova 'Empty Activity' de nome: '''UD05_02_DatosPersistentes_BD''' de tipo Launcher e sen compatibilidade.
import java.io.FileOutputStream;
+
: Modifica o arquivo '''AndroidManifiest.xml''' e engade unha label á activity como [http://wiki.cifprodolfoucha.es/index.php?title=PDM_Creando_proxecto_base xa vimos na creación do proxecto base].
import java.io.IOException;
 
import java.io.InputStream;
 
import java.io.OutputStream;
 
  
import es.cursoandroid.cifprodolfoucha.aprendiendo.R;
+
 +
<br />
 +
'''Código da clase UD05_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)
  
public class UD06_02_DatosPersistentes_BD extends Activity {
 
    private UD06_02_BaseDatos baseDatos;
 
  
    private void copiarBD() {
+
* 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 [https://es.wikipedia.org/wiki/Singleton patrón Singleton].
        String bddestino = "/data/data/" + getPackageName() + "/databases/"
+
: Isto se implementa da forma seguinte:
                + UD06_02_BaseDatos.NOME_BD;
+
<syntaxhighlight lang="xml" line enclose="div" highlight="" >
        File file = new File(bddestino);
+
public class MiClase {
        if (file.exists()) {
+
    private static MiClase sInstance;
            Toast.makeText(getApplicationContext(), "A BD NON SE VAI COPIAR. XA EXISTE", Toast.LENGTH_LONG).show();
 
            return; // XA EXISTE A BASE DE DATOS
 
        }
 
  
        // En caso de que non exista creamos o cartafol /databases/
 
        String pathbd = "/data/data/" + getPackageName()
 
                + "/databases";
 
        File filepathdb = new File(pathbd);
 
        if (!filepathdb.exists()) {
 
            filepathdb.mkdirs();
 
        }
 
  
        InputStream inputstream;
 
        try {
 
            inputstream = getAssets().open(UD06_02_BaseDatos.NOME_BD);
 
            OutputStream outputstream = new FileOutputStream(bddestino);
 
  
            int tamread;
+
    public static synchronized MiClase getInstance() {
            byte[] buffer = new byte[2048];
+
         if (sInstance == null) {
 
+
             sInstance = new MiClase ();
            while ((tamread = inputstream.read(buffer)) > 0) {
 
                outputstream.write(buffer, 0, tamread);
 
            }
 
 
 
            inputstream.close();
 
            outputstream.flush();
 
            outputstream.close();
 
            Toast.makeText(getApplicationContext(), "BASE DE DATOS COPIADA", Toast.LENGTH_LONG).show();
 
         } catch (IOException e) {
 
             // TODO Auto-generated catch block
 
            e.printStackTrace();
 
 
         }
 
         }
 +
        return sInstance;
 +
    }
  
    }
+
}
 +
</syntaxhighlight>
  
    private void xestionarEventos(){
 
  
        findViewById(R.id.btnAbrirBD_UD06_02_BaseDatos).setOnClickListener(new View.OnClickListener() {
+
: Agora se quero facer uso desta clase non fago: MiClase miClase = new MiClase();
            @Override
+
: Terei que poñer: '''MiClase miClase = MiClase.getInstance();'''
            public void onClick(View v) {
 
                baseDatos = UD06_02_BaseDatos.getInstance(getApplicationContext());
 
                baseDatos.getWritableDatabase();
 
  
                Toast.makeText(getApplicationContext(), "BASE DE DATOS ABERTA", Toast.LENGTH_LONG).show();
+
: Se o fago á vez dende diferentes clase sempre estarei empregando a mesma instancia.  
            }
+
: Da outra forma teríamos un obxecto diferente.
        });
 
    }
 
  
    @Override
 
    protected void onCreate(Bundle savedInstanceState) {
 
        super.onCreate(savedInstanceState);
 
        setContentView(R.layout.activity_ud06_02__datos_persistentes__bd);
 
  
        xestionarEventos();
 
        copiarBD();
 
    }
 
}
 
</syntaxhighlight>
 
  
* Liñas 19-55: Procedemento para copiar a base de datos. Xa o vimos na [http://wiki.cifprodolfoucha.es/index.php?title=PDM_Avanzado_Datos_Persistentes_Arquivos sección de Arquivos]].
+
* Aplicado ao noso caso:
:* 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.
+
<syntaxhighlight lang="xml" line enclose="div" highlight="10-20" >
* 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.
+
package es.cursoandroid.cifprodolfoucha.aprendiendo.Persistencia.BasesDatos;
:* 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===
+
import android.content.Context;
 +
import android.database.sqlite.SQLiteDatabase;
 +
import android.database.sqlite.SQLiteOpenHelper;
  
Se estamos a utilizar o emulador ou podemos ser root no dispositivo real, podemos conectarnos á base de datos dende consola.
+
public class UD05_02_BaseDatos extends SQLiteOpenHelper{
 +
    public final static String NOME_BD="EX_02_BD_FILE.db";
 +
    public final static int VERSION_BD=1;
 +
    private static UD05_02_BaseDatos sInstance;
  
O proceso é o seguinte:
+
    public static synchronized UD05_02_BaseDatos getInstance(Context context) {
 +
        // Use the application context, which will ensure that you
 +
        // don't accidentally leak an Activity's context.
 +
        // See this article for more information: http://bit.ly/6LRzfx
 +
        if (sInstance == null) {
 +
            sInstance = new UD05_02_BaseDatos(context.getApplicationContext());
 +
        }
 +
        return sInstance;
 +
    }
  
* Abrimos un terminal (linux) ou consola (windows).
+
    private String CREAR_TABOA_AULAS ="CREATE TABLE AULAS ( " +
 +
            "_id  INTEGER PRIMARY KEY AUTOINCREMENT," +
 +
            "nome VARCHAR( 50 ) NOT NULL)";
  
* 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:
+
    public UD05_02_BaseDatos(Context context) {
:* adb shell (WINDOWS)
+
        super(context, NOME_BD, null, VERSION_BD);
:* ./adb shell (LINUX)
+
        // TODO Auto-generated constructor stub
 +
    }
  
* Unha vez no shell, conectamos coa base de datos escribindo:
+
    @Override
 +
    public void onCreate(SQLiteDatabase db) {
 +
        // TODO Auto-generated method stub
 +
      // db.execSQL(CREAR_TABOA_AULAS);
  
:* sqlite3 /data/data/o_voso_paquete/databases/NOME_BASEDATOS
+
    }
  
* Agora estamos conectados a SQLIte e podemos executar ordes SQL (deben acabar en ; ).
+
    @Override
 +
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
 +
        // TODO Auto-generated method stub
  
:Por exemplo:
+
        // db.execSQL("DROP TABLE IF EXISTS AULAS");
:* .tables : lista as táboas.
+
        // onCreate(db);
:* SELECT * FROM NOME_TABOA; (amosa os datos)
+
    }
  
* Para saír do sqlite poñeremos:
+
}
::.exit
+
</syntaxhighlight>
 
 
* Para saír do adb shell:
 
::exit
 
  
  
 +
'''Código do layout xml'''
 +
<syntaxhighlight lang="xml" line enclose="div" highlight="" >
 +
<?xml version="1.0" encoding="utf-8"?>
 +
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 +
    xmlns:app="http://schemas.android.com/apk/res-auto"
 +
    xmlns:tools="http://schemas.android.com/tools"
 +
    android:layout_width="match_parent"
 +
    android:layout_height="match_parent"
 +
    tools:context=".Persistencia.BasesDatos.UD05_02_DatosPersistentes_BD">
  
 +
    <Button
 +
        android:id="@+id/btnAbrirBD_UD05_02_BaseDatos"
 +
        android:layout_width="wrap_content"
 +
        android:layout_height="wrap_content"
 +
        android:layout_marginBottom="8dp"
 +
        android:layout_marginEnd="8dp"
 +
        android:layout_marginStart="8dp"
 +
        android:layout_marginTop="8dp"
 +
        android:text="ABRIR BASE DE DATOS"
 +
        app:layout_constraintBottom_toBottomOf="parent"
 +
        app:layout_constraintEnd_toEndOf="parent"
 +
        app:layout_constraintStart_toStartOf="parent"
 +
        app:layout_constraintTop_toTopOf="parent" />
 +
</android.support.constraint.ConstraintLayout>
 +
</syntaxhighlight>
  
<u>Nota:</u> Coidado cas maiúsculas / minúsculas.
 
  
==Operacións sobre unha base de datos==
+
'''Código da clase UD05_02_DatosPersistentes_BD'''<br/>
 +
'''Obxectivo:''' Copiar unha base de datos dende /assets/ ao cartafol /databases/.<br/>
 +
<syntaxhighlight lang="java" line enclose="div" highlight="19-55,59-70" >
 +
package es.cursoandroid.cifprodolfoucha.aprendiendo.Persistencia.BasesDatos;
  
===Introdución===
+
import android.app.Activity;
 +
import android.os.Bundle;
 +
import android.view.View;
 +
import android.widget.Toast;
  
As operacións que imos facer sobre a base de datos son:
+
import java.io.File;
 
+
import java.io.FileOutputStream;
* '''SELECT''': Selección.
+
import java.io.IOException;
* '''UPDATE''': Actualización.
+
import java.io.InputStream;
* '''DELETE''': Borrado.
+
import java.io.OutputStream;
* '''INSERT''': Engadir.
 
  
<u>Nota:</u> Neste curso consideramos que os alumnos teñen coñecementos para entender as ordes SQL que dan soporte a estas operacións.
+
import es.cursoandroid.cifprodolfoucha.aprendiendo.R;
  
 +
public class UD05_02_DatosPersistentes_BD extends Activity {
 +
    private UD05_02_BaseDatos baseDatos;
  
Para poder realizar as operacións anteriormente comentadas imos necesitar facer uso dun obxecto da [http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html clase SQLiteDatabase].
+
    private void copiarBD() {
 +
        String bddestino = "/data/data/" + getPackageName() + "/databases/"
 +
                + UD05_02_BaseDatos.NOME_BD;
 +
        File file = new File(bddestino);
 +
        if (file.exists()) {
 +
            Toast.makeText(getApplicationContext(), "A BD NON SE VAI COPIAR. XA EXISTE", Toast.LENGTH_LONG).show();
 +
            return; // XA EXISTE A BASE DE DATOS
 +
        }
  
Podemos obtelo de dúas formas:
+
        // En caso de que non exista creamos o cartafol /databases/
 +
        String pathbd = "/data/data/" + getPackageName()
 +
                + "/databases";
 +
        File filepathdb = new File(pathbd);
 +
        if (!filepathdb.exists()) {
 +
            filepathdb.mkdirs();
 +
        }
  
* Dito obxecto o temos como parámetro no método '''onCreate''' e no método '''onUpgrade''' da clase que deriva de '''SQLiteOpenHelper''':
+
        InputStream inputstream;
:<syntaxhighlight lang="java" line enclose="div" highlight="2,8" >
+
        try {
@Override
+
            inputstream = getAssets().open(UD05_02_BaseDatos.NOME_BD);
public void onCreate(SQLiteDatabase db) {
+
            OutputStream outputstream = new FileOutputStream(bddestino);
// TODO Auto-generated method stub
 
  
}
+
            int tamread;
 +
            byte[] buffer = new byte[2048];
  
@Override
+
            while ((tamread = inputstream.read(buffer)) > 0) {
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+
                outputstream.write(buffer, 0, tamread);
// TODO Auto-generated method stub
+
            }
 
}
 
</syntaxhighlight>
 
  
: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'.
+
            inputstream.close();
 
+
            outputstream.flush();
* Cando abrimos a base de datos para escribir ou para ler:
+
            outputstream.close();
 +
            Toast.makeText(getApplicationContext(), "BASE DE DATOS COPIADA", Toast.LENGTH_LONG).show();
 +
        } catch (IOException e) {
 +
            // TODO Auto-generated catch block
 +
            e.printStackTrace();
 +
        }
  
:<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
    }
SQLiteDatabase sqlLiteDB = ud4_05_baseDatos.getWritableDatabase();
 
</syntaxhighlight>
 
  
:<u>Nota:</u> Código baseado a partires do exemplo anterior.
+
    private void xestionarEventos(){
  
Agora con dito obxecto podemos facer as operacións SQL.
+
        findViewById(R.id.btnAbrirBD_UD05_02_BaseDatos).setOnClickListener(new View.OnClickListener() {
 +
            @Override
 +
            public void onClick(View v) {
 +
                baseDatos = UD05_02_BaseDatos.getInstance(getApplicationContext());
 +
                baseDatos.getWritableDatabase();
  
===INSERT===
+
                Toast.makeText(getApplicationContext(), "BASE DE DATOS ABERTA", Toast.LENGTH_LONG).show();
 +
            }
 +
        });
 +
    }
  
Engadir unha nova fila.
+
    @Override
 +
    protected void onCreate(Bundle savedInstanceState) {
 +
        super.onCreate(savedInstanceState);
 +
        setContentView(R.layout.activity_UD05_02__datos_persistentes__bd);
  
Máis información en: http://www.w3schools.com/sql/sql_insert.asp
+
        xestionarEventos();
 +
        copiarBD();
 +
    }
 +
}
 +
</syntaxhighlight>
  
Temos dúas formas de engadir datos:
+
* Liñas 19-55: Procedemento para copiar a base de datos. Xa o vimos na [http://wiki.cifprodolfoucha.es/index.php?title=PDM_Avanzado_Datos_Persistentes_Arquivos sección de Arquivos]].
 +
:* 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.
  
* Utilizando a orden INSERT de SQL:
+
===Xestionar a base de datos dende consola===
  
<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
Se estamos a utilizar o emulador ou podemos ser root no dispositivo real, podemos conectarnos á base de datos dende consola.
sqlLiteDB.execSQL("INSERT INTO NOME_TABOA (campo1,campo2,....) VALUES ('valor1','valor2',....)");
 
</syntaxhighlight>
 
  
<u>Nota:</u> Os valores numéricos non levan comillas.
+
O proceso é o seguinte:
  
 +
* Abrimos un terminal (linux) ou consola (windows).
  
* A segunda forma é utilizando un obxecto da [http://developer.android.com/reference/android/content/ContentValues.html clase ContentValues], o cal garda pares da forma clave-valor, e no noso caso, serán pares nome_columna-valor.
+
* 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'.
  
Chamaremos despois aos métodos '''insert()''', '''update()''' e '''delete()''' da clase SQLiteDatabase.
+
* Dende a consola deberedes escribir:
 +
:* WINDOWS:
 +
::* adb root
 +
::* adb shell
 +
:* LINUX
 +
::* ./adb root
 +
::* ./adb shell
  
Por exemplo:
+
* Unha vez no shell, conectamos coa base de datos escribindo:
  
<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
:* sqlite3 /data/data/o_voso_paquete/databases/NOME_BASEDATOS
ContentValues datosexemplo = new ContentValues();
 
datosexemplo.put("NOME_COL1", "Valor col1_a");
 
datosexemplo.put("NOME_COL2", "Valor_col2_a");
 
long idFila1 = sqlLiteDB.insert(NOME_TABOA, null, datosexemplo);
 
 
                datosexemplo.clear();
 
datosexemplo.put("NOME_COL1", "Valor col1_b");
 
datosexemplo.put("NOME_COL2", "Valor_col2_b");
 
long idFila2 = sqlLiteDB.insert(NOME_TABOA, null, datosexemplo);
 
</syntaxhighlight>
 
  
 +
* Agora estamos conectados a SQLIte e podemos executar ordes SQL (deben acabar en ; ).
  
* O segundo parámetro leva de valor null, xa que só se usa en contadas ocasións, coma por exemplo, para engadir rexistros baleiros.
+
:Por exemplo:
* Neste exemplo supoñemos que a táboa onde se van engadir os rexistros ten de clave primaria un campo autonumérico.
+
:* .tables : lista as táboas.
* O método insert devolve o id da fila engadida (a clave principal é autonumérica) ou -1 se hai un erro.
+
:* SELECT * FROM NOME_TABOA; (amosa os datos)
  
===UPDATE===
+
* Para saír do sqlite poñeremos:
 +
::.exit
  
Actualiza datos.
+
* Para saír do adb shell:
 +
::exit
  
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.
 
  
<syntaxhighlight lang="java" line enclose="div" highlight="" >
 
ContentValues datosexemplo = new ContentValues();
 
datosexemplo.put("COLUMNA_MODIFICAR","TEXTO NOVO");
 
String condicionwhere = "COL1=?";
 
String[] parametros = new String[]{'Texto a buscar'};
 
int rexistrosafectados = sqlLiteDB.update(NOME_TABOA,datosexemplo,condicionwhere,parametros);
 
</syntaxhighlight>
 
  
Se queremos unha condición composta (por exemplo con AND):
+
<u>'''Nota:'''</u>
<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
:* Coidado cas maiúsculas / minúsculas.
ContentValues datosexemplo = new ContentValues();
+
:* Tedes a lista completa de comandos [https://www.sqlite.org/cli.html neste enlace].
datosexemplo.put("COLUMNA_MODIFICAR","TEXTO NOVO");
 
String condicionwhere = "COL1=? AND COL2=?";
 
String[] parametros = new String[]{'Texto1','Texto2'};
 
int rexistrosafectados = sqlLiteDB.update(NOME_TABOA,datosexemplo,condicionwhere,parametros);
 
</syntaxhighlight>
 
  
Nun exemplo concreto:
+
==Operacións sobre unha base de datos==
  
TÁBOA AULAS (_id, nome):
+
===Introdución===
  
Esta consulta sql:
+
As operacións que imos facer sobre a base de datos son:
  
<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
* '''SELECT''': Selección.
UPDATE AULAS
+
* '''UPDATE''': Actualización.
SET nome='Novo nome'
+
* '''DELETE''': Borrado.
WHERE _id=5;
+
* '''INSERT''': Engadir.
</syntaxhighlight>
 
  
Pasará a ser:
+
<u>Nota:</u> Neste curso consideramos que os alumnos teñen coñecementos para entender as ordes SQL que dan soporte a estas operacións.
<syntaxhighlight lang="java" line enclose="div" highlight="" >
 
ContentValues datos = new ContentValues();
 
datos.put("nome","Novo nome");
 
String condicionwhere = "_id=?";
 
String[] parametros = new String[]{String.valueOf(5)};
 
int rexistrosafectados = sqlLiteDB.update("AULAS",datos,condicionwhere,parametros);
 
</syntaxhighlight>  
 
  
  
 +
Para poder realizar as operacións anteriormente comentadas imos necesitar facer uso dun obxecto da [http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html clase SQLiteDatabase].
  
Aquí hai que sinalar varios aspectos:
+
Podemos obtelo de dúas formas:
* 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:
+
* Dito obxecto o temos como parámetro no método '''onCreate''' e no método '''onUpgrade''' da clase que deriva de '''SQLiteOpenHelper''':
::String CONSULTA="SELECT id,nome FROM CLASES WHERE nome = 'AULA 1'";
+
:<syntaxhighlight lang="java" line enclose="div" highlight="2,8" >
 +
@Override
 +
public void onCreate(SQLiteDatabase db) {
 +
// TODO Auto-generated method stub
  
* 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.
+
@Override
 +
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
 +
// TODO Auto-generated method stub
 +
 +
}
 +
</syntaxhighlight>
  
===DELETE===
+
: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'.
  
Borra filas.
+
* Cando abrimos a base de datos para escribir ou para ler:
  
Máis información: http://www.w3schools.com/sql/sql_delete.asp
+
:<syntaxhighlight lang="java" line enclose="div" highlight="" >
 +
SQLiteDatabase sqlLiteDB = ud4_05_baseDatos.getWritableDatabase();
 +
</syntaxhighlight>
  
O método delete() é parecido ao anterior.  
+
:<u>Nota:</u> Código baseado a partires do exemplo 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:
+
Agora con dito obxecto podemos facer as operacións SQL.
  
<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
===INSERT===
String condicionwhere = "COL1=?";
 
String[] parametros = new String[]{'Valor a buscar'};
 
int rexistrosafectados = sqlLiteDB.delete(NOME_TABOA,condicionwhere,parametros);
 
</syntaxhighlight>
 
  
 +
Engadir unha nova fila.
  
* O método delete devolve o número de filas afectadas ou -1 en caso de erro.
+
Máis información en: http://www.w3schools.com/sql/sql_insert.asp
* Os conceptos da cláusula '''Where''' son os mesmos que no caso do '''Update'''.
 
  
 +
Temos dúas formas de engadir datos:
  
Nun exemplo concreto:
+
* Utilizando a orden INSERT de SQL:
 
 
TÁBOA AULAS (_id, nome):
 
 
 
Esta consulta sql:
 
  
 
<syntaxhighlight lang="java" line enclose="div" highlight="" >
 
<syntaxhighlight lang="java" line enclose="div" highlight="" >
DELETE FROM AULAS
+
sqlLiteDB.execSQL("INSERT INTO NOME_TABOA (campo1,campo2,....) VALUES ('valor1','valor2',....)");
WHERE _id=5;
 
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
Pasará a ser:
+
<u>Nota:</u> Os valores numéricos non levan comillas.
<syntaxhighlight lang="java" line enclose="div" highlight="" >
 
String condicionwhere = "_id=?";
 
String[] parametros = new String[]{String.valueOf(5)};
 
int rexistrosafectados = sqlLiteDB.delete("AULAS",condicionwhere,parametros);
 
</syntaxhighlight>
 
  
===SELECT===
 
  
A selección de filas leva consigo que o resultado vai poder traer de volta moitas filas.
+
* A segunda forma é utilizando un obxecto da [http://developer.android.com/reference/android/content/ContentValues.html clase ContentValues], o cal garda pares da forma clave-valor, e no noso caso, serán pares nome_columna-valor.
  
Veremos máis adiante como devolver esas filas como se foran un Array de obxectos.
+
Chamaremos despois aos métodos '''insert()''', '''update()''' e '''delete()''' da clase SQLiteDatabase.
  
 +
Por exemplo:
  
Para facer consultas á base de datos podemos utilizar dous métodos da clase '''SQLiteQueryBuilder''' : rawQuery e query
+
<syntaxhighlight lang="java" line enclose="div" highlight="" >
 +
ContentValues datosexemplo = new ContentValues();
 +
datosexemplo.put("NOME_COL1", "Valor col1_a");
 +
datosexemplo.put("NOME_COL2", "Valor_col2_a");
 +
long idFila1 = sqlLiteDB.insert(NOME_TABOA, null, datosexemplo);
 +
 +
                datosexemplo.clear();
 +
datosexemplo.put("NOME_COL1", "Valor col1_b");
 +
datosexemplo.put("NOME_COL2", "Valor_col2_b");
 +
long idFila2 = sqlLiteDB.insert(NOME_TABOA, null, datosexemplo);
 +
</syntaxhighlight>
  
* '''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.
+
* 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.
  
:Por exemplo:
+
===UPDATE===
:<syntaxhighlight lang="java" line enclose="div" highlight="" >
 
  Cursor cursor = sqlLiteDB.rawQuery("select _id,nome from AULAS where _id = ?", new String[] { String.valueOf(5) });
 
</syntaxhighlight>
 
  
:* O segundo parámetro é un array de Strings no caso de que a consulta leve parámetros (símbolo ?).
+
Actualiza datos.
  
* '''query'''
+
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.
  
:<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
<syntaxhighlight lang="java" line enclose="div" highlight="" >
Cursor cursor = sqlLiteDB.query(DATABASE_TABLE,  
+
ContentValues datosexemplo = new ContentValues();
  new String[] { col1,col2,... },
+
datosexemplo.put("COLUMNA_MODIFICAR","TEXTO NOVO");
  null, null, null, null, null);  
+
String condicionwhere = "COL1=?";
 +
String[] parametros = new String[]{"Texto a buscar"};
 +
int rexistrosafectados = sqlLiteDB.update(NOME_TABOA,datosexemplo,condicionwhere,parametros);
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
:Os parámetros en query son:
+
Se queremos unha condición composta (por exemplo con AND):
:*DATABASE_TABLE: Nome da táboa.
+
<syntaxhighlight lang="java" line enclose="div" highlight="" >
:*Lista de columnas, nun array de String. Se poñemos null devolve todas.
+
ContentValues datosexemplo = new ContentValues();
:*Cláusula where
+
datosexemplo.put("COLUMNA_MODIFICAR","TEXTO NOVO");
:*Array de string no caso de que a cláusula where leve parámetros da forma ‘id=?’, é dicir, co caracter ?.
+
String condicionwhere = "COL1=? AND COL2=?";
:*Array de String onde irán os nomes das columnas para facer group by.
+
String[] parametros = new String[]{"Texto1","Texto2"};
:*Array de String onde irán os nomes das columnas para facer having.
+
int rexistrosafectados = sqlLiteDB.update(NOME_TABOA,datosexemplo,condicionwhere,parametros);
:*Array de String onde irán os nomes das columnas para facer order by.
+
</syntaxhighlight>
 +
 
 +
Nun exemplo concreto:
 +
 
 +
TÁBOA AULAS (_id, nome):
  
:Por exemplo, a orde SQL:
+
Esta consulta sql:
  
:SELECT nome FROM AULAS
+
<syntaxhighlight lang="java" line enclose="div" highlight="" >
 +
UPDATE AULAS
 +
SET nome='Novo nome'
 +
WHERE _id=5;
 +
</syntaxhighlight>
  
:sería:
+
Pasará a ser:
:<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
<syntaxhighlight lang="java" line enclose="div" highlight="" >
Cursor cursor = sqlLiteDB.query("AULAS",  
+
ContentValues datos = new ContentValues();
  new String[] { nome },
+
datos.put("nome","Novo nome");
  null, null, null, null, null);  
+
String condicionwhere = "_id=?";
 +
String[] parametros = new String[]{String.valueOf(5)};
 +
int rexistrosafectados = sqlLiteDB.update("AULAS",datos,condicionwhere,parametros);
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
<u>Nos imos a usar a primeira opción.</u>
 
  
  
Os dous tipos de consultas (rawquery e query) devolven un obxecto Cursor.
+
Aquí hai que sinalar varios aspectos:
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.
+
* 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)
  
O proceso normalmente será o de percorrer todo o cursor e devolver un '''ArrayList''' dun tipo determinado (verase despois).
+
* 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'";
  
:<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
* Cada '?' se corresponde cun dato do array de parámetros. No noso exemplo enviamos o ID que teremos que converter a String:
  Cursor cursor = sqlLiteDB.rawQuery("select _id,nome from AULAS", null);
+
::String[] parametros = new String[]{String.valueOf(5)};
  if (cursor.moveToFirst()) {                // Se non ten datos xa non entra
 
while (!cursor.isAfterLast()) {    // Quédase no bucle ata que remata de percorrer o cursor. Fixarse que leva un ! (not) diante
 
long id =cursor.getLong(0);
 
                String nome = cursor.getString(1);
 
cursor.moveToNext();
 
}
 
  }
 
</syntaxhighlight>
 
  
:Dentro do bucle temos acceso ós datos de cada fila.
+
* O método update devolve o número de filas afectadas ou -1 en caso de erro.
: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.
 
  
<br/>
+
===DELETE===
:'''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.
+
Borra filas.
<br/>
 
<br/>
 
<br/>
 
  
----
+
Máis información: http://www.w3schools.com/sql/sql_delete.asp
:<u>'''Aclaración'''</u>: 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 [http://developer.android.com/reference/android/widget/SimpleCursorAdapter.html SimpleCursorAdapter]] e asociar dito Adaptador a un elemento gráfico como unha lista.
+
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:
  
:Por exemplo:
+
<syntaxhighlight lang="java" line enclose="div" highlight="" >
:<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
String condicionwhere = "COL1=?";
SimpleCursorAdapter mAdapter = new SimpleCursorAdapter(this,R.layout.list_layout_creado, cursor, new String[] { "nome" },new int[] { android.R.id.text1});
+
String[] parametros = new String[]{'Valor a buscar'};
 +
int rexistrosafectados = sqlLiteDB.delete(NOME_TABOA,condicionwhere,parametros);
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
: 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.
+
* O método delete devolve o número de filas afectadas ou -1 en caso de erro.
: Lembrar que xa vimos o uso de adaptadores [http://wiki.cifprodolfoucha.es/index.php?title=Spinner_a_trav%C3%A9s_de_adaptador na Unidade 4 desta Wiki].
+
* Os conceptos da cláusula '''Where''' son os mesmos que no caso do '''Update'''.
  
----
 
  
 +
Nun exemplo concreto:
  
<br/>
+
TÁBOA AULAS (_id, nome):
  
===Onde facer as operacións===
+
Esta consulta sql:
  
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).
+
<syntaxhighlight lang="java" line enclose="div" highlight="" >
 +
DELETE FROM AULAS
 +
WHERE _id=5;
 +
</syntaxhighlight>
  
Poderíamos facelo na activity a partires de obter o obxecto sqlLiteDB, pero desta forma quedará moito máis claro.
+
Pasará a ser:
 +
<syntaxhighlight lang="java" line enclose="div" highlight="" >
 +
String condicionwhere = "_id=?";
 +
String[] parametros = new String[]{String.valueOf(5)};
 +
int rexistrosafectados = sqlLiteDB.delete("AULAS",condicionwhere,parametros);
 +
</syntaxhighlight>
  
 +
===SELECT===
  
Polo tanto imos ter que gardar o obxecto sqlLiteDB na propia clase para poder ter acceso a el e poder facer as operacións.
+
A selección de filas leva consigo que o resultado vai poder traer de volta moitas filas.
  
Unha forma de facelo é a seguinte:
+
Veremos máis adiante como devolver esas filas como se foran un Array de obxectos.
  
* Definimos unha propiedade public dentro da clase SQLiteOpenHelper.
 
  
<syntaxhighlight lang="java" enclose="div" highlight="" >
+
Para facer consultas á base de datos podemos utilizar dous métodos da clase '''SQLiteQueryBuilder''' : rawQuery e query
public class UD6_03_BaseDatos extends SQLiteOpenHelper{
 
  
public SQLiteDatabase sqlLiteDB;
+
* '''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:
 +
:<syntaxhighlight lang="java" line enclose="div" highlight="" >
 +
  Cursor cursor = sqlLiteDB.rawQuery("select _id,nome from AULAS where _id = ?", new String[] { String.valueOf(5) });
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
 +
:* O segundo parámetro é un array de Strings no caso de que a consulta leve parámetros (símbolo ?).
  
* Creamos un método '''abrirBD()''' y '''pecharBD()''':
+
* '''query'''
  
<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
:<syntaxhighlight lang="java" line enclose="div" highlight="" >
public class UD6_03_BaseDatos extends SQLiteOpenHelper{
+
Cursor cursor = sqlLiteDB.query(DATABASE_TABLE,
    public final static String NOME_BD="UD06_03_BD_FILE.db";
+
  new String[] { col1,col2,... },
    public final static int VERSION_BD=1;
+
  null, null, null, null, null);  
    private static UD06_03_BaseDatos sInstance;
+
</syntaxhighlight>
  
    private SQLiteDatabase db;
+
: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.
  
    public static synchronized UD06_03_BaseDatos getInstance(Context context) {
+
:Por exemplo, a orde SQL:
        // Use the application context, which will ensure that you
+
 
        // don't accidentally leak an Activity's context.
+
:SELECT nome FROM AULAS
        // See this article for more information: http://bit.ly/6LRzfx
 
        if (sInstance == null) {
 
            sInstance = new UD06_03_BaseDatos(context.getApplicationContext());
 
        }
 
        return sInstance;
 
    }
 
        ...............
 
    public void abrirBD(){
 
        if (!sqlLiteDB.isOpen()){
 
          sqlLiteDB = sInstance.getWritableDatabase();
 
        }
 
    }
 
  
    public void pecharBD(){
+
:sería:
        if (sqlLiteDB.isOpen()){
+
:<syntaxhighlight lang="java" line enclose="div" highlight="" >
          sqlLiteDB.close();
+
Cursor cursor = sqlLiteDB.query("AULAS",
        }
+
  new String[] { nome },
    }
+
  null, null, null, null, null);  
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
 +
<u>Nos imos a usar a primeira opción.</u>
  
* Agora definimos os método que necesitemos para dar soporte á nosa aplicación.
 
  
===Xuntando todo cos Modelos===
+
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).
  
Normalmente os datos dunha base de datos os imos 'modelizar' en forma de clases.
+
:<syntaxhighlight lang="java" line enclose="div" highlight="" >
 +
  Cursor cursor = sqlLiteDB.rawQuery("select _id,nome from AULAS", null);
 +
  while (cursor.moveToNext()) {    // Quédase no bucle ata que remata de percorrer o cursor
 +
        long id =cursor.getLong(0);
 +
        String nome = cursor.getString(1);
 +
  }
 +
</syntaxhighlight>
  
Así, se se ten unha táboa AULAS poderase modelizar utilizando unha clase AULAS que teña como propiedades as columnas da táboa.
+
: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.
  
----
+
<br/>
Non pretendemos entrar a explicar o [http://es.wikipedia.org/wiki/Lenguaje_unificado_de_modelado UML] e o [http://es.wikipedia.org/wiki/Diagrama_de_clases Diagrama de Clases].
+
:'''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. No exemplo o moveToNext() xa vai no bucle, pero poderíamos poñer como condición while(!cursor.isAferLast()) e ter o cursor.moveNext() dentro do bucle.
  
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.
+
: 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.
 +
<br/>
 +
<br/>
 +
<br/>
  
 
----
 
----
 +
:<u>'''Aclaración'''</u>: 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 [http://developer.android.com/reference/android/widget/SimpleCursorAdapter.html SimpleCursorAdapter]] e asociar dito Adaptador a un elemento gráfico como unha lista.
  
 +
:Por exemplo:
 +
:<syntaxhighlight lang="java" line enclose="div" highlight="" >
 +
SimpleCursorAdapter mAdapter = new SimpleCursorAdapter(this,R.layout.list_layout_creado, cursor, new String[] { "nome" },new int[] { android.R.id.text1});
 +
</syntaxhighlight>
 +
 +
: 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 [http://wiki.cifprodolfoucha.es/index.php?title=Spinner_a_trav%C3%A9s_de_adaptador na Unidade 4 desta Wiki].
 +
 +
----
  
Imos velo cun <u>exemplo concreto</u>:
 
 
* TÁBOA AULAS: (_id, nome)
 
  
* Definimos a clase que vai gardar esta información: '''Aulas'''
+
<br/>
: Creamos esta clase dentro do paquete 'BaseDatos' (nas prácticas teredes que crear un paquete cun nome concreto)
 
  
<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
===Onde facer as operacións===
public class Aulas {
 
  
private long _id;
+
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).
private String nome;
 
 
public Aulas (long id, String nome){
 
this._id=id;
 
this.nome=nome;
 
}
 
 
public long get_id() {
 
return _id;
 
}
 
public void set_id(long _id) {
 
this._id = _id;
 
}
 
public String getNome() {
 
return nome;
 
}
 
public void setNome(String nome) {
 
this.nome = nome;
 
}
 
 
public String toString(){
 
return nome;
 
}
 
}
 
</syntaxhighlight>
 
 
<u>'''NOTA IMPORTANTE:'''</u> 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().
 
  
<u>NOTA:</u>: 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.
+
Poderíamos facelo na activity a partires de obter o obxecto sqlLiteDB, pero desta forma quedará moito máis claro.
  
* Agora que temos definida a clase podemos definir os métodos que imos necesitar.
 
  
<u>Nota:</u> 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).
+
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.
  
:Por exemplo:
+
<syntaxhighlight lang="java" enclose="div" highlight="" >
 +
public class UD6_03_BaseDatos extends SQLiteOpenHelper{
  
*Método: engadirAula(Aulas aula_engadir).  
+
private SQLiteDatabase sqlLiteDB;
:: Engade unha aula á base de datos.  
+
        ...............
:: Leva como parámetro a aula a engadir.
 
<syntaxhighlight lang="java" line enclose="div" highlight="" >
 
public long engadirAula(Aulas aula_engadir){
 
ContentValues valores = new ContentValues();
 
valores.put("nome", aula_engadir.getNome());
 
long id = sqlLiteDB.insert("AULAS",null,valores);
 
 
return id;
 
}
 
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
: 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).
 
:<u>Nota:</u>Poderíamos devolver un obxecto da clase Aula co novo id xa posto.
 
  
 +
* Creamos un método '''abrirBD()''' y '''pecharBD()''':
  
*Método: ArrayList<Aulas> obterAulas().
+
<syntaxhighlight lang="java" line enclose="div" highlight="" >
:: Devolve as aulas da táboa AULAS.
+
public class UD6_03_BaseDatos extends SQLiteOpenHelper{
:: A forma de devolver moitos obxectos dunha clase é utilizando un [http://developer.android.com/reference/java/util/ArrayList.html ArrayList].
+
    public final static String NOME_BD="UD05_03_BD_FILE.db";
<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
    public final static int VERSION_BD=1;
        private final String CONSULTAR_AULAS ="SELECT _id,nome FROM AULAS order by nome";
+
    private static UD05_03_BaseDatos sInstance;
  
public ArrayList<Aulas> obterAulas() {
+
    private SQLiteDatabase dbsqlLiteDB;
ArrayList<Aulas> aulas_devolver = new ArrayList<Aulas>();
 
  
Cursor datosConsulta = sqlLiteDB.rawQuery(CONSULTAR_AULAS, null);
+
    public static synchronized UD05_03_BaseDatos getInstance(Context context) {
if (datosConsulta.moveToFirst()) {
+
        // Use the application context, which will ensure that you
Aulas aula;
+
        // don't accidentally leak an Activity's context.
while (!datosConsulta.isAfterLast()) {
+
        // See this article for more information: http://bit.ly/6LRzfx
aula = new Aulas(datosConsulta.getLong(0),
+
        if (sInstance == null) {
datosConsulta.getString(1));
+
            sInstance = new UD05_03_BaseDatos(context.getApplicationContext());
aulas_devolver.add(aula);
+
        }
datosConsulta.moveToNext();
+
        return sInstance;
}
+
    }
}
+
        ...............
return aulas_devolver;
+
    public void abrirBD(){
}
+
        if (sqlLiteDB==null || !sqlLiteDB.isOpen()){
</syntaxhighlight>
+
          sqlLiteDB = sInstance.getWritableDatabase();
 +
        }
 +
    }
  
==Caso práctico==
+
    public void pecharBD(){
 +
        if (sqlLiteDB!=null || sqlLiteDB.isOpen()){
 +
          sqlLiteDB.close();
 +
        }
 +
    }
 +
</syntaxhighlight>
  
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:
+
* Agora definimos os método que necesitemos para dar soporte á nosa aplicación.
* '''_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.
+
===Xuntando todo cos Modelos===
  
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.
+
Normalmente os datos dunha base de datos os imos 'modelizar' en forma de clases.
  
Así, por exemplo, cando damos de alta unha aula, non se comproba que tiñamos escrito algo na caixa de texto.
+
Así, se se ten unha táboa AULAS poderase modelizar utilizando unha clase AULAS que teña como propiedades as columnas da táboa.
  
<gallery caption="Operacións sobre unha base de datos" widths="320" heights="300px" perrow="2">
+
----
Image:PDM_Avanzada_DatosPersistentes_31.jpg| Aspecto da aplicación ao iniciarse.
+
Non pretendemos entrar a explicar o [http://es.wikipedia.org/wiki/Lenguaje_unificado_de_modelado UML] e o [http://es.wikipedia.org/wiki/Diagrama_de_clases Diagrama de Clases].
Image:PDM_Avanzada_DatosPersistentes_32.jpg| Exemplo de aula modificada.
 
Image:PDM_Avanzada_DatosPersistentes_33.jpg| Exemplo de aula dada de baixa.
 
</gallery>
 
  
===Preparación===
+
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.
  
* 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===
+
Imos velo cun <u>exemplo concreto</u>:
  
'''Nome da clase:''' UD06_03_BaseDatos
+
* TÁBOA AULAS: (_id, nome)
  
'''Código da clase que xestiona a base de datos.'''
+
* Definimos a clase que vai gardar esta información: '''Aulas'''
<syntaxhighlight lang="java" line enclose="div" highlight="15,86-88,90-92,36-42,44-51,53-63,65-79" >
+
: Creamos esta clase dentro do paquete 'BaseDatos' (nas prácticas teredes que crear un paquete cun nome concreto)
package es.cursoandroid.cifprodolfoucha.aprendiendo.Persistencia.BasesDatos;
 
  
import android.content.ContentValues;
+
<syntaxhighlight lang="java" line enclose="div" highlight="" >
import android.content.Context;
+
public class Aulas {
import android.database.Cursor;
 
import android.database.sqlite.SQLiteDatabase;
 
import android.database.sqlite.SQLiteOpenHelper;
 
  
import java.util.ArrayList;
+
private long _id;
 
+
private String nome;
public class UD06_03_BaseDatos extends SQLiteOpenHelper{
+
    public final static String NOME_BD="UD06_03_BD_FILE.db";
+
public Aulas (long id, String nome){
    public final static int VERSION_BD=1;
+
this._id=id;
    private static UD06_03_BaseDatos sInstance;
+
this.nome=nome;
    private SQLiteDatabase sqlLiteDB;
+
}
 
+
    /* SENTENZAS SQL */
+
public long get_id() {
    private final String TABOA_AULAS="AULAS";
+
return _id;
    private final String CONSULTAR_AULAS ="SELECT _id,nome FROM AULAS order by nome";
+
}
 
+
public void set_id(long _id) {
 
+
this._id = _id;
    public static synchronized UD06_03_BaseDatos getInstance(Context context) {
+
}
        // Use the application context, which will ensure that you
+
public String getNome() {
        // don't accidentally leak an Activity's context.
+
return nome;
        // See this article for more information: http://bit.ly/6LRzfx
+
}
        if (sInstance == null) {
+
public void setNome(String nome) {
            sInstance = new UD06_03_BaseDatos(context.getApplicationContext());
+
this.nome = nome;
        }
+
}
        return sInstance;
+
    }
+
public String toString(){
 +
return nome;
 +
}
 +
}
 +
</syntaxhighlight>
 +
 +
<u>'''NOTA IMPORTANTE:'''</u> 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().
  
    private String CREAR_TABOA_AULAS ="CREATE TABLE AULAS ( " +
+
<u>NOTA:</u>: 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.
            "_id  INTEGER PRIMARY KEY AUTOINCREMENT," +
 
            "nome VARCHAR( 50 )  NOT NULL)";
 
  
    public long engadirAula(Aulas aula_engadir){
+
* Agora que temos definida a clase podemos definir os métodos que imos necesitar.
        ContentValues valores = new ContentValues();
 
        valores.put("nome", aula_engadir.getNome());
 
        long id = sqlLiteDB.insert("AULAS",null,valores);
 
  
        return id;
+
<u>Nota:</u> 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:
  
    public int borrarAula(Aulas aula){
+
*Método: engadirAula(Aulas aula_engadir).
        String condicionwhere = "_id=?";
+
:: Engade unha aula á base de datos.
        String[] parametros = new String[]{String.valueOf(aula.get_id())};
+
:: Leva como parámetro a aula a engadir.
        int rexistrosafectados = sqlLiteDB.delete(TABOA_AULAS,condicionwhere,parametros);
+
<syntaxhighlight lang="java" line enclose="div" highlight="" >
 +
public long engadirAula(Aulas aula_engadir){
 +
ContentValues valores = new ContentValues();
 +
valores.put("nome", aula_engadir.getNome());
 +
long id = sqlLiteDB.insert("AULAS",null,valores);
 +
 +
return id;
 +
}
 +
</syntaxhighlight>
  
        return rexistrosafectados;
+
: 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).
 +
:<u>Nota:</u>Poderíamos devolver un obxecto da clase Aula co novo id xa posto.
  
    }
 
  
    public int modificarAula(Aulas aula_modificar){
+
*Método: ArrayList<Aulas> obterAulas().
        ContentValues datos = new ContentValues();
+
:: Devolve as aulas da táboa AULAS.
         datos.put("nome", aula_modificar.getNome());
+
:: A forma de devolver moitos obxectos dunha clase é utilizando un [http://developer.android.com/reference/java/util/ArrayList.html ArrayList].
 +
<syntaxhighlight lang="java" line enclose="div" highlight="" >
 +
         private final String CONSULTAR_AULAS ="SELECT _id,nome FROM AULAS order by nome";
  
        String where = "_id=?";
+
public ArrayList<Aulas> obterAulas() {
        String[] params = new String[]{String.valueOf(aula_modificar.get_id())};
+
ArrayList<Aulas> aulas_devolver = new ArrayList<Aulas>();
  
        int rexistrosModificados = sqlLiteDB.update(TABOA_AULAS, datos, where, params);
+
Cursor datosConsulta = sqlLiteDB.rawQuery(CONSULTAR_AULAS, null);
 +
if (datosConsulta.moveToFirst()) {
 +
Aulas aula;
 +
while (!datosConsulta.isAfterLast()) {
 +
aula = new Aulas(datosConsulta.getLong(0),
 +
datosConsulta.getString(1));
 +
aulas_devolver.add(aula);
 +
datosConsulta.moveToNext();
 +
}
 +
}
 +
return aulas_devolver;
 +
}
 +
</syntaxhighlight>
  
        return rexistrosModificados;
+
==Caso práctico==
    }
 
  
    public ArrayList<Aulas> obterAulas() {
+
Neste exemplo imos copiar unha base de datos dende /assets/ ao cartafol .../databases/.
        ArrayList<Aulas> aulas_devolver = new ArrayList<Aulas>();
 
  
        Cursor datosConsulta = sqlLiteDB.rawQuery(CONSULTAR_AULAS, null);
+
Dita BD consta dunha única táboa de nome '''AULAS''' cos seguintes campos:
        if (datosConsulta.moveToFirst()) {
+
* '''_id''': clave autonumérica.
            Aulas aula;
+
* '''nome''': nome da aula.
            while (!datosConsulta.isAfterLast()) {
 
                aula = new Aulas(datosConsulta.getLong(0),
 
                        datosConsulta.getString(1));
 
                aulas_devolver.add(aula);
 
                datosConsulta.moveToNext();
 
            }
 
        }
 
        return aulas_devolver;
 
    }
 
  
    public UD06_03_BaseDatos(Context context) {
+
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.
        super(context, NOME_BD, null, VERSION_BD);
 
        // TODO Auto-generated constructor stub
 
    }
 
  
    public void abrirBD(){
+
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.
        sqlLiteDB = sInstance.getWritableDatabase();
 
    }
 
  
    public void pecharBD(){
+
Así, por exemplo, cando damos de alta unha aula, non se comproba que tiñamos escrito algo na caixa de texto.
        sqlLiteDB.close();
 
    }
 
  
    @Override
+
<gallery caption="Operacións sobre unha base de datos" widths="320" heights="300px" perrow="2">
    public void onCreate(SQLiteDatabase db) {
+
Image:PDM_Avanzada_DatosPersistentes_31.jpg| Aspecto da aplicación ao iniciarse.
        // TODO Auto-generated method stub
+
Image:PDM_Avanzada_DatosPersistentes_32.jpg| Exemplo de aula modificada.
      // db.execSQL(CREAR_TABOA_AULAS);
+
Image:PDM_Avanzada_DatosPersistentes_33.jpg| Exemplo de aula dada de baixa.
 +
</gallery>
  
    }
+
===Preparación===
  
    @Override
+
* Deberemos de ter no cartafol /assets/ a base de datos. Descomprimir o arquivo e copiar a base de datos a dito cartafol:
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
 
        // TODO Auto-generated method stub
 
  
        //db.execSQL("DROP TABLE IF EXISTS AULAS");
+
[[Media:EX_03_BD_FILE.zip]]
        //onCreate(db);
 
    }
 
  
}
+
===Creamos a clase que xestiona a base de datos===
</syntaxhighlight>
 
  
 +
'''Nome da clase:''' UD05_03_BaseDatos
  
 +
'''Código da clase que xestiona a base de datos.'''
 +
<syntaxhighlight lang="java" line enclose="div" highlight="15,86-90,92-96,36-42,44-51,53-63,65-76" >
 +
package es.cursoandroid.cifprodolfoucha.aprendiendo.Persistencia.BasesDatos;
  
 +
import android.content.ContentValues;
 +
import android.content.Context;
 +
import android.database.Cursor;
 +
import android.database.sqlite.SQLiteDatabase;
 +
import android.database.sqlite.SQLiteOpenHelper;
  
 +
import java.util.ArrayList;
  
<br />
+
public class UD05_03_BaseDatos extends SQLiteOpenHelper{
===Creamos a activity===
+
    public final static String NOME_BD="EX_03_BD_FILE.db";
 +
    public final static int VERSION_BD=1;
 +
    private static UD05_03_BaseDatos sInstance;
 +
    private SQLiteDatabase sqlLiteDB;
  
* 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.
+
    /* SENTENZAS SQL */
: Modifica o arquivo '''AndroidManifiest.xml''' e engade unha label á activity como [http://wiki.cifprodolfoucha.es/index.php?title=PDM_Creando_proxecto_base xa vimos na creación do proxecto base].
+
    private final String TABOA_AULAS="AULAS";
 +
    private final String CONSULTAR_AULAS ="SELECT _id,nome FROM AULAS order by nome";
  
  
 +
    public static synchronized UD05_03_BaseDatos getInstance(Context context) {
 +
        // Use the application context, which will ensure that you
 +
        // don't accidentally leak an Activity's context.
 +
        // See this article for more information: http://bit.ly/6LRzfx
 +
        if (sInstance == null) {
 +
            sInstance = new UD05_03_BaseDatos(context.getApplicationContext());
 +
        }
 +
        return sInstance;
 +
    }
  
'''Código do layout xml'''
+
     private String CREAR_TABOA_AULAS ="CREATE TABLE AULAS ( " +
<syntaxhighlight lang="xml" line enclose="div" highlight="" >
+
            "_id  INTEGER PRIMARY KEY AUTOINCREMENT," +
<?xml version="1.0" encoding="utf-8"?>
+
            "nome VARCHAR( 50 )  NOT NULL)";
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 
    xmlns:app="http://schemas.android.com/apk/res-auto"
 
     xmlns:tools="http://schemas.android.com/tools"
 
    android:layout_width="match_parent"
 
    android:layout_height="match_parent"
 
    tools:context=".Persistencia.BasesDatos.UD06_03_DatosPersistentes_BD">
 
  
     <ListView
+
     public long engadirAula(Aulas aula_engadir){
         android:id="@+id/lstAulas_UD06_03_BaseDatos"
+
         ContentValues valores = new ContentValues();
        android:layout_width="0dp"
+
         valores.put("nome", aula_engadir.getNome());
         android:layout_height="0dp"
+
         long id = sqlLiteDB.insert("AULAS",null,valores);
         android:layout_marginBottom="8dp"
 
        android:layout_marginEnd="8dp"
 
        android:layout_marginStart="8dp"
 
        android:layout_marginTop="8dp"
 
        app:layout_constraintBottom_toTopOf="@+id/guideline3"
 
        app:layout_constraintEnd_toEndOf="parent"
 
        app:layout_constraintStart_toStartOf="parent"
 
        app:layout_constraintTop_toTopOf="parent" />
 
  
    <android.support.constraint.Guideline
+
         return id;
         android:id="@+id/guideline3"
+
    }
        android:layout_width="wrap_content"
 
        android:layout_height="wrap_content"
 
        android:orientation="horizontal"
 
        app:layout_constraintGuide_percent=".45" />
 
  
     <EditText
+
     public int borrarAula(Aulas aula){
        android:id="@+id/etAula_UD06_03_BaseDatos"
+
         String condicionwhere = "_id=?";
        android:layout_width="0dp"
+
         String[] parametros = new String[]{String.valueOf(aula.get_id())};
        android:layout_height="wrap_content"
+
         int rexistrosafectados = sqlLiteDB.delete(TABOA_AULAS,condicionwhere,parametros);
        android:layout_marginEnd="8dp"
+
 
        android:layout_marginStart="8dp"
+
         return rexistrosafectados;
         android:layout_marginTop="8dp"
 
        android:ems="10"
 
         android:hint="Introduce un aula"
 
         android:inputType="textCapCharacters"
 
        app:layout_constraintEnd_toEndOf="parent"
 
         app:layout_constraintStart_toStartOf="parent"
 
        app:layout_constraintTop_toTopOf="@+id/guideline3" />
 
  
     <Button
+
     }
        android:id="@+id/btnAlta_UD06_03_BaseDatos"
 
        android:layout_width="100sp"
 
        android:layout_height="wrap_content"
 
        android:layout_marginBottom="8dp"
 
        android:layout_marginStart="8dp"
 
        android:text="Alta"
 
        app:layout_constraintBottom_toBottomOf="parent"
 
        app:layout_constraintStart_toStartOf="parent" />
 
  
     <Button
+
     public int modificarAula(Aulas aula_modificar){
         android:id="@+id/btnModificar_UD06_03_BaseDatos"
+
         ContentValues datos = new ContentValues();
        android:layout_width="110sp"
+
         datos.put("nome", aula_modificar.getNome());
        android:layout_height="wrap_content"
 
        android:layout_marginBottom="8dp"
 
         android:layout_marginEnd="8dp"
 
        android:layout_marginStart="8dp"
 
        android:text="Modificar"
 
        app:layout_constraintBottom_toBottomOf="parent"
 
        app:layout_constraintEnd_toStartOf="@+id/btnBaixa_UD06_03_BaseDatos"
 
        app:layout_constraintStart_toEndOf="@+id/btnAlta_UD06_03_BaseDatos" />
 
  
    <Button
+
         String where = "_id=?";
        android:id="@+id/btnBaixa_UD06_03_BaseDatos"
+
         String[] params = new String[]{String.valueOf(aula_modificar.get_id())};
        android:layout_width="100dp"
 
         android:layout_height="wrap_content"
 
        android:layout_marginBottom="8dp"
 
         android:layout_marginEnd="8dp"
 
        android:text="Baixa"
 
        app:layout_constraintBottom_toBottomOf="parent"
 
        app:layout_constraintEnd_toEndOf="parent" />
 
</android.support.constraint.ConstraintLayout>
 
</syntaxhighlight>
 
  
 +
        int rexistrosModificados = sqlLiteDB.update(TABOA_AULAS, datos, where, params);
  
 +
        return rexistrosModificados;
 +
    }
  
<br />
+
    public ArrayList<Aulas> obterAulas() {
'''Código da clase UD6_03_DatosPersistentes_BD'''<br/>
+
        ArrayList<Aulas> aulas_devolver = new ArrayList<Aulas>();
'''Obxectivo:''' Realizar operacións contra unha base de datos.
 
<syntaxhighlight lang="java" line enclose="div" highlight="66-85,87-102,104-120,125-138,144-153" >
 
package es.cursoandroid.cifprodolfoucha.aprendiendo.Persistencia.BasesDatos;
 
  
import android.app.Activity;
+
        Cursor datosConsulta = sqlLiteDB.rawQuery(CONSULTAR_AULAS, null);
import android.os.Bundle;
+
        Aulas aula;
import android.view.View;
+
        while (datosConsulta.moveToNext()) {
import android.widget.AdapterView;
+
          aula = new Aulas(datosConsulta.getLong(0),
import android.widget.ArrayAdapter;
+
                            datosConsulta.getString(1));
import android.widget.Button;
+
          aulas_devolver.add(aula);
import android.widget.EditText;
+
        }
import android.widget.ListView;
+
        return aulas_devolver;
import android.widget.Toast;
+
    }
  
import java.io.File;
+
    public UD05_03_BaseDatos(Context context) {
import java.io.FileOutputStream;
+
        super(context, NOME_BD, null, VERSION_BD);
import java.io.IOException;
+
        // TODO Auto-generated constructor stub
import java.io.InputStream;
+
    }
import java.io.OutputStream;
 
import java.util.ArrayList;
 
  
import es.cursoandroid.cifprodolfoucha.aprendiendo.R;
+
    public void abrirBD(){
 +
        if (sqlLiteDB==null || !sqlLiteDB.isOpen()){
 +
          sqlLiteDB = sInstance.getWritableDatabase();
 +
        }
 +
    }
  
public class UD06_03_DatosPersistentes_BD extends Activity {
+
    public void pecharBD(){
    private UD06_03_BaseDatos baseDatos;
+
         if (sqlLiteDB!=null && sqlLiteDB.isOpen()){
    private Aulas aula_seleccionada=null;  // Garda a aula seleccionada da lista
+
          sqlLiteDB.close();
 
 
    private void copiarBD() {
 
         String bddestino = "/data/data/" + getPackageName() + "/databases/"
 
                + UD06_03_BaseDatos.NOME_BD;
 
        File file = new File(bddestino);
 
        if (file.exists()) {
 
            Toast.makeText(getApplicationContext(), "A BD NON SE VAI COPIAR. XA EXISTE", Toast.LENGTH_LONG).show();
 
            return; // XA EXISTE A BASE DE DATOS
 
 
         }
 
         }
 +
    }
  
         String pathbd = "/data/data/" + getPackageName()
+
    @Override
                + "/databases/";
+
    public void onCreate(SQLiteDatabase db) {
        File filepathdb = new File(pathbd);
+
         // TODO Auto-generated method stub
        filepathdb.mkdirs();
+
      // db.execSQL(CREAR_TABOA_AULAS);
  
        InputStream inputstream;
+
    }
        try {
 
            inputstream = getAssets().open(UD06_03_BaseDatos.NOME_BD);
 
            OutputStream outputstream = new FileOutputStream(bddestino);
 
  
            int tamread;
+
    @Override
            byte[] buffer = new byte[2048];
+
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
 +
        // TODO Auto-generated method stub
  
            while ((tamread = inputstream.read(buffer)) > 0) {
+
        //db.execSQL("DROP TABLE IF EXISTS AULAS");
                outputstream.write(buffer, 0, tamread);
+
        //onCreate(db);
            }
+
    }
  
            inputstream.close();
+
}
            outputstream.flush();
+
</syntaxhighlight>
            outputstream.close();
 
            Toast.makeText(getApplicationContext(), "BASE DE DATOS COPIADA", Toast.LENGTH_LONG).show();
 
        } catch (IOException e) {
 
            // TODO Auto-generated catch block
 
            e.printStackTrace();
 
        }
 
  
    }
 
  
    private void xestionarEventos(){
 
  
        Button btnAltaAula = (Button)findViewById(R.id.btnAlta_UD06_03_BaseDatos);
 
        btnAltaAula.setOnClickListener(new View.OnClickListener() {
 
  
            @Override
 
            public void onClick(View v) {
 
                // TODO Auto-generated method stub
 
                EditText editAula = (EditText) findViewById(R.id.etAula_UD06_03_BaseDatos);
 
                // Habería que comprobar se hai algún dato na caixa de texto, pero neste exemplo centrámonos no funcionamento da base de datos.
 
  
                Aulas aula = new Aulas(0, editAula.getText().toString());
+
<br />
                long id = baseDatos.engadirAula(aula); // Obtemos o id. Neste exemplo non faremos nada con el.
 
                aula.set_id(id);
 
  
                // Poderíamos engadir a nova aula ó adaptador pero neste exemplo
+
===Creamos a activity===
                // recargamos a lista
 
                cargarLista();
 
                editAula.setText("");
 
  
                Toast.makeText(getApplicationContext(), "AULA ENGADIDA",Toast.LENGTH_LONG).show();
+
* Dentro do paquete '''BasesDatos''' (creado previamente no paso anterior) crear unha nova 'Empty Activity' de nome: '''UD05_03_DatosPersistentes_BD''' de tipo Launcher e sen compatibilidade.
            }
+
: Modifica o arquivo '''AndroidManifiest.xml''' e engade unha label á activity como [http://wiki.cifprodolfoucha.es/index.php?title=PDM_Creando_proxecto_base xa vimos na creación do proxecto base].
        });
 
  
        Button btnBaixaAula = (Button)findViewById(R.id.btnBaixa_UD06_03_BaseDatos);
 
        btnBaixaAula.setOnClickListener(new View.OnClickListener() {
 
  
            @Override
 
            public void onClick(View v) {
 
                // TODO Auto-generated method stub
 
  
                baseDatos.borrarAula(aula_seleccionada);
+
'''Código do layout xml'''
                EditText editAula = (EditText)findViewById(R.id.etAula_UD06_03_BaseDatos);
+
<syntaxhighlight lang="xml" line enclose="div" highlight="" >
                editAula.setText("");
+
<?xml version="1.0" encoding="utf-8"?>
 +
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 +
    xmlns:app="http://schemas.android.com/apk/res-auto"
 +
    xmlns:tools="http://schemas.android.com/tools"
 +
    android:layout_width="match_parent"
 +
    android:layout_height="match_parent"
 +
    tools:context=".Persistencia.BasesDatos.UD05_03_DatosPersistentes_BD">
  
                // Poderíamos borrar a aula do adaptador e non cargar a lista novamente
+
    <ListView
                cargarLista();
+
        android:id="@+id/lstAulas_UD05_03_BaseDatos"
                Toast.makeText(getApplicationContext(), "AULA BORRADA",Toast.LENGTH_LONG).show();
+
        android:layout_width="0dp"
            }
+
        android:layout_height="0dp"
         });
+
        android:layout_marginBottom="8dp"
 +
        android:layout_marginEnd="8dp"
 +
        android:layout_marginStart="8dp"
 +
        android:layout_marginTop="8dp"
 +
        app:layout_constraintBottom_toTopOf="@+id/guideline3"
 +
        app:layout_constraintEnd_toEndOf="parent"
 +
        app:layout_constraintStart_toStartOf="parent"
 +
         app:layout_constraintTop_toTopOf="parent" />
  
         Button btnModificaraAula = (Button)findViewById(R.id.btnModificar_UD06_03_BaseDatos);
+
    <android.support.constraint.Guideline
         btnModificaraAula.setOnClickListener(new View.OnClickListener() {
+
         android:id="@+id/guideline3"
 
+
         android:layout_width="wrap_content"
            @Override
+
        android:layout_height="wrap_content"
            public void onClick(View v) {
+
        android:orientation="horizontal"
                // TODO Auto-generated method stub
+
        app:layout_constraintGuide_percent=".45" />
  
                EditText editAula = (EditText)findViewById(R.id.etAula_UD06_03_BaseDatos);
+
    <EditText
 +
        android:id="@+id/etAula_UD05_03_BaseDatos"
 +
        android:layout_width="0dp"
 +
        android:layout_height="wrap_content"
 +
        android:layout_marginEnd="8dp"
 +
        android:layout_marginStart="8dp"
 +
        android:layout_marginTop="8dp"
 +
        android:ems="10"
 +
        android:hint="Introduce un aula"
 +
        android:inputType="textCapCharacters"
 +
        app:layout_constraintEnd_toEndOf="parent"
 +
        app:layout_constraintStart_toStartOf="parent"
 +
        app:layout_constraintTop_toTopOf="@+id/guideline3" />
  
                Aulas aula_modificar = new Aulas(aula_seleccionada.get_id(),editAula.getText().toString());
+
    <Button
                baseDatos.modificarAula(aula_modificar);
+
        android:id="@+id/btnAlta_UD05_03_BaseDatos"
 
+
        android:layout_width="100sp"
                // Poderíamos borrar a aula do adaptador e non cargar a lista novamente
+
        android:layout_height="wrap_content"
                cargarLista();
+
        android:layout_marginBottom="8dp"
                Toast.makeText(getApplicationContext(), "AULA MODIFICADA",Toast.LENGTH_LONG).show();
+
        android:layout_marginStart="8dp"
            }
+
        android:text="Alta"
         });
+
        app:layout_constraintBottom_toBottomOf="parent"
 +
         app:layout_constraintStart_toStartOf="parent" />
  
 +
    <Button
 +
        android:id="@+id/btnModificar_UD05_03_BaseDatos"
 +
        android:layout_width="110sp"
 +
        android:layout_height="wrap_content"
 +
        android:layout_marginBottom="8dp"
 +
        android:layout_marginEnd="8dp"
 +
        android:layout_marginStart="8dp"
 +
        android:text="Modificar"
 +
        app:layout_constraintBottom_toBottomOf="parent"
 +
        app:layout_constraintEnd_toStartOf="@+id/btnBaixa_UD05_03_BaseDatos"
 +
        app:layout_constraintStart_toEndOf="@+id/btnAlta_UD05_03_BaseDatos" />
  
 +
    <Button
 +
        android:id="@+id/btnBaixa_UD05_03_BaseDatos"
 +
        android:layout_width="100dp"
 +
        android:layout_height="wrap_content"
 +
        android:layout_marginBottom="8dp"
 +
        android:layout_marginEnd="8dp"
 +
        android:text="Baixa"
 +
        app:layout_constraintBottom_toBottomOf="parent"
 +
        app:layout_constraintEnd_toEndOf="parent" />
 +
</android.support.constraint.ConstraintLayout>
 +
</syntaxhighlight>
  
        ListView lista = (ListView)findViewById(R.id.lstAulas_UD06_03_BaseDatos);
 
        lista.setOnItemClickListener(new AdapterView.OnItemClickListener() {
 
  
            @Override
 
            public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
 
                                    long arg3) {
 
                // TODO Auto-generated method stub
 
                // Obtemos o obxecto aula seleccionado
 
                ArrayAdapter<Aulas> adaptador = (ArrayAdapter<Aulas>) arg0.getAdapter();
 
                aula_seleccionada = adaptador.getItem(arg2);
 
                EditText editAula = (EditText)findViewById(R.id.etAula_UD06_03_BaseDatos);
 
                editAula.setText(aula_seleccionada.getNome());
 
  
            }
+
<br />
        });
+
'''Código da clase UD6_03_DatosPersistentes_BD'''<br/>
    }
+
'''Obxectivo:''' Realizar operacións contra unha base de datos.
 +
<syntaxhighlight lang="java" line  enclose="div"  highlight="66-85,87-102,104-120,125-138,144-153" >
 +
package es.cursoandroid.cifprodolfoucha.aprendiendo.Persistencia.BasesDatos;
  
    /**
+
import android.app.Activity;
    * Accede á base de datos para obter as aulas e as asina á lista.
+
import android.os.Bundle;
    */
+
import android.view.View;
    private void cargarLista(){
+
import android.widget.AdapterView;
 
+
import android.widget.ArrayAdapter;
        ListView lista = (ListView)findViewById(R.id.lstAulas_UD06_03_BaseDatos);
+
import android.widget.Button;
 +
import android.widget.EditText;
 +
import android.widget.ListView;
 +
import android.widget.Toast;
  
        ArrayList<Aulas> aulas = baseDatos.obterAulas();
+
import java.io.File;
        ArrayAdapter<Aulas> adaptador = new ArrayAdapter<Aulas>(getApplicationContext(),
+
import java.io.FileOutputStream;
                android.R.layout.simple_list_item_1,aulas);
+
import java.io.IOException;
        lista.setAdapter(adaptador);
+
import java.io.InputStream;
 +
import java.io.OutputStream;
 +
import java.util.ArrayList;
  
    }
+
import es.cursoandroid.cifprodolfoucha.aprendiendo.R;
  
 +
public class UD05_03_DatosPersistentes_BD extends Activity {
 +
    private UD05_03_BaseDatos baseDatos;
 +
    private Aulas aula_seleccionada=null;  // Garda a aula seleccionada da lista
  
     @Override
+
     private void copiarBD() {
    public void onStart(){
+
         String bddestino = "/data/data/" + getPackageName() + "/databases/"
         super.onStart();
+
                + UD05_03_BaseDatos.NOME_BD;
 
+
        File file = new File(bddestino);
         if (baseDatos==null) {   // Abrimos a base de datos para escritura
+
         if (file.exists()) {
             baseDatos = UD06_03_BaseDatos.getInstance(getApplicationContext());
+
             Toast.makeText(getApplicationContext(), "A BD NON SE VAI COPIAR. XA EXISTE", Toast.LENGTH_LONG).show();
            baseDatos.abrirBD();
+
             return; // XA EXISTE A BASE DE DATOS
 
 
             cargarLista();
 
 
         }
 
         }
    }
 
  
    @Override
+
        String pathbd = "/data/data/" + getPackageName()
    public void onStop(){
+
                + "/databases/";
         super.onStop();
+
        File filepathdb = new File(pathbd);
 +
         filepathdb.mkdirs();
  
         if (baseDatos!=null){   // Pechamos a base de datos.
+
         InputStream inputstream;
             baseDatos.pecharBD();
+
        try {
             baseDatos=null;
+
             inputstream = getAssets().open(UD05_03_BaseDatos.NOME_BD);
        }
+
             OutputStream outputstream = new FileOutputStream(bddestino);
  
    }
+
            int tamread;
 +
            byte[] buffer = new byte[2048];
  
    @Override
+
            while ((tamread = inputstream.read(buffer)) > 0) {
    protected void onCreate(Bundle savedInstanceState) {
+
                outputstream.write(buffer, 0, tamread);
        super.onCreate(savedInstanceState);
+
            }
         setContentView(R.layout.activity_ud06_03__datos_persistentes__bd);
+
 
 +
            inputstream.close();
 +
            outputstream.flush();
 +
            outputstream.close();
 +
            Toast.makeText(getApplicationContext(), "BASE DE DATOS COPIADA", Toast.LENGTH_LONG).show();
 +
         } catch (IOException e) {
 +
            // TODO Auto-generated catch block
 +
            e.printStackTrace();
 +
        }
  
        copiarBD();
 
        xestionarEventos();
 
 
     }
 
     }
}
 
  
</syntaxhighlight>
+
    private void xestionarEventos(){
  
* Liñas 66-85: Xestionamos o evento Click sobre o botón de alta.
+
        Button btnAltaAula = (Button)findViewById(R.id.btnAlta_UD05_03_BaseDatos);
:* 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.
+
        btnAltaAula.setOnClickListener(new View.OnClickListener() {
:* 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.
+
            @Override
:* 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.
+
            public void onClick(View v) {
:* Liña 99: Chamamos a o método que volve chamar á base de datos para cargar as aulas novamente.
+
                // TODO Auto-generated method stub
 +
                EditText editAula = (EditText) findViewById(R.id.etAula_UD05_03_BaseDatos);
 +
                // Habería que comprobar se hai algún dato na caixa de texto, pero neste exemplo centrámonos no funcionamento da base de datos.
  
* Liñas 104-120: Xestionamos o evento Click sobre o botón de modificar.
+
                Aulas aula = new Aulas(0, editAula.getText().toString());
:* 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.
+
                long id = baseDatos.engadirAula(aula); // Obtemos o id. Neste exemplo non faremos nada con el.
:* Liña 114: Chamamos á base de datos para modificar o nome da aula seleccionada.
+
                aula.set_id(id);
:* 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.
+
                // Poderíamos engadir a nova aula ó adaptador pero neste exemplo
:* Liña 133: Gardamos na propiedade 'aula_seleccionada' a aula seleccionada.
+
                // recargamos a lista
:* Liña 135: Cambiamos o texto do EditText polo seleccionado.
+
                cargarLista();
 
+
                editAula.setText("");
* 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.
 
  
 +
                Toast.makeText(getApplicationContext(), "AULA ENGADIDA",Toast.LENGTH_LONG).show();
 +
            }
 +
        });
  
 +
        Button btnBaixaAula = (Button)findViewById(R.id.btnBaixa_UD05_03_BaseDatos);
 +
        btnBaixaAula.setOnClickListener(new View.OnClickListener() {
  
 +
            @Override
 +
            public void onClick(View v) {
 +
                // TODO Auto-generated method stub
 +
 +
                baseDatos.borrarAula(aula_seleccionada);
 +
                EditText editAula = (EditText)findViewById(R.id.etAula_UD05_03_BaseDatos);
 +
                editAula.setText("");
 +
 +
                // Poderíamos borrar a aula do adaptador e non cargar a lista novamente
 +
                cargarLista();
 +
                Toast.makeText(getApplicationContext(), "AULA BORRADA",Toast.LENGTH_LONG).show();
 +
            }
 +
        });
 +
 +
        Button btnModificaraAula = (Button)findViewById(R.id.btnModificar_UD05_03_BaseDatos);
 +
        btnModificaraAula.setOnClickListener(new View.OnClickListener() {
  
 +
            @Override
 +
            public void onClick(View v) {
 +
                // TODO Auto-generated method stub
  
 +
                EditText editAula = (EditText)findViewById(R.id.etAula_UD05_03_BaseDatos);
  
 +
                Aulas aula_modificar = new Aulas(aula_seleccionada.get_id(),editAula.getText().toString());
 +
                baseDatos.modificarAula(aula_modificar);
  
 +
                // Poderíamos borrar a aula do adaptador e non cargar a lista novamente
 +
                cargarLista();
 +
                Toast.makeText(getApplicationContext(), "AULA MODIFICADA",Toast.LENGTH_LONG).show();
 +
            }
 +
        });
 +
 +
 +
 +
        ListView lista = (ListView)findViewById(R.id.lstAulas_UD05_03_BaseDatos);
 +
        lista.setOnItemClickListener(new AdapterView.OnItemClickListener() {
 +
 +
            @Override
 +
            public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
 +
                                    long arg3) {
 +
                // TODO Auto-generated method stub
 +
                // Obtemos o obxecto aula seleccionado
 +
                ArrayAdapter<Aulas> adaptador = (ArrayAdapter<Aulas>) arg0.getAdapter();
 +
                aula_seleccionada = adaptador.getItem(arg2);
 +
                EditText editAula = (EditText)findViewById(R.id.etAula_UD05_03_BaseDatos);
 +
                editAula.setText(aula_seleccionada.getNome());
 +
 +
            }
 +
        });
 +
    }
 +
 +
    /**
 +
    * Accede á base de datos para obter as aulas e as asina á lista.
 +
    */
 +
    private void cargarLista(){
 +
 +
        ListView lista = (ListView)findViewById(R.id.lstAulas_UD05_03_BaseDatos);
 +
 +
        ArrayList<Aulas> aulas = baseDatos.obterAulas();
 +
        ArrayAdapter<Aulas> adaptador = new ArrayAdapter<Aulas>(getApplicationContext(),
 +
                android.R.layout.simple_list_item_1,aulas);
 +
        lista.setAdapter(adaptador);
 +
 +
    }
 +
 +
 +
    @Override
 +
    public void onStart(){
 +
        super.onStart();
 +
 +
        if (baseDatos==null) {  // Abrimos a base de datos para escritura
 +
            baseDatos = UD05_03_BaseDatos.getInstance(getApplicationContext());
 +
            baseDatos.abrirBD();
 +
 +
            cargarLista();
 +
        }
 +
    }
 +
 +
    @Override
 +
    public void onStop(){
 +
        super.onStop();
 +
 +
        if (baseDatos!=null){    // Pechamos a base de datos.
 +
            baseDatos.pecharBD();
 +
            baseDatos=null;
 +
        }
 +
 +
    }
 +
 +
    @Override
 +
    protected void onCreate(Bundle savedInstanceState) {
 +
        super.onCreate(savedInstanceState);
 +
        setContentView(R.layout.activity_UD05_03__datos_persistentes__bd);
 +
 +
        copiarBD();
 +
        xestionarEventos();
 +
    }
 +
}
 +
 +
</syntaxhighlight>
 +
 +
* 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.
 +
 +
 +
 +
 +
 +
 +
<br />
 +
 +
==Aclaración sobre el patrón Singlenton==
 +
 +
* No exemplo anterior empregamos o patrón [https://es.wikipedia.org/wiki/Singleton 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.
 +
 +
<syntaxhighlight lang="xml" line enclose="div" highlight="" >
 +
public class MiClase {
 +
    private static MiClase sInstance;
 +
 +
 +
 +
    public static synchronized MiClase getInstance() {
 +
        if (sInstance == null) {
 +
            sInstance = new MiClase ();
 +
        }
 +
        return sInstance;
 +
    }
 +
 +
}
 +
</syntaxhighlight>
 +
 +
 +
* 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, <u>primeiro pasa polo método onStart da segunda Activity</u> 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()
 +
 +
 +
 +
 +
<br />
 +
==Transaccións==
 +
 +
* No caso de querer facer varias operacións de modificación de forma conxunta sobre a a base de datos será necesario crear [https://es.wikipedia.org/wiki/ACID unha transacción].
 +
: Para iso temos que empregar o seguinte código (tendo como referencia o obxecto SQLiteDatabase => db no exemplo):
 +
 +
<syntaxhighlight lang="xml" line enclose="div" highlight="" >
 +
db.beginTransaction();
 +
try {
 +
    // Operaciones varias que impliquen modificación de datos.
 +
 +
    db.setTransactionSuccessful();
 +
} catch {
 +
    //Erro na transacción. Non debemos facer un return, xa que debe chegar a executar finally
 +
} finally {
 +
    db.endTransaction();  // Se todo vai ben a transacción se confirma. En caso de erro fará o RollBack
 +
}
 +
</syntaxhighlight>
 +
 +
 +
 +
* Máis información [https://www.sqlite.org/atomiccommit.html no seguinte enlace].
 +
 +
 +
 +
<br />
 +
'''[https://wiki.cifprodolfoucha.es/index.php?title=Programaci%C3%B3n_de_dispositivos_m%C3%B3biles#UNIDADE_5:_Datos_Persistentes Enlace a la página principal de la UD5]'''
 +
<br />
 +
<br />
 +
'''[https://wiki.cifprodolfoucha.es/index.php?title=Programación_de_dispositivos_móbiles Enlace a la página principal del curso]'''
 +
<br />
  
  

Revisión actual del 17:09 17 ene 2021

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.


  • Nota:


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 UD05_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á UD05_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 UD05_01_BaseDatos:

Código da clase UD05_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 UD05_01_BaseDatos extends SQLiteOpenHelper{
 8     public final static String NOME_BD="UD05_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 UD05_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: UD05_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.UD05_01_A1_DatosPersistentes_BD">
 8 
 9     <Button
10         android:id="@+id/btnCrearBD_UD05_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     <Button
23         android:id="@+id/btnPecharBD_UD05_01_A1_BaseDatos"
24         android:layout_width="wrap_content"
25         android:layout_height="wrap_content"
26         android:layout_marginStart="8dp"
27         android:layout_marginTop="8dp"
28         android:layout_marginEnd="8dp"
29         android:layout_marginBottom="8dp"
30         android:text="PECHAR BASE DE DATOS"
31         app:layout_constraintBottom_toBottomOf="parent"
32         app:layout_constraintEnd_toEndOf="parent"
33         app:layout_constraintHorizontal_bias="0.502"
34         app:layout_constraintStart_toStartOf="parent"
35         app:layout_constraintTop_toTopOf="parent"
36         app:layout_constraintVertical_bias="0.347" />
37 
38 </android.support.constraint.ConstraintLayout>


Código da clase UD05_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 UD05_01_DatosPersistentes_BD extends Activity {
11     private UD05_01_BaseDatos baseDatos;
12     
13     private void xestionarEventos(){
14 
15         findViewById(R.id.btnCrearBD_UD05_01_A1_BaseDatos).setOnClickListener(new View.OnClickListener() {
16             @Override
17             public void onClick(View v) {
18                 if (baseDatos==null){
19                     baseDatos = new UD05_01_BaseDatos(getApplicationContext());
20                     baseDatos.getWritableDatabase();
21 
22                     Toast.makeText(getApplicationContext(), "BD ABERTA PARA LECTURA/ESCRITURA", Toast.LENGTH_LONG).show();
23                 }
24 
25             }
26         });
27         findViewById(R.id.btnPecharBD_UD05_01_A1_BaseDatos)
28                 .setOnClickListener(new View.OnClickListener() {
29                     @Override
30                     public void onClick(View v) {
31                         if (baseDatos!=null){
32                             baseDatos.close();
33                             baseDatos = null;
34                             Toast.makeText(getApplicationContext(),"BD PECHADA",Toast.LENGTH_SHORT).show();
35                         }
36                     }
37                 });
38 
39 
40     }
41 
42     @Override
43     protected void onCreate(Bundle savedInstanceState) {
44         super.onCreate(savedInstanceState);
45         setContentView(R.layout.activity_UD05_01__datos_persistentes__bd);
46 
47         xestionarEventos();
48     }
49 }
  • 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.
  • Liña 32: Pechamos a base de datos ao premer no botón.


  • NOTA IMPORTANTE: Fixarse que aínda que premamos moitas veces no botón de crear base de datos, o método onCreate soamente se chama no caso de que non exista a base de datos. Se xa existe non se chama.
  • Podedes probar a poñer un número de versión diferente á base de datos e comprobar cun Log como pasa polo método onUpgrade.



  • Agora temos que resolver o 'problema' de onde se van a realizar as operacións contra a base de datos...
Se o fixéramos dende a Activity (é dicir, na activity irían as ordes SQL (INSERT, UPDATE, DELETE, SELECT) ) soamente teríamos que gardar a referencia ao obxecto SQLiteDatabase que devolve o método getWritableDatabase() ou o método getReadableDatabase() da seguinte forma:
 1 public class UD05_01_BD_EXEMPLO extends AppCompatActivity {
 2 
 3     private UD05_01_BaseDatos baseDatos;
 4     private SQLiteDatabase operacionsBD;
 5 
 6     private void xestionarEventos(){
 7         findViewById(R.id.btnCrearBD_UD05_01_A1_BaseDatos)
 8                 .setOnClickListener(new View.OnClickListener() {
 9                     @Override
10                     public void onClick(View v) {
11                         if (baseDatos==null){
12                             baseDatos = new UD05_01_BaseDatos(getApplicationContext());
13                             operacionsBD = baseDatos.getReadableDatabase();
14                             Toast.makeText(getApplicationContext(),"BD ABERTA PARA LECTURA/ESCRITURA",Toast.LENGTH_SHORT).show();
15                         }
16                     }
17                 });
18     ...........
A partires de entón, cando se queira facer unha operación contra a base de datos teríamos que facer:
1 ...
2 operacionsBD.rawQuery("select _id,nome from AULAS", null); 
3 ...
Veremos máis adainte o que fai dita orden. O que tedes que ter claro é que calquera operación SQL se ten que facer empregando o obxeto SQLiteDatabase que devolve o método getReadableDatabase() ou getWritableDatabase().


  • No noso caso, imos facer todas as operación dentro da clase UD05_01_BaseDatos (a que derive de SQLiteOpenHelper) e polo tanto necesitamos gardar dentro de dita clase unha referencia ao obxecto operacionsBD.
A forma 'óptima' de facelo é empregando o patrón Singleton que veremos máis adiante, neste exemplo imos ver unha solución sinxela...
O único que temos que facer é modificar a clase UD05_01_BaseDatos:
 1 public class UD05_01_BaseDatos extends SQLiteOpenHelper {
 2 
 3     public final static String NOME_BD = "basedatos.db";
 4     public final static int VERSION_BD=2;
 5 
 6     private SQLiteDatabase operacionsBD;
 7 
 8     private String CREAR_TABOA_AULAS ="CREATE TABLE AULAS ( " +
 9             "_id  INTEGER PRIMARY KEY AUTOINCREMENT," +
10             "nome VARCHAR( 50 )  NOT NULL)";
11 
12     public UD05_01_BaseDatos(Context context) {
13         super(context, NOME_BD, null, VERSION_BD);
14     }
15 
16     public void asigarSQLiteDatabase(SQLiteDatabase operacionsBD){
17         this.operacionsBD = operacionsBD;
18     }
19 
20     .....
  • E agora dende a AppCompactActivity UD05_01_BD_EXEMPLO gardamos a referencia:
 1     ...........
 2     private void xestionarEventos(){
 3         findViewById(R.id.btnCrearBD_UD05_01_A1_BaseDatos)
 4                 .setOnClickListener(new View.OnClickListener() {
 5                     @Override
 6                     public void onClick(View v) {
 7                         if (baseDatos==null){
 8                             baseDatos = new UD05_01_BaseDatos(getApplicationContext());
 9                             SQLiteDatabase operacionsBD = baseDatos.getWritableDatabase();
10                             baseDatos.asigarSQLiteDatabase(operacionsBD);
11                             Toast.makeText(getApplicationContext(),"BD ABERTA PARA LECTURA/ESCRITURA",Toast.LENGTH_SHORT).show();
12                         }
13                     }
14                 });
15 
16      .............
  • Agora imos poder facer todas as operacións dentro da clase UD05_01_BaseDatos facendo uso da referencia ao obxecto operacionsBD.


  • IMPORTANTE: Loxicamente nunha aplicación 'normal' abriremos, nas activities necearias, a base de datos no método onCreate e a pecharemos no método onDestroy.





Ferramenta interna: Database Inspector

  • A partires do Android Studio versión 4.1.1 é posible acceder e visualizar os datos dunha base de datos asociada a unha aplicación mentres se está a executar.
Para iso é necesario que a versión do emulador sexa maior ou igual á API 26.





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:EX_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: UD05_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 UD05_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 patrón 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 UD05_02_BaseDatos extends SQLiteOpenHelper{
 8     public final static String NOME_BD="EX_02_BD_FILE.db";
 9     public final static int VERSION_BD=1;
10     private static UD05_02_BaseDatos sInstance;
11 
12     public static synchronized UD05_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 UD05_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 UD05_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.UD05_02_DatosPersistentes_BD">
 8 
 9     <Button
10         android:id="@+id/btnAbrirBD_UD05_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 UD05_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 UD05_02_DatosPersistentes_BD extends Activity {
17     private UD05_02_BaseDatos baseDatos;
18 
19     private void copiarBD() {
20         String bddestino = "/data/data/" + getPackageName() + "/databases/"
21                 + UD05_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(UD05_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_UD05_02_BaseDatos).setOnClickListener(new View.OnClickListener() {
62             @Override
63             public void onClick(View v) {
64                 baseDatos = UD05_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_UD05_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:
  • WINDOWS:
  • adb root
  • adb shell
  • LINUX
  • ./adb root
  • ./adb shell
  • 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.
  • Tedes a lista completa de comandos neste enlace.

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   while (cursor.moveToNext()) {     // Quédase no bucle ata que remata de percorrer o cursor
3         long id =cursor.getLong(0);
4         String nome = cursor.getString(1);
5   }
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. No exemplo o moveToNext() xa vai no bucle, pero poderíamos poñer como condición while(!cursor.isAferLast()) e ter o cursor.moveNext() dentro do bucle.
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{

	private SQLiteDatabase sqlLiteDB;
        ...............


  • Creamos un método abrirBD() y pecharBD():
 1 public class UD6_03_BaseDatos extends SQLiteOpenHelper{
 2     public final static String NOME_BD="UD05_03_BD_FILE.db";
 3     public final static int VERSION_BD=1;
 4     private static UD05_03_BaseDatos sInstance;
 5 
 6     private SQLiteDatabase dbsqlLiteDB;
 7 
 8     public static synchronized UD05_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 UD05_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:EX_03_BD_FILE.zip

Creamos a clase que xestiona a base de datos

Nome da clase: UD05_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 UD05_03_BaseDatos extends SQLiteOpenHelper{
 12     public final static String NOME_BD="EX_03_BD_FILE.db";
 13     public final static int VERSION_BD=1;
 14     private static UD05_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 UD05_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 UD05_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         Aulas aula;
 70         while (datosConsulta.moveToNext()) {
 71            aula = new Aulas(datosConsulta.getLong(0),
 72                             datosConsulta.getString(1));
 73            aulas_devolver.add(aula);
 74         }
 75         return aulas_devolver;
 76     }
 77 
 78     public UD05_03_BaseDatos(Context context) {
 79         super(context, NOME_BD, null, VERSION_BD);
 80         // TODO Auto-generated constructor stub
 81     }
 82 
 83     public void abrirBD(){
 84         if (sqlLiteDB==null || !sqlLiteDB.isOpen()){
 85           sqlLiteDB = sInstance.getWritableDatabase();
 86         }
 87     }
 88 
 89     public void pecharBD(){
 90         if (sqlLiteDB!=null && sqlLiteDB.isOpen()){
 91           sqlLiteDB.close();
 92         }
 93     }
 94 
 95     @Override
 96     public void onCreate(SQLiteDatabase db) {
 97         // TODO Auto-generated method stub
 98        // db.execSQL(CREAR_TABOA_AULAS);
 99 
100     }
101 
102     @Override
103     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
104         // TODO Auto-generated method stub
105 
106         //db.execSQL("DROP TABLE IF EXISTS AULAS");
107         //onCreate(db);
108     }
109 
110 }




Creamos a activity

  • Dentro do paquete BasesDatos (creado previamente no paso anterior) crear unha nova 'Empty Activity' de nome: UD05_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.UD05_03_DatosPersistentes_BD">
 8 
 9     <ListView
10         android:id="@+id/lstAulas_UD05_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_UD05_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_UD05_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_UD05_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_UD05_03_BaseDatos"
63         app:layout_constraintStart_toEndOf="@+id/btnAlta_UD05_03_BaseDatos" />
64 
65     <Button
66         android:id="@+id/btnBaixa_UD05_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 UD05_03_DatosPersistentes_BD extends Activity {
 23     private UD05_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                 + UD05_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(UD05_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_UD05_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_UD05_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_UD05_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_UD05_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_UD05_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_UD05_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_UD05_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_UD05_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_UD05_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 = UD05_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_UD05_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()




Transaccións

  • No caso de querer facer varias operacións de modificación de forma conxunta sobre a a base de datos será necesario crear unha transacción.
Para iso temos que empregar o seguinte código (tendo como referencia o obxecto SQLiteDatabase => db no exemplo):
 1 db.beginTransaction();
 2 try {
 3     // Operaciones varias que impliquen modificación de datos.
 4 
 5     db.setTransactionSuccessful();
 6 } catch {
 7     //Erro na transacción. Non debemos facer un return, xa que debe chegar a executar finally
 8 } finally {
 9     db.endTransaction();  // Se todo vai ben a transacción se confirma. En caso de erro fará o RollBack
10 }




Enlace a la página principal de la UD5

Enlace a la página principal del curso




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