«

»

ene 23 2013

Cómo añadir datos precargados en tus aplicaciones

Hay ocasiones en las que por alguna razón necesitamos incluir una serie de datos precargados en nuestra aplicación android, de forma que cuando el usuario instale la aplicación pueda empezar a disfrutar de contenido sin necesidad de tener que depender de un servicio externo.

En el siguiente artículo os plantearemos una serie de alternativas sobre cómo partir de una base de datos con datos existentes. No existe una opción única o mejor que todas, ya que cada una de las soluciones aquí presentadas se adaptan a distintas situaciones.

Opción 1 – Carga manual al crear la base de datos

Si los datos son pocos y éstos no van a cambiar mucho la forma más sencilla es insertarlos manualmente durante la creación de la base de datos.

Al trabajar con bases de datos en android, tendremos una clase que extiende de SQLiteOpenHelper y que será la encargada de generar la base de datos. El método onCreate será el lugar donde podremos insertar los valores, de esta forma nos aseguramos que se hará siempre que se cree la base de datos.

Si estamos trabajando directamente con SQLiteOpenHelper (no estamos utilizando una librería externa de gestión de base de datos) nuestro método onCreate podría quedar de la siguiente forma:

    public void onCreate(SQLiteDatabase db) {
        // Sentencias de creación de base de datos
        // db.execSQL("CREATE TABLE ...");
        db.beginTransaction();
        try {
            ContentValues values = new ContentValues();
            for (int i = 0; i < NOMRES.length; i++) {
                values.put("nombre", NOMBRES[i]);
                values.put("edad", EDADES[i]);
                db.insert("usuarios", null, values);
            }
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
    }

Como vemos el funcionamiento es muy sencillo. Los datos podrían venir de cualquier otra clase de nuestro código en lugar de arrays (en el ejemplo NOMBRES y EDADES). Lo importante aquí empieza en la línea 4, declaramos que vamos a iniciar una transacción, insertamos los datos, declaramos que hemos insertado los datos correctamente y finalizamos la transacción. Se utilizan transacciones para que la inserción de datos se haga de una forma más rápida.

Si estamos utilizando alguna librería de gestión de base de datos siempre tendremos la posibilidad de ejecutar código durante la creación de la base de datos. ORMLite por ejemplo extiende de OrmLiteSqliteOpenHelper que también incluye el método onCreate.

Opción 2 – Cargar script SQL al crear la base de datos

Una segunda opción y quizás la más recomendable después de la Opción 4 consiste en crearnos un fichero SQL de inserción, leerlo e importarlo en el onCreate. El fichero podemos crearlo en el directorio assets y tendría una sentencia SQL por línea. Por ejemplo:


1
2
3
4
5
INSERT INTO usuarios (nombre, edad) VALUES ('usuario1', 18);
INSERT INTO usuarios (nombre, edad) VALUES ('usuario2', 48);
INSERT INTO usuarios (nombre, edad) VALUES ('usuario3', 30);
INSERT INTO usuarios (nombre, edad) VALUES ('usuario4', 55);
INSERT INTO usuarios (nombre, edad) VALUES ('usuario5', 16);

Si suponemos que el fichero se ha grabado en el directorio assets con el nombre import.sql nuestro método onCreate del SQLiteOpenHelper quedaría de la siguiente forma:

    public void onCreate(SQLiteDatabase db) {
        // Sentencias de creación de base de datos
        // db.execSQL("CREATE TABLE ...");
        InputStream is = null;
        try {
             is = mContext.getAssets().open("import.sql");
             if (is != null) {
                 db.beginTransaction();
                 BufferedReader reader = new BufferedReader(new InputStreamReader(is));
                 String line = reader.readLine();
                 while (!TextUtils.isEmpty(line)) {
                     db.execSQL(line);
                     line = reader.readLine();
                 }
                 db.setTransactionSuccessful();
             }
        } catch (Exception ex) {
            // Muestra log             
        } finally {
            db.endTransaction();
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    // Muestra log
                }                
            }
        }
    }

