Con nuevas herramientas como Android Public Beta Testing y Testflight para iOS, enviar nuevas actualizaciones de una aplicación móvil nativa con mayor regularidad es una tendencia creciente.
Además de cambiar la lógica empresarial y los modelos de datos con mayor regularidad, también es necesario dar soporte a las versiones anteriores. A diferencia de un sitio web, en el que el usuario siempre accede a la última versión desde el navegador, las aplicaciones móviles pueden permanecer en el dispositivo de un usuario durante un largo periodo de tiempo sin actualizarse.
En este tutorial, aprenderás cómo enviar una actualización a tu aplicación Android con Couchbase Mobile. Utilizarás un webhook de Sync Gateway para importar datos de una API de terceros bajo demanda (en este caso la API de Google Places) con Node.js. En la primera instancia, la aplicación Android sincronizará todos los datos y en una versión posterior de la aplicación, sólo extraerá un subconjunto de ellos. Al cambiar las reglas de acceso en la función de sincronización, utilizará la pasarela de sincronización resincronizar para reconstruir las reglas de acceso de acuerdo con la función de sincronización actualizada.
Empecemos.
El orden en el que reunirás los distintos componentes es el siguiente:
- Instalación de Sync Gateway
- Configuración del Webhook
- Creación del servidor de aplicaciones
- Creación de la aplicación Android
Primeros pasos
Descargue Sync Gateway y descomprima el archivo:
http://www.couchbase.com/nosql-databases/downloads#Couchbase_Mobile
Puede encontrar el binario de Sync Gateway en la carpeta papelera y ejemplos de archivos de configuración en la carpeta ejemplos carpeta. Copie la carpeta basic-walrus-bucket.json en la raíz del proyecto:
1 |
$ cp /Descargas/couchbase-sincronizar-pasarela/ejemplos/básico-morsa-cubo.json /ruta/a/proj/sincronizar-pasarela-config.json |
Inicie Sync Gateway:
1 |
$ ~/Descargas/couchbase-sincronizar-pasarela/papelera/sync_gateway |
Webhook de la pasarela de sincronización
El webhook se define en el archivo de configuración que creó anteriormente y toma un archivo url para POST y un función de filtro (opcional). Añada el manejadores de eventos campo en sync-gateway-config.json con las siguientes propiedades:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
{ "log": ["*"], "bases de datos": { "db": { "cubo": "por defecto", "servidor": "http://localhost:8091", "usuarios": { "INVITADO": { "desactivado": falso, "admin_canales": ["*"] } }, "event_handlers": { "documento_cambiado": [ { "manipulador": "webhook", "url": "http://localhost:8000/sync_request", "filtro": `función(doc) { si (doc.tipo == "perfil") { devolver verdadero; } devolver falso; }` } ] } } } } |
He aquí lo que hace cada nuevo campo:
- manipulador: Está especificando que el tipo de evento sea webhook
- url: La url a la que enviar la petición POST con el documento en el cuerpo del mensaje.
- filtro: Una función escrita en JavaScript para activar el webhook sólo si devuelve verdadero. Tenga en cuenta que el webhook sólo se ejecutará si el documento tiene una etiqueta tipo igual a perfil
Guarde los cambios y reinicie Sync Gateway:
1 |
$ ~/Descargas/couchbase-sincronizar-pasarela/papelera/sincronizar_pasarela ./sincronizar-pasarela-config.json |
Servidor de aplicaciones
En esta sección, utilizarás Node.js y el framework Express para configurar un App Server que gestione el webhook. En el mismo directorio, instala los módulos de Node.js necesarios:
1 |
npm instale express cuerpo-analizador --guardar |
En un nuevo archivo llamado servidor.js añada el siguiente código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
var express = requiere(exprés); var bodyParser = requiere('body-parser'); var aplicación = express(); aplicación.utilice(bodyParser.json()); /** * Manejar la solicitud webhook Sync Gateway. * El cuerpo de la solicitud contiene el documento. */ aplicación.Correo electrónico:('/sync_request', función (consulte, res) { var documento = consulte.cuerpo; consola.registro('Manejar webhook con doc :: %s', JSON.stringify(documento)); res.sendStatus(200); }); var servidor = aplicación.escuche(8000, función () { var host = servidor.dirección().dirección; var puerto = servidor.dirección().puerto; consola.registro(App a la escucha en http://%s:%s, host, puerto); }); |
Inicie el servidor de aplicaciones Node.js ejecutando node servidor.js y guarde un documento en Sync Gateway utilizando la API REST para asegurarse de que el webhook funciona:
1 2 3 |
$ rizo -vX POST :4984/db/ -H Content-Type: application/json' -d '{"tipo": "perfil", "nombre": "james"}' |
Debería ver la siguiente salida en los registros del App Server:
¡Magnífico! El webhook está funcionando. A continuación, va a añadir algo de código para importar los datos JSON de la API de Lugares en Sync Gateway. Para ello, utilizará RxJS y Request. El código que maneja más de un evento o cálculo asíncrono se complica rápidamente. RxJS realiza estos cálculos ciudadanos de primera clase y proporciona un modelo que permite APIs legibles y componibles. Y el módulo Request es la librería de-facto para hacer las peticiones http en NodeJS más simples que nunca. En el mismo directorio, instala las dependencias:
1 |
$ npm instale solicitar rx --guardar |
Copia requestRx.js de este Repo de GitHub en la carpeta del proyecto. Simplemente estamos envolviendo la API Request en construcciones RxJS (flatMap, filter, subscribe...). Por ejemplo, en lugar de utilizar solicitar.obtener
utilizarás requestRx.get
.
Abra un nuevo archivo llamado sync.jsrequieren el requestRx y Rx y añada el siguiente código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
var requestRx = requiere('./requestRx.js'); var Rx = requiere(rx); const clave_api = 'AIzaSyD4e6ZUIc9G2AxKansIUKa0enFzWZy5h8w'; const url = https://maps.googleapis.com/maps/api/place; const pasarela = http://localhost:4985/db; var SyncRequest = { syncRequest: función(ciudad) { consola.registro('ciudad para sincronizar con %s', ciudad); // 1. Buscar lugares requestRx.consiga(`${url}/búsqueda de texto/json?clave=${clave_api}&consulta=restaurantes+en+${ciudad}`) .suscríbase a((res) => { var lugares = JSON.analizar(res.cuerpo).resultados; lugares = lugares.mapa(función (lugar) { lugar.ciudad = ciudad; lugar._id = lugar.lugar_id; borrar lugar.lugar_id; devolver lugar; }); var lugaresStream = Rx.Observable.fromArray(lugares); // 2. Enviar los Lugares en bloque a Sync Gateway requestRx({uri: `${pasarela}/_bulk_docs`, método: POST, json: {docs: lugares}}) .flatMap((docsRes) => { var docsStream = Rx.Observable.fromArray(docsRes.cuerpo); // Fusionar la fotoreferencia del lugar con el doc id y rev devolver Rx.Observable.zip(lugaresStream, docsStream, (lugar, doc) => { devolver { id: doc.id, rev: doc.rev, ref: lugar.fotos ? lugar.fotos[0].foto_referencia : 'CmRdAAAAA6MaWi5PIUhFumEYbHiM8IWHhJJvw1ss11QH1prE_x0PnUgyiyIiQmSNvfMu1lztLAA0mNdZa5Mr32ho5hE5nOKDAdOfVKcw4kLe0LKdDoYFENmRR1FE4AosTUhBvNCvEhB5HYf69MG389U27lkhrcPqGhSDbG7UhU9buWSEn2DRpy8E_R3oAg' } }); }) .flatMap((doc) => { // 3. Obtener la foto jpg binaria utilizando la propiedad ref (es decir, fotorreferencia) var opciones = { uri: `${url}/foto?clave=${clave_api}&maxwidth=400&fotorreferencia=${doc.ref}`, codificación: null }; devolver requestRx.consiga(opciones) .flatMap((foto) => { // 4. Guardar la foto como archivo adjunto en el documento correspondiente devolver requestRx({ uri: `${pasarela}/${doc.id}/foto?rev=${doc.rev}`, método: PUT, cabeceras: {Tipo de contenido: imagen/jpg}, cuerpo: foto.cuerpo }) }) }) .suscríbase a((res) => { }); }); } }; módulo.exportaciones = SyncRequest; |
Esto es lo que ocurre paso a paso:
- Obtener los Lugares que coinciden con la consulta restaurantes en Londres. Utilice la función de interpolación de cadenas ES 6 en la url.
- En a graneldocs es muy práctico para importar grandes conjuntos de datos a una instancia de Sync Gateway. Encontrará más información al respecto en docs.
- Después de guardar el documento, guardas la foto como un archivo adjunto, primero debes obtener la imagen de la API de Lugares. Fíjate en el icono codificación se establece en null. Esto es requerido por el módulo Request para cualquier cuerpo de respuesta que no sea una cadena. Más información en la sección Solicitar documentos.
- Debe indicar a Sync Gateway en qué documento (especificando el identificador del documento) y revisión de ese documento (especificando el número de revisión) desea guardar este archivo adjunto.
Observe que en la última línea está exportando el archivo SyncRequest objeto. En servidor.jsrequieren el sync.js y llamar al archivo syncRequest pasando en consecuencia el método ciudad campo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
var express = requiere(exprés); var bodyParser = requiere('body-parser'); var aplicación = express(); aplicación.utilice(bodyParser.json()); var sincronizar = requiere('./sync'); /** * Manejar la solicitud webhook Sync Gateway. * El cuerpo de la solicitud contiene el documento. */ aplicación.Correo electrónico:('/sync_request', función (consulte, res) { var documento = consulte.cuerpo; consola.registro('Manejar webhook con doc :: %s', JSON.stringify(documento)); sincronizar.syncRequest(documento.ciudad); res.sendStatus(200); }); var servidor = aplicación.escuche(8000, función () { var host = servidor.dirección().dirección; var puerto = servidor.dirección().puerto; consola.registro(App a la escucha en http://%s:%s, host, puerto); }); |
Habrá observado que sync.js está escrito con la sintaxis ES6. Ejecutar node servidor.js no funcionará y deberá instalar primero el Babel globalmente:
1 |
npm instale babel-nodo -g |
Ahora puede ejecutar el programa sync.js de forma independiente con el siguiente comando:
1 |
babel-nodo -e "require('./sync.js').syncRequest('London')" |
Abra el panel de administración para supervisar los documentos guardados en Sync Gateway.
http://localhost:4985/_admin/
Ahora debería ver 20 documentos en los que cada uno es un lugar de Londres:
Creación de la aplicación Android
En esta sección, aprenderás a utilizar el SDK de Google Maps en Android para obtener la ciudad en la que se encuentra actualmente el usuario. Con esa información, crearás un documento de tipo perfil para almacenar la ciudad actual.
Abra Android Studio y seleccione Iniciar un nuevo proyecto de Android Studio del Inicio rápido menú.
Nombre de la aplicación CityExplorerestablezca un dominio de empresa y una ubicación de proyecto adecuados y, a continuación, haga clic en Siguiente:
En el cuadro de diálogo Dispositivos Android de destino, asegúrese de marcar Teléfono y tabletaajuste el SDK mínimo a API 22: Android 5.1 (Lollipop) para ambos, y haga clic en Siguiente:
En la siguiente Añadir una actividad a Móvil seleccione Añadir Actividad en blanco y nombre la actividad Actividad principal:
Utilizará el proveedor de localización fusionado para recuperar la última ubicación conocida del dispositivo. En build.gradleañada la siguiente dependencia:
1 |
compilar com.google.android.gms:play-services:7.5.0 |
Añadir un permiso de solicitud de ubicación en AndroidManifest.xml en el manifestar Etiqueta XML:
1 |
En MainActivity.javaañade un nuevo método llamado buildGoogleApiClient para inicializar el SDK de la API de Google y llamar al método en onCreate:
1 2 3 4 5 6 7 8 |
protegido sincronizado void buildGoogleApiClient() { mGoogleApiClient = nuevo GoogleApiClient.Constructor(este) .addConnectionCallbacks(este) .addOnConnectionFailedListener(este) .addApi(LocationServices.API) .construya(); mGoogleApiClient.conecte(); } |
A continuación, hará Actividad principal aplicar la GoogleApiClient.ConnectionCallbacks y GoogleApiClient.OnConnectionFailedListener y recuperar la ubicación en onConnected:
1 2 3 4 5 |
@Anular público void onConnected(Paquete paquete) { Ubicación mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient); Geocodificador geocodificador = nuevo Geocodificador(este, Local.getDefault()); Lista |
1 |
Ejecute la aplicación y debería ver la ciudad actual en el centro de la pantalla:
A continuación, añadirás Couchbase Lite a tu proyecto. En build.gradle añade lo siguiente:
1 2 3 4 5 6 7 |
// solución para el problema de "archivos duplicados durante el empaquetado del APK // véase https://groups.google.com/d/msg/adt-dev/bl5Rc4Szpzg/wC8cylTWuIEJ packagingOptions { excluir META-INF/ASL2.0 excluir META-INF/LICENCIA excluir META-INF/NOTICE } |
Añada el paquete Couchbase Lite Android en build.gradle:
1 |
compilar com.couchbase.lite:couchbase-lite-android:1.1.0 |
Crear un SyncManager.java y añadir el código para iniciar una replicación pull y push en modo continuo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
público clase SyncManager { privado estático final Cadena NOMBRE_BASE_DE_DATOS = "cityexplorer"; privado estático final Cadena SYNC_URL = "http://localhost:4984/db/"; privado estático final Cadena VISTA_CIUDADES = "getCities"; privado Contexto contexto; privado Director director; privado Base de datos base de datos; público SyncManager(Contexto contexto) { este.contexto = contexto; openDatabase(); } privado void openDatabase() { pruebe { director = nuevo Director(nuevo AndroidContext(contexto), Director.OPCIONES_POR_DEFECTO); } captura (IOException e) { e.printStackTrace(); } pruebe { base de datos = director.getDatabase(NOMBRE_BASE_DE_DATOS); } captura (CouchbaseLiteException e) { e.printStackTrace(); } startSync(); } privado void startSync() { URL url = null; pruebe { url = nuevo URL(SYNC_URL); } captura (MalformedURLException e) { e.printStackTrace(); } Replicación pulse = base de datos.createPushReplication(url); pulse.setContinuous(verdadero); pulse.iniciar(); Replicación tire de = base de datos.createPullReplication(url); tire de.setContinuous(verdadero); tire de.iniciar(); } público Base de datos getDatabase() { devolver base de datos; } público void setDatabase(Base de datos base de datos) { este.base de datos = base de datos; } } |
Ejecute la aplicación y eche un vistazo en LogCat, debería ver que la replicación se ha realizado correctamente. Pero no hay forma de consultar los documentos que se han sincronizado hasta ahora. Para ello, utilizarás una Vista Couchbase que indexará los documentos por su ciudad propiedad.
En SyncManager.javaañade el siguiente método:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
privado void registerViews() { Ver ciudadesVer = base de datos.getView(VISTA_CIUDADES); ciudadesVer.setMapReduce(nuevo Mapeador() { @Anular público void mapa(Mapa<Cadena, Objeto> documento, Emisor emisor) { si (documento.consiga("nombre") != null) { Lista<objeto anchura="300" altura="150"> clave = nuevo ArrayList<objeto>(); clave.añada(documento.consiga("ciudad")); emisor.emite(clave, null); } } }, nuevo Reductor() { @Anular público Objeto reducir(Lista<objeto> llaves, Lista<objeto> valores, booleano reducir) { devolver nuevo Entero(valores.talla()); } }, "7"); } A consulta que ver, añada otro método llamado <fuerte>consultaCiudades</fuerte> como así que: <pre> privado void consultaCiudades() { Consulta consulta = base de datos.getView(VISTA_CIUDADES).createQuery(); consulta.setGroupLevel(1); pruebe { QueryEnumerator enumeración = consulta.ejecute(); para (Fila de consulta fila : enumeración) { Sistema.fuera.println("Row is" + fila.getValue() + " y la tecla " + fila.getKey()); } } captura (CouchbaseLiteException e) { e.printStackTrace(); } } |
Y llamarlo después registerViews
en el openDatabase
Ejecute la aplicación y debería ver el número de plazas de cada ciudad en LogCat:También ha configurado la replicación push, pero aún no ha persistido ningún documento localmente. En el
onConnected
método de MainActivity.java añadirá código para persistir un nuevo documento localmente con un tipo igual a perfil y ciudad con el nombre de la ciudad devuelto por la api de localización fusionada.Antes de ejecutar la aplicación vamos a recapitular los diferentes componentes de esta arquitectura:Fíjate en que las flechas están agrupadas por colores:
- Naranja: Cuando un usuario abre la aplicación, el documento de perfil se mantiene localmente y se envía a Sync Gateway a través de la replicación push continua y activa un webhook.
- Azul: El App Server gestiona los webhooks e importa las plazas de la ciudad en cuestión a Sync Gateway.
- Verde: El usuario correspondiente recibe los documentos según las reglas de acceso definidas en la función de sincronización.
En Verde se producirá siempre algún tiempo después de que se abra la aplicación. Sería bueno supervisar la consulta de ciudades en la aplicación Android para controlar los datos a medida que se extraen de Sync Gateway. Para ello se utilizará un LiveQuery en lugar de Consulta. Actualice el consultaCiudades a lo siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
privado void consultaCiudades() { final Consulta consulta = base de datos.getView(VISTA_CIUDADES).createQuery(); consulta.setGroupLevel(1); LiveQuery liveQuery = consulta.toLiveQuery(); liveQuery.addChangeListener(nuevo LiveQuery.ChangeListener() { @Anular público void cambiado(LiveQuery.Evento de cambio evento) { pruebe { QueryEnumerator enumeración = consulta.ejecute(); para (Fila de consulta fila : enumeración) { Registro.d("CityExplorer", "Row is" + fila.getValue() + " y la tecla " + fila.getKey()); } } captura (CouchbaseLiteException e) { e.printStackTrace(); } } }); liveQuery.iniciar(); } |
Hasta ahora debería tener 20 documentos en Sync Gateway y puede comprobarlo en el Admin Dashboard. Reinicie la aplicación y esto creará el documento de perfil, lo enviará a Sync Gateway para importar 20 lugares alrededor de la ubicación del usuario. Esto resultará en la adición de 20 nuevos lugares a Sync Gateway:Observe que el Sync Gateway tiene ahora 41 documentos (el documento de perfil y 20 lugares más en la ubicación del dispositivo que ejecuta la aplicación) y que el LiveQuery de la aplicación Android devuelve los mismos documentos por ciudad (en este ejemplo 20 en Londres y 20 en Saint-Étienne-de-Tinée).
Cambio de la función de sincronización
Hasta ahora ha seguido el tutorial sin crear usuarios. Es decir, todos los dispositivos móviles que se conectan a Sync Gateway reciben los mismos documentos. Esto no es manejable a medida que crece el tamaño de los datos. Además, si un usuario se encuentra en Saint-Étienne-de-Tinée, no es necesario sincronizar lugares de Londres con su dispositivo. Para añadir este filtrado de documentos por dispositivo hay que hacer dos cosas:
- Crear usuarios y autenticarse como usuario particular.
- Actualice la función de sincronización para que un usuario concreto tenga acceso a los documentos de la ciudad en la que se encuentra.
Para crear un usuario se utilizará curl para enviar una petición POST en el puerto admin:
1 2 3 |
$ rizo -vX POST :4985/db/Usuario/ -H Content-Type: application/json' -d '{"nombre": "james", "password": "letmein"}' |
En la aplicación para Android, actualice el startSync
método en SyncManager.java con el autenticador básico pasando las mismas credenciales:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
privado void startSync() { URL url = null; pruebe { url = nuevo URL(SYNC_URL); } captura (MalformedURLException e) { e.printStackTrace(); } Autentificador autentificador = nuevo BasicAuthenticator("james", "letmein"); Replicación pulse = base de datos.createPushReplication(url); pulse.setContinuous(verdadero); pulse.setAuthenticator(autentificador); pulse.iniciar(); Replicación tire de = base de datos.createPullReplication(url); tire de.setContinuous(verdadero); tire de.setAuthenticator(autentificador); tire de.iniciar(); } |
Navegue hasta el Sincroniza en el Panel de control del administrador:http://localhost:4985/_admin/db/db/syncUpdate la función de sincronización con lo siguiente:
1 2 3 4 5 6 7 8 |
función(doc, oldDoc) { si (doc.tipo == "perfil") { canal(doc.ciudad); acceda a(doc._id, doc.ciudad); } si no si (doc.tipo == "ciudad") { canal(doc.ciudad); } } |
A continuación, haga clic en el botón Modo de vista previa en directo y el banner superior se volverá amarillo.Esto significa que puede probar la Función de Sincronización en documentos aleatorios pero nada se despliega todavía, esto es sólo para probar el resultado. Utilice el botón aleatorio para ver la salida de canal/acceso dado un documento aleatorio como entrada:
Para implementar esta nueva función de sincronización, lo mejor es detener Sync Gateway, actualizar el archivo de configuración y volver a iniciarlo. Una vez reiniciado Sync Gateway, desinstale la aplicación del dispositivo y vuelva a ejecutarla. Debería observar que se replican los mismos 40 documentos. De hecho, ha actualizado la función de sincronización, pero los documentos que persistían hasta ahora no han tenido la oportunidad de ejecutarse a través de la nueva función de sincronización. Para solucionarlo, utilice la función resincronizar en la sección siguiente.
Resincronizar
Cada vez que modifique la función de sincronización, asegúrese de llamar a la función _resync para volver a calcular las reglas de acceso a los canales para todos los documentos existentes en la base de datos:
1 |
$ rizo -vX POST http://localhost:4985/db/_resync |
Borra la aplicación y reiníciala. Esta vez, sólo verá los 20 lugares en LogCat porque el usuario se encuentra en Saint-Étienne-de-Tinée y ese usuario no tiene acceso al Londres y, por tanto, no sacará esos documentos.
Conclusión
En este tutorial, aprendió a utilizar un webhook para importar documentos desde una API de terceros a Sync Gateway mediante un App Server. También utilizó el resincronizar al cambiar su función de sincronización para actualizar el acceso a los canales.