miércoles, 18 de julio de 2012

Base de Datos en Android

Una de las cosas que me rondaba en la cabeza cuando comencé a interesarme en programar para Android era el tema de la base de datos.

Leyendo encontre que la base de datos mas utilizada para el desarrollo Android es SQLite. NO es el gran gestor de base de datos y está muy bien porque para un dispositivo móvil no necesitamos un Oracle ni mucho menos, porque así sea la ultima tablet o el ultimo Teléfono del mercado los recursos con los que contamos en hardware son muy limitados. Pero SqLite permite crear tablas, indices, primary key etc. Cuenta con un pequeño motor sql que permite hacer consultas y muchas cosas mas. Así que suficiente.

No es un proceso independiente como las grandes Bases de datos Sqlserver o Postgresql que son servicios a los que hay que conectarse. Una base en SQLite es simplemente un archivo .db que se integra junto con la aplicación, muy semejante a cuando antes utilizábamos base de datos Access (Los que nacimos en el 86 o antes me entienden).

Voy a compartirles como me conecto yo a una base de datos SQLite en mis aplicaciones Android, la metodología que uso y que herramientas utilizo para gestionar la base.

Paso 1: Crear la Base de datos SQLite
Como decía anteriormente la base de datos no es mas que un archivo .db, se la puede crear utilizando SQLite Database Browser; esta es una herramienta gratuita que permite gestionar de manera visual la base de datos, muy intuitiva y fácil de usar.
Esta es una vista de la herramienta.
Como se observa permite hacer lo básico y necesario para gestionar una base de datos: crear la base, tablas, índices, ver el contenido de la base y ejecutar sentencias sql.
Yo he creado una base de datos dbgasto.db con una tabla motivo y una tabla gasto. Una vez que tengamos la base de datos de nuestra aplicación es necesario crear la tabla android_metadata porque si no existe, al momento de hacer la conexion a la base mandará un error informando que no se ecuentra la tabla android_metadata. La tabla es bien sencilla solo tiene un campo llamado locale con un registro 'es_ES'.

Para crear la tabla utilizamos los botones de la barra de herramientas:
El primero permite crear la tabla, le ponemos de nombre android_metadata y le añadimos un campo locale de tipo TEXT. Luego insertamos un registro con el valor de 'es_ES'. Al final la tabla debe quedar así:
Ahora si el siguiente paso es conectarse a dicha base desde la aplicación de Android.

Paso 2: Ubicación de la base de datos en el Proyecto Android
Una vez que tengamos nuestro archivo .db que corresponde a la base de datos, copiamos el archivo en la carpeta assets de nuestro proyecto android. La razón es muy sencilla porque mediante código podemos acceder fácilmente a esta carpeta utilizando el contexto de nuestra aplicación como se verá mas adelante.
En mi ejemplo la base se llama dbgasto.db

Paso 3: Conexión a la base de datos 
Crearemos nuestra propia clase para conectarnos a la base, esta extenderá de la clase SQLiteOpenHelper. La metodología que utilizo es la siguiente.

  1. Primero verifica si la base de datos existe.
  2. Si existe ok, se conecta a la base y continua.
  3. Si no existe, se crea la base de datos y luego se sobrescribe dicha base con el contenido de nuestra base en la carpeta assets.
