A partir de la sesión 101 del Pista móvil Couchbase LIVE Nueva Yorken el que repasamos cómo empezar a integrar Couchbase Lite en tus proyectos iOS y Android. Desde el "Diapositivas "Couchbase Mobile 101: Cómo crear su primera aplicación móvilexploramos las APIs de Couchbase Mobile recorriendo la aplicación de ejemplo Grocery Sync que se puede encontrar en el repositorio de Github para iOS y Android. En este blog, recapitularemos a alto nivel las características y APIs de Couchbase Lite que fueron presentadas en la sesión Couchbase 101, así como parte del código encontrado en el ejemplo Grocery Sync. Para empezar 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 traer Couchbase Lite a tus proyectos móviles, necesitaríamos 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] Director
En Director es la clase de nivel superior a la que se debe hacer referencia al crear un espacio de nombres para bases de datos. Crear una base de datos es simplemente hacer referencia a un nombre de cadena como a continuación:
iOS
|
1 2 3 4 5 6 7 8 9 |
NSError* error; auto.base de datos = [[CBLManager sharedInstance] base de datosNamed: kDatabaseName error: &error]; si (!auto.base de datos) { [auto showAlert: @"No se pudo abrir la base de datos" error: error fatal: SÍ]; devolver NO; } |
Android
|
1 2 3 4 |
director = nuevo Director(nuevo AndroidContext(este), Director.OPCIONES_POR_DEFECTO); base de datos = director.getDatabase(NOMBRE_BASE_DE_DATOS); |
Con ese código en su lugar, somos capaces de recuperar los documentos que contienen JSON de la base de datos en consecuencia. La base de datos también sirve como fuente y destino para la replicación. Cada documento tiene un nombre único y un ID único. Más allá de eso tienen JSON como sus propiedades, donde el objeto JSON es un conjunto de propiedades de nombre donde sus valores pueden ser nombres o cadenas, números, matrices, diccionarios, y etc.
[2] Documentos
En Documento incluye un ID de documento inmutable dentro de la base de datos donde el cuerpo del documento toma la forma de un objeto anidado JSON 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 "tipo" que luego tiene una cadena que define el tipo de sus documentos. Se trata de 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 ayuda con la indexación. Los documentos también contienen revisiones a efectos de seguimiento de historiales de cambios y conflictos, por lo que es clave para el funcionamiento de la replicación.
Para insertar documentos se utiliza el botón 'crearDocumento()devolverá un ID de un documento que está en forma de UUID generado aleatoriamente. En la aplicación de ejemplo para iOS, se crea un 'NSDictionary' que corresponde a un 'NSObject' en Objective-C con las propiedades 'text', 'check' y 'created_at' definidas. Abajo para ..
iOS
|
1 2 3 4 5 6 7 8 9 10 11 12 |
NSDictionary *documento = @{@"texto": texto, @"comprobar": @NO, @"fecha_creada": [CBLJSON JSONObjectConFecha: [NSDate fecha]]}; // Guardar el documento: CBLDocumento* doc = [base de datos crearDocumento]; NSError* error; si (![doc putProperties: documento error: &error]) { [auto showErrorAlert: @"No se ha podido guardar el nuevo elemento" paraError: error]; } |
creamos las propiedades del nuevo documento y luego guardamos el documento haciendo referencia a la base de datos para el método 'createDocument()'. El 'JSONObjectWithDate' es una función de utilidad para tomar un objeto de fecha Cocao y lo convierte 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 el 'currentTimeString' para el objeto donde el ID del documento se construye mediante la combinación del 'currentTime' de la clase 'Calendar' y el UUID del método 'randomUUID()'. Haciendo referencia a la "base de datos" creada a partir de la clase Manager, se crea un documento llamando al mismo método "createDocument()" que en iOS. En Android, los emparejamientos Clave-Valor se asemejan a una estructura de objetos HashMap, por lo que creamos un Mapa que es el equivalente Java del objeto JSON en la variable 'properties'. Las mismas tres propiedades se insertan en el mapa Java por el método 'put()' y luego para persistir en disco, el objeto Map se pasa al método 'putProperties()'. Esto se ilustra a continuación:
Android
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
SimpleDateFormat formato de fecha = nuevo SimpleDateFormat( "aaaa-MM-dd'T'HH:mm:ss.SSS'Z'"); UUID uuid = UUID.randomUUID(); Calendario calendario = Calendario gregoriano.getInstance(); largo currentTime = calendario.getTimeInMillis(); Cadena currentTimeString = formato de fecha.formato(calendario.getTime()); Cadena id = currentTime + "-" + uuid.toString(); Documento documento = base de datos.crearDocumento(); Mapa(Cadena, Objeto) propiedades = nuevo HashMap(Cadena, Objeto)(); propiedades.poner("_id", id); propiedades.poner("texto", texto); propiedades.poner("comprobar", Booleano.FALSO); propiedades.poner("fecha_creada", currentTimeString); documento.putProperties(propiedades); |
[3] Archivos adjuntos
En Adjunto aunque no se utiliza en el ejemplo, permite a los documentos adjuntar cualquier blob binario de tamaño arbitrario y, por lo tanto, es una técnica de optimización para la replicación en la que las actualizaciones de los documentos son independientes de las actualizaciones de los adjuntos, ya que se almacenan por separado del cuerpo JSON. Por ejemplo, esto puede ser un caso de uso para cuando los metadatos, documento JSON, se cambia en un archivo adjunto asociado y por lo tanto si un documento se actualiza sin cambios en un archivo adjunto, a continuación, el replicador puede omitir el envío del archivo adjunto.
[4] Puntos de vista
En Ver permite a las aplicaciones crear y mantener índices secundarios utilizando la técnica map & reduce. Empezamos con un documento JSON y la función de mapa es la función que escribes que toma ese documento como entrada y produce un conjunto de pares clave-valor. La salida de esa función de mapa que se ejecuta a través de todos los documentos en la base de datos genera un índice. En la aplicación de ejemplo Grocery Sync, estamos definiendo una Vista con una función de mapa que indexa los elementos pendientes por fecha de creación. Crear una Vista para..
iOS
|
1 2 3 4 5 6 7 |
[[laBaseDeDatos verNombrado: @"porFecha"] setMapBlock: MAPBLOCK({ id fecha = doc[@"fecha_creada"]; si (fecha) emite(fecha, nil); }) versión: @"1.1"]; |
Para iOS, primero estamos creando una 'Vista' en la base de datos. La base de datos es también un contenedor o espacio de nombres para las 'Vistas', por lo que decimos 'viewNamed: @"byDate"' donde si la View no existe la crearemos y si existe la devolveremos. El resto del bloque de código es para establecer su Map Block. Esta es una vista MapReduce, lo que significa que tiene una función Map. Obtenemos la fecha del documento mirando la propiedad 'created_at'. Y si el documento tiene una, la emitimos como clave. En la aplicación Grocery Sync, no se emite nada para el valor, porque en realidad va a volver a agarrar el propio documento para obtener el resto de los datos. La cadena de versión, '@"1.1″' al final se utiliza para comunicarse con la base de datos sobre si su función de mapa ha cambiado o no. Dado que la base de datos no puede decir cuando la función de mapa ha cambiado de una ejecución a la siguiente; una técnica es aumentar la cadena de versión para decirle a la base de datos que tire el índice actual y lo reconstruya.
Android
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
com.couchbase.lite.Ver viewItemsByDate = base de datos.getView(Cadena.formato("%s/%s", designDocName, byDateViewName)); viewItemsByDate.setMap(nuevo Mapeador() { @Anular público void mapa(Mapa(Cadena, Objeto) documento, Emisor emisor) { Objeto createdAt = documento.consiga("fecha_creada"); si (createdAt != null) { emisor.emite(createdAt.toString(), null); } } }, "1.0"); |
Esta es la versión Java-Android en la que, de forma similar a iOS, llamamos a 'database.getView()' y luego creamos la 'función map' utilizando alguna sintaxis de clase interna que en Java existe como un objeto 'Mapper()'. Los índices son capaces de ser actualizados en la demanda y la información útil son capaces de ser extraídos del documento que desea indexar donde entonces la clave-valor puede ser emitido. Cada vez que algo cambia, la función map se alimenta de 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 que están abreviados por 'clave' y como la clave es la marca de tiempo, los elementos estarán ordenados 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. De este modo, cuando realices una consulta y tengas una fila en ella, podrás utilizarla para volver al documento y recuperar toda la fila de la base de datos si lo deseas. La idea aquí es que una vez que tengas este índice, lo consultes. La consulta se hace diciendo: "Quiero todas las entradas del índice con una clave en particular, o un conjunto de claves, o un rango de claves".
[5] Consultas
Las consultas pueden entonces buscar un rango de filas desde una vista, y utilizar las claves y 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 manejando la tabla desde una consulta de vista mediante la creación de una consulta que se ordena por fecha descendente donde los elementos más recientes se muestran primero.
iOS
|
1 2 3 4 5 6 7 8 9 |
CBLLiveQuery* consulta = [[[base de datos verNombrado:@"porFecha"] consulta] asLiveQuery]; consulta.descendente = SÍ; // Introduce la consulta en el CBLUITableSource, que la utiliza para manejar la tabla. // (El CBLUITableSource utiliza KVO para observar la propiedad .rows de la consulta). auto.fuente de datos.consulta = consulta; auto.fuente de datos.labelProperty = @"texto"; |
Con los artículos en el índice, queremos usar ese índice para manejar la vista de tabla que es la UI principal de Grocery Sync. En iOS, generamos una consulta que es 'viewNamed: @"byDate"' y llamamos a query que crea la consulta sobre ella. Luego vemos 'LiveQuery' donde es un subconjunto especial de consulta que realmente rastreará la Vista en el tiempo. Establecemos la propiedad "descendente" en la consulta a "sí", ya que queremos obtener las filas en orden descendente de las fechas en tener los elementos más recientes creados en la parte superior. Por último, con el código específico de iOS, le decimos al 'dataSource' acerca de la consulta e indicamos qué propiedad mostrar como etiqueta en la vista de tabla.
Android
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
liveQuery = ver.createQuery().toLiveQuery(); liveQuery.addChangeListener(nuevo LiveQuery.ChangeListener() { público void cambiado(final LiveQuery.Evento de cambio evento) { runOnUiThread(nuevo Ejecutable() { público void ejecute() { grocerySyncArrayAdapter.borrar(); para (Iterador(Fila de consulta) it = evento.getRows(); it.hasNext();) { grocerySyncArrayAdapter.añada(it.siguiente()); } grocerySyncArrayAdapter.notifyDataSetChanged(); |
Del mismo modo, la versión Java-Android se inicia de la misma manera donde se crea una consulta haciendo que la vista llame a 'toLiveQuery()' sobre ella para generar el 'liveQuery'. Y luego se ejecuta 'addChangeListener()' en esa liveQuery, seguido del método 'changed()' que se llama para las actualizaciones de la Query, que es cada vez que cambia el resultado de esa Query. Y la salida para cuando se ejecuta la consulta es un array de 'QueryRows' donde cada QueryRow es un objeto y tiene propiedades como Key y Value y DocumentID pero también una propiedad document que cargará el documento de vuelta de la base de datos. Está pasando por un 'Iterator()' para obtener todas las filas de la consulta y añadirlas al 'grocerySyncArrayAdapter' que es una clase personalizada que tiene para almacenar el conjunto de datos.
[6] LiveQuery
Podemos pensar en LiveQuery como una envoltura alrededor de la consulta que escucha las notificaciones de cambio de la base de datos. Así, cuando la base de datos cambie, el LiveQuery lanzará de nuevo la consulta, reejecutándola en segundo plano de forma asíncrona. Y entonces comparará los resultados de la consulta con los resultados anteriores que ya tenía. Si los resultados han cambiado, el LiveQuery señalará sus propios eventos de notificación que la aplicación puede manejar en consecuencia, como redibujar la interfaz de usuario basada en esa nueva consulta. A continuación, el código ilustra cómo mostrar las celdas de la tabla para..
iOS
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
- (void)couchTableSource:(CBLUITableFuente*)fuente willUseCell:(UITableViewCell*)célula paraFila:(CBLQueryRow*)fila { // Establecer el fondo de la celda y la fuente: ......... // Configurar el contenido de la celda. La función Map (arriba) copia las propiedades del doc. // en su valor, para que podamos leerlos sin tener que cargar el documento. NSDictionary* rowValue = fila.valor; BOOL comprobado = [rowValue[@"comprobar"] boolValue]; si (comprobado) { célula.etiqueta de texto.textColor = [UIColor grayColor]; célula.imagenVer.imagen = [UIImage imageNamed:@"comprobado"]; } si no { célula.etiqueta de texto.textColor = [UIColor negroColor]; célula.imagenVer.imagen = [UIImage imageNamed: @"desmarcado"]; } // cell.textLabel.text ya está establecido, gracias a la configuración de labelProperty |
Para la aplicación de ejemplo aquí, el 'CBLUITableSource' de Couchbase Lite actuará como intermediario entre el LiveQuery y el UITableView, recibiendo notificaciones de cambios y por tanto manejando la tabla basada en un Query. También actúa como objeto fuente de datos para la tableView, lo que significa que es el objeto al que la tableView va a pedir que le proporcione todos los datos de las filas. Tu objeto 'controlador' se convierte entonces en el delegado del UITableView donde recibirá notificaciones del TableView sobre cuando el usuario pulse en una de las filas.
Android
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
público Ver getView(int posición, Ver itemView, VerGrupo padre) { //... Vista de texto etiqueta = ((ViewHolder)itemView.getTag()).etiqueta; Fila de consulta fila = getItem(posición); RevisiónGuardada currentRevision = fila.getDocument().getCurrentRevision(); // Casilla de verificación Objeto consulte = (Objeto) currentRevision.getProperty("comprobar"); booleano isGroceryItemChecked = falso; si (consulte != null && consulte instanceof Booleano) isGroceryItemChecked = ((Booleano)consulte).booleanValue(); // Texto Cadena groceryItemText = (Cadena) currentRevision.getProperty("texto"); etiqueta.setText(groceryItemText); |
Aquí está el equivalente de Android en la clase Grocery Sync Adapter. Obtiene la 'QueryRow' llamando a 'getItem()' basándose en el número de fila de la tabla. A continuación, obtiene el documento de la fila de consulta y obtiene su revisión actual. Luego utiliza las propiedades check y text para rellenar los UI Controls 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 cambiar la marca de verificación. A continuación se ilustra cómo se hace esto haciendo referencia a la QueryRow en un índice particular y recuperar el documento fuera de él.
iOS
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { // Pedir a CBLUITableSource la fila de consulta correspondiente, y obtener su documento: CBLQueryRow *fila = [auto.fuente de datos rowAtIndex:indexPath.fila]; CBLDocument *doc = fila.documento; // Alternar la propiedad 'checked' del documento: NSMutableDictionary *docContenido = [doc.propiedades mutableCopy]; BOOL wasChecked = [docContenido[@"comprobar"] boolValue]; docContenido[@"comprobar"] = @(!wasChecked); // Guardar cambios: NSError* error; si (![doc.currentRevision putProperties: docContenido error: &error]) { [auto showErrorAlert: @"Error al actualizar el elemento" paraError: error]; } } |
Esta es una llamada a un método sobre el propio UITableView en su delegado. Diciendo que una fila fue seleccionada lo que significa 'TAPPED' Así que va a ir a la fuente de datos. Que es el objeto UI Table Source, y le preguntará por la fila de consulta en ese índice, y obtendrá el documento. Así que ahora básicamente está haciendo un ciclo de Lectura, Escritura, Modificación en ese documento. Donde obtiene las propiedades... hace una copia mutable de las propiedades donde ahora tenemos un diccionario mutable donde podemos actualizar. Lee la propiedad verificada como un booleano, y la escribe de nuevo como lo contrario. Así que esto es invertir el valor booleano de la propiedad comprobada. Luego llama a putProperties al final para guardar ese valor de nuevo.
Android
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
público void onItemClick(AdaptadorVer(?) adaptadorVer, Ver ver, int posición, largo id) { Fila de consulta fila = (Fila de consulta) adaptadorVer.getItemAtPosition(posición); Documento documento = fila.getDocument(); Mapa(Cadena, Objeto) nuevasPropiedades = nuevo HashMap(Cadena, Objeto)(documento.getProperties()); booleano comprobado = ((Booleano) nuevasPropiedades.consiga("comprobar")).booleanValue(); nuevasPropiedades.poner("comprobar", !comprobado); pruebe { documento.putProperties(nuevasPropiedades); grocerySyncArrayAdapter.notifyDataSetChanged(); } captura (Excepción e) { |
Pasando ahora a la versión de Android, tenemos un 'onItemClick()' que es llamado por la GUI de Android. Se va a obtener su QueryRow en esa posición, obtener el documento, obtener las propiedades y, a continuación, poner las propiedades. En las APIs de Java es idiomático usar excepciones donde no lo es en Objective C, así que esto tiene un try-catch envuelto alrededor del manejador al guardar el documento. Si en la Ventana de Tiempo algo más modificara el documento, probablemente el replicador, entonces esto arrojaría un error. Obtendrías un error de conflicto.
A continuación nos adentraremos en la clase Couchbase Lite Replicator y en la clase Portal para desarrolladores de Couchbase Mobile es un buen recurso para empezar.
A partir de ahí nos sumergiremos en Pasarela de sincronización Couchbase en nuestra sesión 102, en la que hablaré de "Cómo añadir Secure Sync a tu aplicación móvil". Completaremos el día con cómo activar la función Peer-to-Peer de Couchbase Mobile en la sesión 103 con Austin Gonyoudonde se pueden crear experiencias sociales únicas dentro de la aplicación mediante "Building a Peer-to-Peer App with Couchbase Mobile".