Como vemos resulta muy sencillo. La variable mContext podemos inicializarla en el constructor del SQLiteOpenHelper. Luego sólo tenemos que cargar el fichero, leerlo línea a línea e ir ejecutando las sentencias. Esto mismo podría valernos para el método onUpgrade, con un fichero SQL con sentencias de actualización de base de datos.

Esta opción es muy recomendable cuando tenemos un número considerable de datos, pero hay que tener en cuenta que es posible que se ejecute en el hilo principal (por ejemplo porque inicializamos el helper en una activity) llegándolo a bloquear si tarda mucho.

Al igual que antes, la mayoría de los gestores de base de datos proporcionan el método onCreate donde podemos realizar esta misma tarea.

Opción 3 – Copiar una base de datos creada con anterioridad

Esta opción y la última son las aconsejables cuando el volumen de datos es muy grande. No obstante, aunque la opción 3 pueda llegar a ser la más cómoda es, sin lugar a dudas, la que más problemas y quebraderos de cabeza puede darnos. No es la más recomendable, sin embargo es una solución posible y como tal os la presentamos en el artículo.

La idea se basa en crear una base de datos SQLite, insertar datos mediante un programa externo en nuestro ordenador, empaquetarla en el directorio assets de nuestra aplicación y en la creación de la base de datos darle el cambiazo a la recién creada por la nuestra.

Existen multitud de recursos sobre cómo realizar esta tarea, basta con una simple búsqueda en Google para encontrar alguno de los artículos. A continuación vamos a comentar los pasos por encima.

Paso 1 – Preparar la base de datos

El primer paso es preparar nuestra base de datos. Podemos utilizar la herramienta sqlite3 que incorpora el SDK de android o algún editor gráfico como SQLite Database Browser o SQLiteman. Lo importante es que nuestra base de datos debe tener una tabla con el nombre android_metadata con una única columna de nombre locale y con una fila con el valor en_US. Podemos crearla con las siguientes dos sentencias SQL:


1
2
CREATE TABLE android_metadata (locale TEXT DEFAULT 'en_US');
INSERT INTO android_metadata VALUES ('en_US');

Una vez hecho esto creamos las tablas de nuestra aplicación e insertamos los datos necesarios.

Paso 2 – Copiar la base de datos en el directorio assets

Este paso, a priori sencillo, puede darnos más de un problema. El motivo es que si nuestra base de datos ocupa más de 1MB, al crear el APK la base de datos estará en el directorio assets pero comprimida y al leerla nos dará un error.

La herramienta aapt ignora para su compresión algunos recursos en base a su extensión, como por ejemplo mp3 o avi, porque se supone que estos ficheros ya están comprimidos. Por tanto, tenemos dos formas de evitar que la herramienta aapt comprima nuestro fichero de base de datos al crear el APK:

  • Forma fácil: Le cambiamos la extensión a .mp3
  • Forma correcta: Le pasamos el parámetro ‘-0′ (cero) seguido de la extensión ‘db’ (o la extensión del fichero de nuestra base de datos si es otra) a la herramienta cuando vayamos a crear el APK

Paso 3 – Inicializar la base de datos y copiar la nuestra

El último paso consiste en modificar nuestra clase SQLiteOpenHelper para que realice los siguientes pasos:

  1. Compruebe si la base de datos está inicializada
  2. Si no lo está, inicialice y la sobrescriba con la base de datos del directorio assets

Como dijimos antes, existen varias páginas que explican cómo realizar esta tarea. A continuación podemos ver una posible implementación del SQLiteOpenHelper

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;

public class DBHelper extends SQLiteOpenHelper {
    
    public static final int DATABASE_VERSION = 1;
    public static final String DATABASE_NAME = "basededatos.db";
    
    private Context mContext;
    