La clase la voy a llamar Basedatos y como decía anteriormente extiende de SQLiteOpenHelper.
La Clase con sus parámetros y constructor sería la siguiente:

 public class Basedatos extends SQLiteOpenHelper { 
     //Ruta por defecto de las bases de datos en el sistema Android
     private static String DB_PATH = "/data/data/app.gastos/databases/";
     //archivo de la base de datos que esta en la carpeta assets
     private static String DB_NAME = "dbgasto.db"; 
     private SQLiteDatabase myDataBase; 
     private final Context myContext;
     public static final String KEY_ID = "_id";

     public Basedatos(Context context) {
          //el uno corresponde a la version de la base de datos; 
          // En mi caso es la primera versión 
          super(context, DB_NAME, null, 1);
          this.myContext = context;
     }

DB_PATH es la ruta por defecto donde android almacena las bases de datos de sus aplicaciones. /data/data/Paquete de la apliación/databases.
El paquete de la aplicación en mi caso es el app.gastos,  toma como referencia el paquete del contexto de la clase que recibo como parámetro en el constructor. 
Si queremos borrar la base manualmente debemos acceder a esa ruta pero para eso debemos Rootear nuestro teléfono.
DB_NAME es simplemente el nombre del archivo de mi base de datos que se encuentra en la carpeta assest.
myDataBase es donde voy almacenar el objeto SQLiteDatabase que representa la conexión a mi base.
myContext es donde voy almacenar el contexto de la clase que recibo como parámetro en el constructor, esto es muy importante ya que es a través de este contexto como puedo acceder a la carpeta assets de la aplicación.
KEY_ID es una variable String que representa el campo clave primaria que voy a utilizar en mis tablas. y es público para poder cambiarlo cuando yo quiera. Por defecto el campo para las claves primarias las he llamado _id.

El constructor recibe como parámetro el contexto de la aplicación. Simplemente hay que enviarle como parámetro una referencia de la clase desde donde estoy llamando al constructor, en otras palabras enviarle this. Dentro del constructor llamo al constructor de la clase padre, es importante enviarle el contexto y el nombre de la base que voy a utilizar. Para terminar asigno el contexto recibido a mi variable myContext.

el siguiente código se encarga de verificar si existe la base o no y copiarla al dispositivo.

/**
 * Comprueba si la base de datos existe para evitar copiar 
 * siempre el fichero cada vez que se abra la aplicación.
 */
 private boolean checkDataBase(){
      SQLiteDatabase checkDB = null;
      try{ 
           String myPath = DB_PATH + DB_NAME;
           checkDB = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);
 
      }catch(SQLiteException e){ 
           //si llegamos aqui es porque la base de datos no existe todavía. 
      }
      if(checkDB != null){
           checkDB.close();
      }
      return checkDB != null ? true : false;
 }
 
 /**
 * Copia nuestra base de datos desde la carpeta assets a la recién creada
 * base de datos en la carpeta de sistema, desde dónde podremos acceder a ella.
 * */
 private void copyDataBase() throws IOException{
      //Abrimos el fichero de base de datos como entrada
      //A través del contexto accedemos a la carpeta assets
      InputStream myInput = myContext.getAssets().open(DB_NAME);
 
      //Ruta a la base de datos vacía recién creada
      String outFileName = DB_PATH + DB_NAME;
 
      //Abrimos la base de datos vacía como salida
      OutputStream myOutput = new FileOutputStream(outFileName);
 
      //Transferimos los bytes desde el fichero de entrada al de salida
      byte[] buffer = new byte[1024];
      int length;
      while ((length = myInput.read(buffer))>0){
           myOutput.write(buffer, 0, length);
      }
 
      //Liberamos los streams
      myOutput.flush();
      myOutput.close();
      myInput.close();
 }
 
 /**
 * Crea una base de datos vacía en el sistema y la reescribe con nuestro fichero 
 * de base de datos.
 * */
 public void createDataBase() throws IOException{ 
      boolean dbExist = checkDataBase();
 
      if(dbExist){
           //la base de datos existe y no hacemos nada.
      }else{
           //Llamando a este método se crea la base de datos vacía en la ruta por defecto 
           //del sistema
           //de nuestra aplicación por lo que podremos sobreescribirla con nuestra base de datos.
           this.getReadableDatabase();
 
           try { 
                copyDataBase(); 
           }catch (IOException e) {
                throw new Error("Error copiando Base de Datos");
           }
      } 
 }

Todo comienza en el método createDataBase, este pregunta si la base de datos existe, si existe no hace nada caso contrario llama al método getReadableDatabase() de la clase padre; este método manda a crear la base de datos en la ruta /data/data/paquete/databases/dbgasto.db  y la crea con el nombre dbgasto.db porque ese nombre fue que le enviamos como párametro en el constructor por medio de la variable DB_NAME. Luego de eso se llama al método copyDataBase que sobreescribe la base de datos creada recientemente con la de la carpeta assets.


 /**
  * Abre la base de datos
  */
  public void open() throws SQLException{ 
      if(myDataBase==null || (!(myDataBase.isOpen())) ){ 
      try {
           createDataBase(); //Si no existe crea la base de datos, si existe no hace nada 
      } catch (IOException e) {
           throw new Error("Ha sido imposible crear la Base de Datos");
      }
           String myPath = DB_PATH + DB_NAME;
           myDataBase = SQLiteDatabase.openDatabase(myPath, null,SQLiteDatabase.OPEN_READONLY);
      }
  }
 

  public void close(){
      myDataBase.close(); 
  }


