Couchbase Lite

Couchbase Mobile 101: Cómo crear tu primera aplicación [Couchbase LIVE Nueva York]

Lectura de 14 minutos

Desde la sesión 101 en la Pista móvil de Couchbase LIVE New York, repasamos cómo comenzar a integrar Couchbase Lite en sus proyectos de iOS y Android. A partir de “Diapositivas de ”Couchbase Mobile 101: Cómo crear tu primera aplicación móvil”, exploramos las API de Couchbase Mobile repasando la aplicación de ejemplo Grocery Sync, que se puede encontrar en el repositorio de GitHub de iOS y Android.En este blog, repasaremos a un alto nivel las características y APIs de Couchbase Lite que se presentaron en la sesión de Couchbase 101, así como parte del código que se encuentra en el ejemplo de Grocery Sync. Para comenzar, usted descargar Couchbase Lite Enterprise Edition para la plataforma en la que estás desarrollando y sigue el tutorial de iOS o el tutorial de Android para integrar Couchbase Lite en tus proyectos móviles.

Después de incorporar Couchbase Lite a sus proyectos móviles, necesitaremos inicializar Couchbase Lite y recuperar o crear una base de datos. A continuación, se presentan algunos conceptos y requisitos de Couchbase Mobile que necesitamos.

[1] Gerente

El Gerente es la clase de nivel superior a la que se hace referencia para crear un espacio de nombres para bases de datos. Crear una base de datos es simplemente hacer referencia a un nombre de cadena como se muestra a continuación:

iOS

Android

Con ese código implementado, podemos recuperar de la base de datos los documentos que contienen JSON en consecuencia. La base de datos también sirve como origen y destino para la replicación. Los documentos tienen cada uno su respectivo nombre único y su ID único. Más allá de eso, tienen JSON como sus propiedades, donde el objeto JSON es un conjunto de propiedades con nombre cuyos valores pueden ser nombres o cadenas, números, arreglos, diccionarios, etcétera.

[2] Documentos

El Documento incluye un ID de documento inmutable dentro de la base de datos donde el cuerpo del documento toma la forma de un objeto JSON anidado de pares clave-valor. Para permitir que diferentes tipos de documentos coexistan en una base de datos, la convención que se utiliza comúnmente es incluir una propiedad llamada ‘type’ que luego tiene una cadena (String) que define el tipo de sus documentos. Esta es una técnica utilizada para realizar un seguimiento de los diferentes tipos de documentos si hay más de un tipo en una base de datos y también ayudar con la indexación. Los documentos también contienen revisiones para fines de seguimiento de historiales de cambios y conflictos, por lo tanto, es fundamental para cómo funciona la replicación.

Para insertar documentos, el ‘crearDocumento()‘el método devolverá el ID de un documento que tiene el formato de un UUID generado aleatoriamente. En la aplicación de ejemplo para iOS, se crea un ‘NSDictionary’ para corresponder a un ‘NSObject’ en Objective-C con las propiedades ‘text’, ‘check’ y ‘created_at’ definidas. A continuación, para...

iOS


    NSDictionary *document = @{@"text":       text,
                               @"check":      @NO,
                               @"created_at": [CBLJSON JSONObjectWithDate:
                                                                   [NSDate date]]};
    // Guardar el documento:
    CBLDocument* doc = [database createDocument];
    NSError* error;
    if (![doc putProperties: document error: &error;]) {
        [self showErrorAlert: @"No se pudo guardar el nuevo elemento" forError: error];
    }

creamos las propiedades del nuevo documento y luego guardamos el documento haciendo referencia a la base de datos para el método ‘createDocument()’. ‘JSONObjectWithDate’ es una función de utilidad para tomar un objeto de fecha de Cocoa y convertirlo en un formato de cadena ISO8601, ya que las fechas no se pueden almacenar como objetos nativos en JSON.

Para Android, la clase ‘SimpleDateFormat’ está creando la ‘currentTimeString’ para el objeto donde el ID del documento se construye mediante la combinación de la ‘currentTime’ de la clase ‘Calendar’ y el UUID del método ‘randomUUID()’. Haciendo referencia a la ‘database’ que se creó a partir de la clase Manager, se crea un documento llamando al mismo método ‘createDocument()’ que en iOS. En Android, los pares de Clave-Valor se asemejan a una estructura de objetos HashMap y, por lo tanto, creamos un Mapa que es el equivalente en Java del objeto JSON en la variable ‘properties’. Las mismas tres propiedades se insertan en el mapa de Java mediante el método ‘put()’ y luego, para persistir en el disco, el objeto Map se pasa al método ‘putProperties()’. Esto se ilustra a continuación:

Android


    SimpleDateFormat dateFormatter = new SimpleDateFormat(
                                                   "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
    UUID uuid = UUID.randomUUID();
    Calendar calendar = GregorianCalendar.getInstance();
    long currentTime = calendar.getTimeInMillis();
    String currentTimeString = dateFormatter.format(calendar.getTime());
    String id = currentTime + "-" + uuid.toString();

    Document document = database.createDocument();
    Map(String, Object) properties = new HashMap(String, Object)();
    properties.put("_id", id);
    properties.put("text", text);
    properties.put("check", Boolean.FALSE);
    properties.put("created_at", currentTimeString);

    document.putProperties(properties);

[3] Adjuntos

El Adjunto función, aunque no se usa en el ejemplo, permite a los documentos adjuntar un blob binario de cualquier tamaño arbitrario y, por lo tanto, es una técnica para optimizar la replicación donde las actualizaciones de documentos son independientes de las actualizaciones de los adjuntos, ya que se almacenan por separado del cuerpo JSON. Por ejemplo, este puede ser un caso de uso para cuando los metadatos, el JSON del documento, se cambian en un adjunto asociado y, por lo tanto, si se actualiza un documento sin cambios en un adjunto, el replicador puede omitir el envío del adjunto.

[4] Vistas

El Ver permite a las aplicaciones crear y mantener índices secundarios utilizando la técnica de mapeo y reducción (map & reduce). Comenzamos con un documento JSON y la función de mapeo (Map Function) es la función que escribes que toma ese documento como entrada y produce un conjunto de pares de clave-valor. El resultado de esa función de mapeo que se ejecuta en todos los documentos de la base de datos genera un índice. En la aplicación de ejemplo Grocery Sync, estamos definiendo una Vista con una función de mapeo que indexa los elementos pendientes por fecha de creación. Creando una Vista para...

iOS

Para iOS, primero estamos creando una ‘Vista’ en la base de datos. La base de datos también es un contenedor o espacio de nombres para las ‘Vistas’, por lo que decimos ‘viewNamed: @“byDate”’ donde, si la vista no existe todavía, la crearemos y si existe, la devolveremos. El resto del bloque de código es para configurar su Bloque Map. Por lo tanto, esta es una vista MapReduce y significa que tiene una función map. Extraemos la fecha del documento buscando la propiedad ‘created_at’. Y si el documento tiene una, la emitimos como la clave. En la aplicación Grocery Sync, no emite nada para el valor porque en realidad va a buscar el documento mismo para obtener el resto de los datos. La cadena de versión, ‘@”1.1‘' al final, se usa para comunicar a la base de datos si su función map ha cambiado o no. Dado que la base de datos no puede saber cuándo ha cambiado la función map de una ejecución a la siguiente; una técnica es aumentar la cadena de versión para indicarle a la base de datos que descarte el índice actual y lo reconstruya.

Android

Esta es la versión de Java para Android donde, de manera similar a iOS, llamamos a ‘database.getView()’ y luego creamos la ‘función map’ utilizando cierta sintaxis de clases internas que en Java existe como un objeto ‘Mapper()’. Los índices se pueden actualizar bajo demanda y se puede extraer información útil del documento que deseas indexar para luego emitir la clave-valor. Cada vez que algo cambia, la función map recibe ese documento. La función map llama a una función llamada ’emit()’ que toma la ‘clave y el valor’ como parámetros.

Lo que la aplicación Grocery Sync está haciendo aquí es generar el índice de todos los elementos pendientes ordenados por la ‘clave’ y, dado que la clave es la marca de tiempo, los elementos se ordenarán cronológicamente, donde las cadenas de valor son los nombres de los elementos ‘pendientes’. El índice también recuerda el ID del documento que emitió ese par clave-valor. Por lo tanto, al realizar una consulta y cuando tienes una fila en tu consulta, puedes usar eso para volver al documento y recuperar toda esa fila de la base de datos si lo deseas. La idea aquí es que una vez que tengas este índice, lo consultarás. La mentalidad de consulta se realiza diciendo: “Quiero todas las entradas del índice con una clave particular, o un conjunto de claves, o un rango de claves”.”

[5] Consultas

Las consultas pueden entonces buscar un rango de filas de una vista y usar las claves y los valores de las filas directamente u obtener los documentos de los que provienen a partir del ID del documento. En el código siguiente, estamos impulsando la tabla desde una consulta de vista mediante la creación de una consulta ordenada por fecha descendente donde los elementos más nuevos se muestran primero.

iOS

Con los elementos del índice, queremos usar ese índice para controlar la vista de tabla, que es la interfaz de usuario principal de Grocery Sync. En iOS, generamos una consulta que es ‘viewNamed: @”byDate”‘ y llamamos a la consulta, lo que crea una consulta en ella. Luego vemos ‘LiveQuery’, donde es un subconjunto especial de consulta que en realidad rastreará la Vista a lo largo del tiempo. Establecemos la propiedad ‘descending’ de la consulta en ‘yes’ ya que queremos obtener las filas en orden descendente de las fechas para tener los elementos creados más recientemente en la parte superior. Por último, con el código específico de iOS, le indicamos al ‘dataSource’ acerca de la consulta e indicamos qué propiedad mostrar como la etiqueta en la vista de tabla.

Android


    liveQuery = view.createQuery().toLiveQuery();

    liveQuery.addChangeListener(new LiveQuery.ChangeListener() {
        public void changed(final LiveQuery.ChangeEvent event) {
            runOnUiThread(new Runnable() {
                public void run() {
                    grocerySyncArrayAdapter.clear();
                    for (Iterator(QueryRow) it = event.getRows(); it.hasNext();) {
                        grocerySyncArrayAdapter.add(it.next());
                    }
                    grocerySyncArrayAdapter.notifyDataSetChanged();

De igual manera, la versión de Java-Android se inicia de la misma forma, donde se crea una consulta haciendo que la vista invoque a ‘toLiveQuery()’ en ella para generar la ‘liveQuery’. Y luego se ejecuta ‘addChangeListener()’ en esa liveQuery, seguido de la llamada al método ‘changed()’ para las actualizaciones de la consulta, lo cual ocurre cada vez que cambia el resultado de dicha consulta. Y el resultado al ejecutar la consulta es una matriz de ‘QueryRows’ donde cada QueryRow es un objeto y tiene propiedades como Key, Value y DocumentID, pero también una propiedad de documento que cargará el documento nuevamente desde la base de datos. Este recorre un ‘Iterator()’ para obtener todas las filas de la consulta y agregarlas a ‘grocerySyncArrayAdapter’, que es una clase personalizada que tiene para almacenar el conjunto de datos.
[6] LiveQuery

Podemos pensar en LiveQuery como un contenedor alrededor de la consulta que escucha las notificaciones de cambio de la base de datos. Por lo tanto, cuando la base de datos cambia, LiveQuery volverá a iniciar la consulta, ejecutándola nuevamente en segundo plano de forma asíncrona. Y luego comparará los resultados de la consulta con los resultados anteriores que ya tenía. Si los resultados han cambiado, LiveQuery emitirá sus propios eventos de notificación que la aplicación podrá manejar en consecuencia, como redibujar la interfaz de usuario basándose en esa nueva consulta. A continuación, el código ilustra cómo mostrar las celdas de la tabla para...

iOS


- (void)couchTableSource:(CBLUITableSource*)source
              willUseCell:(UITableViewCell*)cell
                   forRow:(CBLQueryRow*)row
{
    // Establecer el fondo y la fuente de la celda:
    ………
    
    // Configurar el contenido de la celda. La función de mapeo (arriba) copia las propiedades del documento
    // en su valor, por lo que podemos leerlas sin tener que cargar el documento.
    NSDictionary* rowValue = row.value;
    BOOL checked = [rowValue[@"check"] boolValue];
    if (checked) {
        cell.textLabel.textColor = [UIColor grayColor];
        cell.imageView.image = [UIImage imageNamed:@"checked"];
    } else {
        cell.textLabel.textColor = [UIColor blackColor];
        cell.imageView.image = [UIImage imageNamed: @"unchecked"];
    }
    // cell.textLabel.text ya está configurado, gracias a la configuración de labelProperty
}

Para la aplicación de ejemplo aquí presente, el ‘CBLUITableSource’ de Couchbase Lite actuará como un intermediario entre LiveQuery y UITableView, recibiendo notificaciones de cambio y controlando así la tabla basándose en una consulta. También actúa como el objeto de origen de datos para la tableView, lo que significa que es el objeto al que la tableView le pedirá que proporcione todos los datos para las filas. Su objeto ‘controlador’ se convierte entonces en el delegado de la UITableView, donde recibirá notificaciones de la TableView sobre cuándo el usuario toca una de las filas.

Android


public View getView(int position, View itemView, ViewGroup parent) {
 //...
 TextView label = ((ViewHolder)itemView.getTag()).label;
 QueryRow row = getItem(position);
 SavedRevision currentRevision = row.getDocument().getCurrentRevision();
 // Check box
 Object check = (Object) currentRevision.getProperty("check");
 boolean isGroceryItemChecked = false;
 if (check != null && check instanceof Boolean)
     isGroceryItemChecked = ((Boolean)check).booleanValue();
 // Text
 String groceryItemText = (String) currentRevision.getProperty("text");
 label.setText(groceryItemText);

Aquí está el equivalente para Android en la clase Grocery Sync Adapter. Obtiene el ‘QueryRow’ llamando a ‘getItem()’ según el número de fila en la tabla. Luego, obtiene el documento de la fila de consulta y obtiene su revisión actual. A continuación, utiliza las propiedades de verificación y texto para rellenar los controles de la interfaz de usuario en la fila.

Por último, para la aplicación de ejemplo Grocery Sync, necesitamos responder a una pulsación en una fila de la tabla, como por ejemplo alternar la marca de verificación. A continuación, ilustramos cómo se hace esto haciendo referencia a QueryRow en un índice determinado y recuperando el documento a partir de este.

iOS


- (void)tableView:(UITableView *)tableView 
         didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Pedir a CBLUITableSource la fila de consulta correspondiente y obtener su documento:
    CBLQueryRow *row = [self.dataSource rowAtIndex:indexPath.row];
    CBLDocument *doc = row.document;

    // Alternar la propiedad 'checked' del documento:
    NSMutableDictionary *docContent = [doc.properties mutableCopy];
    BOOL wasChecked = [docContent[@"check"] boolValue];
    docContent[@"check"] = @(!wasChecked);

    // Guardar los cambios:
    NSError* error;
    if (![doc.currentRevision putProperties: docContent error: &error]) {
        [self showErrorAlert: @"No se pudo actualizar el elemento" forError: error];
    }
}

Este es un llamado a un método en la propia UITableView en su delegado. Indica que se seleccionó una fila, lo que significa ‘TOCADA’. Por lo tanto, va a acudir al origen de datos, que es el objeto UI Table Source, y le pedirá la fila de consulta en ese índice, y extraerá el documento de ella. Así que ahora básicamente está realizando un ciclo de lectura, escritura y modificación en ese documento. Donde obtiene las propiedades... hace una copia mutable de las propiedades donde ahora tenemos un diccionario mutable que podemos actualizar. Lee la propiedad de selección como un booleano y la vuelve a escribir como lo opuesto. Entonces, esto está invirtiendo el valor booleano de la propiedad de selección. Luego llama a putProperties al final para guardar ese valor nuevamente.

Android


public void onItemClick(AdapterView(?) adapterView, View view, int position, long id) 
{
    QueryRow row = (QueryRow) adapterView.getItemAtPosition(position);
    Document document = row.getDocument();
    Map(String, Object) newProperties = 
                              new HashMap(String, Object)(document.getProperties());

    boolean checked = ((Boolean) newProperties.get("check")).booleanValue();
    newProperties.put("check", !checked);

    try {
        document.putProperties(newProperties);
        grocerySyncArrayAdapter.notifyDataSetChanged();
    } catch (Exception e) {

Pasando a la versión de Android ahora, tenemos un ‘onItemClick()’ que es llamado por la interfaz gráfica de usuario de Android. Este va a obtener su QueryRow en esa posición, obtener el documento, extraer las propiedades y luego insertar las propiedades. En las API de Java es idiomático usar excepciones, lo cual no ocurre en Objective-C, por lo que esto tiene un bloque try-catch envuelto alrededor del manejo al guardar el documento. Si dentro de la ventana de tiempo alguna otra cosa modificó el documento, probablemente el replicador, entonces esto arrojaría un error. Obtendrías un error de conflicto.

A continuación, entraremos en la clase Replicator de Couchbase Lite y el Portal de desarrolladores de Couchbase Mobile es un excelente recurso para empezar.

 A partir de ahí nos sumergiremos en Couchbase Sync Gateway en nuestra sesión 102, donde hablaré sobre “Cómo agregar sincronización segura a tu aplicación móvil.”  Terminaremos el día con cómo habilitar la función de igual a igual (Peer-to-Peer) de Couchbase Mobile en la sesión 103 con Austin Gonyou, donde puedes crear experiencias sociales únicas dentro de la aplicación “Construyendo una aplicación de igual a igual (peer-to-peer) con Couchbase Mobile”.”

Compartir este artículo

Autor

William fue un Defensor de Desarrolladores en el equipo de Ingeniería Móvil/Experiencia de Desarrolladores en Couchbase. Su amor por el café y el código lo ha trascendido al mundo móvil mientras aprecia las experiencias presenciales fuera de línea. Antes, William trabajó en el equipo de Relaciones con Desarrolladores en Twitter, BlackBerry y Microsoft, además de haber sido ingeniero de software de GPS embebido en Research In Motion. William se graduó de la Universidad McGill en Ingeniería de Software Eléctrico

Deja un comentario

¿Listo para comenzar con Couchbase Capella?

Comenzar a construir

Visita nuestro portal para desarrolladores para explorar NoSQL, consultar recursos y comenzar con los tutoriales.

Usa Capella gratis

Empieza a usar Couchbase en tan solo unos clics. Capella DBaaS es la forma más fácil y rápida de comenzar.

Ponte en contacto

¿Quieres saber más sobre las ofertas de Couchbase? Permítenos ayudarte.