    public DBHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        mContext = context;
    }
    
    public void createDataBase() throws IOException {
        File pathFile = mContext.getDatabasePath(DATABASE_NAME);
        boolean dbExist = checkDataBase(pathFile.getAbsolutePath()); 
        if(!dbExist) {
            this.getReadableDatabase(); 
            try {
                copyDataBase(pathFile); 
            } catch (IOException e) { 
                // Error copying database 
            }
        } 
    }
    
    private boolean checkDataBase(String path) { 
        SQLiteDatabase checkDB = null; 
        try {            
            checkDB = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY); 
        } catch(Exception e){
             // Database doesn't exist 
        } 
        if(checkDB != null) { 
            checkDB.close(); 
        } 
        return checkDB != null;
    }
    
    private void copyDataBase(File pathFile) throws IOException { 
        InputStream myInput = mContext.getAssets().open("basededatos.db");
        OutputStream myOutput = new FileOutputStream(pathFile);
        byte[] buffer = new byte[1024];
        int length;
        while ((length = myInput.read(buffer)) > 0) {
            myOutput.write(buffer, 0, length);
        } 
        myOutput.flush();
        myOutput.close();
        myInput.close(); 
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        
    }

}

Siempre que queramos hacer uso de los métodos getReadableDatabase() y getWritableDatabase() tendríamos que llamar al método createDatabase() que se encarga de crear la base de datos si ésta no existe (si ya existe no hace nada).

¡Excepciones!: Si vas a utilizar este código en tu aplicación ten en cuenta las capturas de excepciones, deberás actuar en consecuencia.

Opción 3′ – Utilizar android-sqlite-asset-helper

La librería android-sqlite-asset-helper nos facilita la implementación mediante la opción 3 pero con la ventaja de que nos ahorra prácticamente todo el trabajo. Por contra, perderemos el control de qué está ocurriendo.

Para utilizarla debemos seguir los siguientes pasos:

  1. Copiar android-sqlite-asset-helper.jar en nuestro directorio libs
  2. Crear nuestro helper extendiendo de SQLiteAssetHelper
  3. Llamar al “super constructor” pasándole el nombre de la base de datos.

Por ejemplo, si invocamos el super constructor de la siguiente forma:

super(context, "mibasededatos", null, 1);

Tendremos que poner nuestra base de datos comprimida en:


1
assets/databases/mibasededatos.zip

Simplemente con esto, la librería gestionará la importación de la base de datos en el caso de que no existiera.

Opción 4 – Estándar

¿Pero qué ocurre si los datos tardan mucho en cargar?, o ¿y si los leo desde un servicio web externo?, o ¿y si me los pasan en XML o JSON?. Bueno, si estás en una de las situaciones anteriores una solución como esta es la más indicada. Es la opción más visual (de hecho es la única que cuenta con vistas) y podemos verla reflejada en el siguiente boceto:

Mockup carga de datos

La  idea es sencilla, al entrar en nuestra pantalla principal comprobamos si están los datos cargados. Si no lo están cargamos una vista como la del boceto e iniciamos una tarea de carga.

El código aquí no tiene sentido, pues existen muchísimas formas de conseguir este comportamiento y depende, entre otras cosas, de nuestra forma de conectarnos a la base de datos.

Esta forma de resolver la carga inicial está especialmente indicada cuando tenemos un volumen de datos bastante considerable y además los datos estén en un formato no impuesto por nosotros, como por ejemplo XML o fichero de texto.

La idea de esta aproximación es ejecutar la tarea en un segundo plano, por lo que si la importación tarda más de un par se segundos de ejecución esta puede ser una buena opción. Podría implementarse fácilmente con una AsyncTask, aunque los detalles ya dependen de vuestro caso.

Las características de esta aproximación son:

  1. Se adapta a cualquier cantidad de datos. Si la importación tarda más de un minuto no es problema, pues no estamos bloqueando el hilo principal de ejecución.
  2. Tendremos que diseñar los layouts, gestionar la carga en segundo plano y actualizar la vista cuando sea necesario.

Conclusiones

Como vemos es relativamente sencillo incorporar unos datos precargados a nuestras aplicaciones android.