El método Open Abre la conexión a la base de datos en modo solo lectura y asigna la conexión a la variable myDataBase. Pero antes llama al método createDataBase, de modo q la primera vez que se ejecute la aplicaión cree la base de datos en el dispositivo. Las siguientes veces dicho método no hara nada.

Estos son los métodos principales para realizar la conexión a una base de datos SqLite en Android.

Todavía faltan algunas cosas como métodos de lectura, escritura, insert, delete, etc. Y ejemplos de como utilizar esta clase.
Continuará...!

10 comentarios:

Developer en Desarrollo dijo...

Veo que haces una exaustiva explicacion acerca de SQLite! quisiera saber si tengo alternativas a esta base o solo se puede utilizar esta!.

Unknown dijo...

Bueno.. según lo que he podido leer es la más utilizada, hasta hay librerías que ya ayudan en todo este tema del manejo de base de datos SqLite ahorrandonos ese trabajo. pero en otros entornos si encontramos otras bases de datos como SqlServer Compact Edition para Windows Mobile. etc..

Anónimo dijo...

Gracias amigo lo pondré en practica

Anónimo dijo...

Hola! gracias por este ejemplo. yo tengo un problema, hago lo mismo q tu, inserto esas dos tablas de mas qe se necesitan para que funcione en un dispositivo real, lo q pasa q yo tengo la base de datos, ya hecha, la pongo en la carpeta assets, y enel emulador me va bien, pero al pasarla a un dispositivo real, no hay forma, me da error. q podria ser¿?¿? tengo permisos de escritura, lectura de almacenamiento externo... ya se nose q mas probar.. o es mi tablet¿?

Unknown dijo...

Amigo al iniciar la aplicación por primera vez la base se copia en la dirección /data/data/app.gastos/databases/ app.gastos es el nombre del paquete donde se encuentra la clase java que hace la conexión. si ya existe ese archivo en la ruta no se vuelve a copiar, sugiero que revises esa dirección en tu dispositivo la borres y vuelvas a correr la aplicación.! Suerte.!! =)

Gaby Mtz dijo...

alguna conexión con SQL Server????

Francisco Jose Martinez Tronco dijo...

Buenos dias.
Estoy desarrollando una pequeña app donde quiero distruibuir una bas de datos sqlite junto con el apk. He visto tu ejemplo y como en muchas páginas que he visitado veo que se copia la BBDD en asset y a continuación en una función copiarbasedatos se lee con inputstream y se escribe con outputstream como cualquier fichero.
Mi problema es que al indicarle al objeto OutputStream la ruta y fichero a crear me da un error de fichero no se puede crear.
Nota : Mi ruta la establezco como /data/data/mi_paquete/databases/mi_bbdd

Si lo pruebo en el emulador de eclipse funciona bien pero si lo depuro contra un terminal fisico me arroja el error comentado.
¿Puedes aportar algo de luz?
Gracias.

Unknown dijo...

Acerca lo de conexiones con sql server .. pues solo se me ocurre a través de webservices..!!

y sobre la ruta.
Verifica si el nombre del paquete de tu app es el que se esta creando en tu ruta.. no se xq. pero me ha pasado que a veces no se me crea la ruta con el mismo nombre del paquete.

para acceder a esa ruta de data/data debes estar con el android rooteado y para ver los ficheros puedes hacerlo con el ddms desde eclipse.

Suerte..!!

Unknown dijo...

Se puede mandar la información de la base automáticamente a un correo?

Fernando dijo...

hola, tengo un problema!!! la clase de conexion la tengo igual, mi problema es q tengo un activity con 3 fragments puesto cada uno en un tabs, cada fragments carga una tabla de la base de datos, pero dos de los 3 fragments me va cargando repetidamente la tabla!! que podra ser ??

Publicar un comentario