En ese artículo se han presentado 4 +1 formas de realizar esta tarea. Y vosotros, ¿utilizáis otros mecanismos?, si habéis utilizado alguna de las propuestas, ¿cuál ha sido vuestra experiencia?.

Acerca del autor

FedeProEx

Ingeniero Informático en la Universidad de Sevilla, programador Java y amante del Heavy Metal. Soy desarrollador android fuera del horario de trabajo con algunas aplicaciones en el market como Tiempo AEMET o aconTags

  • http://desandroid.com DesAndrOId

    Muy bueno. No puedo decir más.

    • Daniel

      Muy útil . Gracias!

  • adrian

    armo mi sqlite DB por fuera y la agrego en assets. Luego la copio a app/nombreApp/….. y listo

  • Julen

    Una pequeña duda en la opción 3:
    en checkDataBase() siempre va a saltar la excepción, ya que el archivo todavía no existe, no sería mejor simplemente comprobar si existe un archivo con ese nombre para evitar la excepción?
    Algo Asi:

    1
    2
    3
    4
    5
    private boolean checkDataBase(File pathFile) {
        boolean checkDB = false;
        checkDB = pathFile.exists();
        return checkDB;
    }

    Un saludo y gracias!

    • FedeProEx

      Buenas!, la excepción saltará siempre que no esté el fichero. La idea de la llamada a SQLiteDatabase.openDatabase() no es sólo comprobar que el fichero está sino que se trata de una base de datos SQLite válida (ha podido abrirse). No obstante si confiamos en que la copia que nosotros hacemos siempre se hace bien podría ser suficiente con el código que planteas. ¡Gracias por el aporte!

  • fresco

    donde puedo conseguir un ejemplo el metodo 3??

    • FedeProEx

      Hola!, desgraciadamente no hay un único código que lo implemente. Cada caso tendrá su implementación concreta. Normalmente tendrás que hacerlo con un Service pero dependerá de tu aplicación. Un saludo

  • Jefferson

    gracias me Ayudaste..!! Al utilizar el metodo 3, respecto al Oncreate y OnUpgrade que pasa con ellos..!! Las consultas las Haria normal y si quiero insertar datos Puedo ??

  • cristian

    hola una consulta, es posible o recomendable precargar una base de datos desde un archivo xml, alojado en la carpeta assets?

  • http://gonzac-studios.blogspot.com.ar/ gonzacst

    Porfin un articulo completo y bien explicado.

    Saludos

  • http://gravatar.com/jefferson7arcos jefferson arcos

    Gracias, ni en ingles mire algo tan bien explicado, un saludo desde Colombia.

  • Richar

    Hola, donde puedo encontrar algún ejemplo del método 4, gracias.

    • FedeProEx

      Hola!

      La verdad es que no se ahora de algún ejemplo completo pero te recomiendo que lo realices con un servicio en segundo plano y conectándolo con la activity para ir mostrando el progreso. En la documentación oficial tienes la información sobre cómo implementarlo.

      Services
      Bound Services

      Saludos

  • Ana

    No hay manera de que conecte mi app con la base de datos. AYUDAA!
    Algo tengo que estar haciendo mal.. :(

  • juan

    buenas estoy estudiando programacion y estoy haciendo una app en android de una agenda de rrhh pero quiero guardar los datos ingresados a sqlite a un archivo para luego ocuparlo y ver los datos

  • Jesus Moran

    Buenas, he realizado todos y cada uno de los pasos aqui listados, efectivamente la base de datos se copia al dispositivo, pero no asi la tabla, la tabla con la que trabajo no se esta copiando y me genera un error, al momento de hacer un recorrido por la base de datos solo me muestra la tabla android metadata, pero el resto de tablas que se requieren no aparecen, que puede ser este error y como podria solucionarlo

  • Stephy Peigonet

    hola, alguien puede ayudar. necesito pre cargar datos de consultas medicas. y desde alli comenzar a trabajar con esos datos. como los puedo cargar? es un programa medico que ya debe tener consultas . y los medicos deben ver un tipo de historia clinica si entrar a un paciente en particular.