Com novas ferramentas, como o Android Public Beta Testing e o Testflight para iOS, o envio de novas atualizações de um aplicativo móvel nativo com mais regularidade é uma tendência crescente.
Além de alterar a lógica comercial e os modelos de dados com mais regularidade, também é necessário oferecer suporte a versões anteriores. Ao contrário de um site em que o usuário sempre acessa a versão mais recente no navegador, os aplicativos móveis podem permanecer no dispositivo do usuário por um longo período de tempo sem serem atualizados.
Neste tutorial, você aprenderá a enviar uma atualização para seu aplicativo Android com o Couchbase Mobile. Você usará um webhook do Sync Gateway para importar dados de uma API de terceiros sob demanda (neste caso, a API do Google Places) com o Node.js. Na primeira instância, o aplicativo Android sincronizará todos os dados e, em uma versão subsequente do aplicativo, extrairá apenas um subconjunto deles. Ao alterar as regras de acesso na função Sync, você usará o Sync Gateway ressincronização para reconstruir as regras de acesso de acordo com a Sync Function atualizada.
Vamos começar!
A ordem em que você montará os diferentes componentes é a seguinte:
- Instalação do Sync Gateway
- Configuração do webhook
- Criação do servidor de aplicativos
- Criação do aplicativo Android
Primeiros passos
Faça o download do Sync Gateway e descompacte o arquivo:
https://www.couchbase.com/nosql-databases/downloads#Couchbase_Mobile
Você pode encontrar o binário do Sync Gateway no diretório caixa e exemplos de arquivos de configuração na pasta exemplos pasta. Copie o basic-walrus-bucket.json na raiz do seu projeto:
|
1 |
$ cp /Downloads/couchbase-sync-gateway/examples/basic-walrus-bucket.json /path/to/proj/sync-gateway-config.json |
Inicie o Sync Gateway:
|
1 |
$ ~/Downloads/couchbase-sync-gateway/bin/sync_gateway |
Webhook do gateway de sincronização
O webhook é definido no arquivo de configuração que você criou acima e recebe um url para o POST e um função de filtro (opcional). Adicione o manipuladores de eventos campo em sync-gateway-config.json com as seguintes propriedades:
|
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": ["*"], "databases": { "db": { "bucket": "default", "server": "https://localhost:8091", "users": { "GUEST": { "disabled": false, "admin_channels": ["*"] } }, "event_handlers": { "document_changed": [ { "handler": "webhook", "url": "https://localhost:8000/sync_request", "filter": `function(doc) { if (doc.type == "profile") { return true; } return false; }` } ] } } } } |
Veja a seguir o que cada novo campo está fazendo:
- manipulador: Você está especificando o tipo de evento a ser webhook
- url: O URL para o qual enviar a solicitação POST com o documento no corpo da mensagem
- filtro: Uma função escrita em JavaScript para acionar o webhook somente se ele retornar verdadeiro. Observe aqui que o webhook será executado somente se o documento tiver um tipo propriedade igual a perfil
Salve as alterações e reinicie o Sync Gateway:
|
1 |
$ ~/Downloads/couchbase-sync-gateway/bin/sync_gateway ./sync-gateway-config.json |
Servidor de aplicativos
Nesta seção, você usará o Node.js e a estrutura Express para configurar um App Server para lidar com o webhook. No mesmo diretório, instale os módulos Node.js necessários:
|
1 |
npm install express body-parser --save |
Em um novo arquivo chamado servidor.js adicione o seguinte 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 = require('express'); var bodyParser = require('body-parser'); var app = express(); app.use(bodyParser.json()); /** * Handle the Sync Gateway webhook request. * The request body contains the document. */ app.post('/sync_request', function (req, res) { var document = req.body; console.log('Handle webhook with doc :: %s', JSON.stringify(document)); res.sendStatus(200); }); var server = app.listen(8000, function () { var host = server.address().address; var port = server.address().port; console.log('App listening at https://%s:%s', host, port); }); |
Inicie o servidor de aplicativos Node.js executando servidor de nó.js e salve um documento no Sync Gateway usando a API REST para garantir que o webhook esteja funcionando:
|
1 2 3 |
$ curl -vX POST :4984/db/ -H 'Content-Type: application/json' -d '{"type": "profile", "name": "james"}' |
Você deverá ver a seguinte saída nos logs do App Server:

Excelente! O webhook está funcionando. Em seguida, você adicionará algum código para importar os dados JSON da API Places para o Sync Gateway. Para isso, você usará o RxJS e o Request. O código que lida com mais de um evento ou computação assíncrona se complica rapidamente. O RxJS faz esses cálculos cidadãos de primeira classe e fornece um modelo que permite APIs legíveis e compostas. E o módulo Request é a biblioteca de fato para tornar as solicitações http no NodeJS mais simples do que nunca. No mesmo diretório, vá em frente e instale as dependências:
|
1 |
$ npm install request rx --save |
Cópia requestRx.js a partir deste Repositório do GitHub na pasta do seu projeto. Estamos simplesmente envolvendo a API de solicitação em construções RxJS (flatMap, filter, subscribe...). Por exemplo, em vez de usar request.getvocê usará requestRx.get.
Abra um novo arquivo chamado sync.js, exigem que o requestRx e Rx e adicione o seguinte 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 = require('./requestRx.js'); var Rx = require('rx'); const api_key = 'AIzaSyD4e6ZUIc9G2AxKansIUKa0enFzWZy5h8w'; const url = 'https://maps.googleapis.com/maps/api/place'; const gateway = 'https://localhost:4985/db'; var SyncRequest = { syncRequest: function(city) { console.log('city to sync with %s', city); // 1. Search for Places requestRx.get(`${url}/textsearch/json?key=${api_key}&query=restaurants+in+${city}`) .subscribe((res) => { var places = JSON.parse(res.body).results; places = places.map(function (place) { place.city = city; place._id = place.place_id; delete place.place_id; return place; }); var placesStream = Rx.Observable.fromArray(places); // 2. Send the Places in bulk to Sync Gateway requestRx({uri: `${gateway}/_bulk_docs`, method: 'POST', json: {docs: places}}) .flatMap((docsRes) => { var docsStream = Rx.Observable.fromArray(docsRes.body); // Merge the place's photoreference with the doc id and rev return Rx.Observable.zip(placesStream, docsStream, (place, doc) => { return { id: doc.id, rev: doc.rev, ref: place.photos ? place.photos[0].photo_reference : 'CmRdAAAAA6MaWi5PIUhFumEYbHiM8IWHhJJvw1ss11QH1prE_x0PnUgyiyIiQmSNvfMu1lztLAA0mNdZa5Mr32ho5hE5nOKDAdOfVKcw4kLe0LKdDoYFENmRR1FE4AosTUhBvNCvEhB5HYf69MG389U27lkhrcPqGhSDbG7UhU9buWSEn2DRpy8E_R3oAg' } }); }) .flatMap((doc) => { // 3. Get the binary jpg photo using the ref property (i.e. photoreference) var options = { uri: `${url}/photo?key=${api_key}&maxwidth=400&photoreference=${doc.ref}`, encoding: null }; return requestRx.get(options) .flatMap((photo) => { // 4. Save the photo as an attachment on the corresponding document return requestRx({ uri: `${gateway}/${doc.id}/photo?rev=${doc.rev}`, method: 'PUT', headers: {'Content-Type': 'image/jpg'}, body: photo.body }) }) }) .subscribe((res) => { }); }); } }; module.exports = SyncRequest; |
Veja a seguir o que está acontecendo passo a passo:
- Obter os Locais que correspondem à consulta restaurantes em Londres. Use o recurso de interpolação de strings do ES 6 na url.
- O volumedocumentos é muito conveniente para a importação de grandes conjuntos de dados para uma instância do Sync Gateway. Leia mais sobre ele na seção documentos.
- Depois de salvar o documento e salvar a foto como anexo, você deve primeiro obter a imagem da API do Places. Observe a codificação é definida como nulo. Isso é exigido pelo módulo Request para qualquer corpo de resposta que não seja uma string. Leia mais sobre isso na seção Solicitar documentos.
- Você deve informar ao Sync Gateway em qual documento (especificando o ID do documento) e a revisão desse documento (especificando o número da revisão) salvar esse anexo.
Observe que na última linha você está exportando o SyncRequest objeto. Em servidor.js, exigem que o sync.js e chamar o arquivo syncRequest passando o método cidade 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 = require('express'); var bodyParser = require('body-parser'); var app = express(); app.use(bodyParser.json()); var sync = require('./sync'); /** * Handle the Sync Gateway webhook request. * The request body contains the document. */ app.post('/sync_request', function (req, res) { var document = req.body; console.log('Handle webhook with doc :: %s', JSON.stringify(document)); sync.syncRequest(document.city); res.sendStatus(200); }); var server = app.listen(8000, function () { var host = server.address().address; var port = server.address().port; console.log('App listening at https://%s:%s', host, port); }); |
Você deve ter notado que sync.js é escrito com a sintaxe ES6. Em execução servidor de nó.js não funcionará e você deve primeiro instalar o Babel módulo globalmente:
|
1 |
npm install babel-node -g |
Agora você pode executar o sync.js de forma independente com o seguinte comando:
|
1 |
babel-node -e "require('./sync.js').syncRequest('London')" |
Abra o Admin Dashboard para monitorar os documentos que foram salvos no Sync Gateway.
https://localhost:4985/_admin/
Agora você deve ver 20 documentos, sendo que cada um deles é um local em Londres:

Criação do aplicativo Android
Nesta seção, você aprenderá a usar o SDK do Google Maps no Android para obter a cidade em que o usuário está localizado no momento. Com essas informações, você criará um documento do tipo perfil para armazenar a cidade atual.
Abra o Android Studio e selecione Iniciar um novo projeto do Android Studio do Início rápido menu.
Nomear o aplicativo CityExplorerdefina um domínio da empresa e um local do projeto adequados e clique em Próximo:

Na caixa de diálogo Target Android Devices (Dispositivos Android de destino), certifique-se de marcar Telefone e tabletdefina o SDK mínimo como API 22: Android 5.1 (Lollipop) para ambos, e clique em Próximo:

No dia seguinte Adicionar uma atividade ao Mobile selecione Add Atividade em branco e nomeie a atividade Atividade principal:

Você usará o provedor de localização fundido para recuperar a última localização conhecida do dispositivo. Em build.gradleadicione a seguinte dependência:
|
1 |
compile 'com.google.android.gms:play-services:7.5.0' |
Adicionar uma permissão de solicitação de localização em AndroidManifest.xml no manifesto Etiqueta XML:
|
1 |
Em MainActivity.javaadicione um novo método chamado criar cliente GoogleApi para inicializar o SDK da API do Google e chamar o método em onCreate:
|
1 2 3 4 5 6 7 8 |
protected synchronized void buildGoogleApiClient() { mGoogleApiClient = new GoogleApiClient.Builder(this) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .addApi(LocationServices.API) .build(); mGoogleApiClient.connect(); } |
Em seguida, você fará Atividade principal implementar o GoogleApiClient.ConnectionCallbacks e GoogleApiClient.OnConnectionFailedListener e recuperar o local em onConnected:
|
1 2 3 4 5 |
@Override public void onConnected(Bundle bundle) { Location mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient); Geocoder geocoder = new Geocoder(this, Locale.getDefault()); List |
|
1 |
Execute o aplicativo e você verá a cidade atual exibida no meio da tela:

Em seguida, você adicionará o Couchbase Lite ao seu projeto. Em build.gradle adicione o seguinte:
|
1 2 3 4 5 6 7 |
// workaround for "duplicate files during packaging of APK" issue // see https://groups.google.com/d/msg/adt-dev/bl5Rc4Szpzg/wC8cylTWuIEJ packagingOptions { exclude 'META-INF/ASL2.0' exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE' } |
Adicione o pacote do Couchbase Lite para Android em build.gradle:
|
1 |
compile 'com.couchbase.lite:couchbase-lite-android:1.1.0' |
Criar um SyncManager.java e adicione o código para iniciar uma replicação pull e push no modo contínuo:
|
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 |
public class SyncManager { private static final String DATABASE_NAME = "cityexplorer"; private static final String SYNC_URL = "https://localhost:4984/db/"; private static final String CITIES_VIEW = "getCities"; private Context context; private Manager manager; private Database database; public SyncManager(Context context) { this.context = context; openDatabase(); } private void openDatabase() { try { manager = new Manager(new AndroidContext(context), Manager.DEFAULT_OPTIONS); } catch (IOException e) { e.printStackTrace(); } try { database = manager.getDatabase(DATABASE_NAME); } catch (CouchbaseLiteException e) { e.printStackTrace(); } startSync(); } private void startSync() { URL url = null; try { url = new URL(SYNC_URL); } catch (MalformedURLException e) { e.printStackTrace(); } Replication push = database.createPushReplication(url); push.setContinuous(true); push.start(); Replication pull = database.createPullReplication(url); pull.setContinuous(true); pull.start(); } public Database getDatabase() { return database; } public void setDatabase(Database database) { this.database = database; } } |
Execute o aplicativo e dê uma olhada no LogCat, e você verá que a replicação foi bem-sucedida. Mas não há como consultar os documentos que foram sincronizados até o momento. Para fazer isso, você usará um Couchbase View que indexará os documentos por seus cidade propriedade.
Em SyncManager.javaadicione o seguinte 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 |
private void registerViews() { View citiesView = database.getView(CITIES_VIEW); citiesView.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { if (document.get("name") != null) { List<object width="300" height="150"> key = new ArrayList<object>(); key.add(document.get("city")); emitter.emit(key, null); } } }, new Reducer() { @Override public Object reduce(List<object> keys, List<object> values, boolean rereduce) { return new Integer(values.size()); } }, "7"); } To query that view, add another method called <strong>queryCities</strong> like so: <pre> private void queryCities() { Query query = database.getView(CITIES_VIEW).createQuery(); query.setGroupLevel(1); try { QueryEnumerator enumeration = query.run(); for (QueryRow row : enumeration) { System.out.println("Row is " + row.getValue() + " and key " + row.getKey()); } } catch (CouchbaseLiteException e) { e.printStackTrace(); } } |
E o chamem depois registrar visualizações no openDatabase Execute o aplicativo e você verá o número de locais em cada cidade no LogCat:
Você também configurou a replicação por push, mas ainda não persistiu nenhum documento localmente. Na seção onConnected método de MainActivity.java você adicionará código para manter um novo documento localmente com um tipo propriedade igual a perfil e cidade Antes de executar o aplicativo, vamos recapitular os diferentes componentes dessa arquitetura:
Observe que as setas estão agrupadas por cor:
- Laranja: Quando um usuário abre o aplicativo, o documento de perfil é mantido localmente e enviado ao Sync Gateway por meio da replicação contínua por push e aciona um webhook.
- Azul: O App Server lida com os webhooks e importa os locais de uma determinada cidade para o Sync Gateway.
- Verde: O usuário apropriado recebe os documentos de acordo com as regras de acesso definidas na Função de sincronização.
O Verde sempre ocorrerá algum tempo depois que o aplicativo for aberto. Seria bom monitorar a consulta de cidades no aplicativo Android para monitorar os dados à medida que eles são extraídos do Sync Gateway. Você fará isso usando um LiveQuery no lugar de um Consulta. Atualizar o consultarCidades para o seguinte:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
private void queryCities() { final Query query = database.getView(CITIES_VIEW).createQuery(); query.setGroupLevel(1); LiveQuery liveQuery = query.toLiveQuery(); liveQuery.addChangeListener(new LiveQuery.ChangeListener() { @Override public void changed(LiveQuery.ChangeEvent event) { try { QueryEnumerator enumeration = query.run(); for (QueryRow row : enumeration) { Log.d("CityExplorer", "Row is " + row.getValue() + " and key " + row.getKey()); } } catch (CouchbaseLiteException e) { e.printStackTrace(); } } }); liveQuery.start(); } |
Até o momento, você deve ter 20 documentos no Sync Gateway e pode verificar isso no painel do administrador. Reinicie o aplicativo e isso criará o documento de perfil e o enviará ao Sync Gateway para importar 20 locais em torno da localização do usuário. Isso resultará na adição de 20 novos locais ao Sync Gateway:
Observe que o Sync Gateway agora tem 41 documentos (o documento de perfil e mais 20 locais na localização do dispositivo que está executando o aplicativo) e o LiveQuery no aplicativo Android retorna os mesmos documentos por cidade (neste exemplo, 20 em Londres e 20 em Saint-Étienne-de-Tinée).
Alteração da função de sincronização
Até agora, você tem seguido o tutorial sem criar usuários. Ou seja, todos os dispositivos móveis que se conectam ao Sync Gateway recebem os mesmos documentos. Isso não é gerenciável à medida que o tamanho dos dados aumenta. Além disso, se um usuário estiver localizado em Saint-Étienne-de-Tinée, não há necessidade de sincronizar lugares em Londres com o dispositivo dele. Para adicionar essa filtragem de documentos por dispositivo, você fará duas coisas:
- Crie usuários e autentique-se como um usuário específico.
- Atualize a função de sincronização para dar a um determinado usuário acesso aos documentos na cidade em que ele está localizado.
Para criar um usuário, você usará o curl para enviar uma solicitação POST na porta de administração:
|
1 2 3 |
$ curl -vX POST :4985/db/_user/ -H 'Content-Type: application/json' -d '{"name": "james", "password": "letmein"}' |
No aplicativo Android, atualize o startSync método em SyncManager.java com o autenticador básico passando as mesmas credenciais:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
private void startSync() { URL url = null; try { url = new URL(SYNC_URL); } catch (MalformedURLException e) { e.printStackTrace(); } Authenticator authenticator = new BasicAuthenticator("james", "letmein"); Replication push = database.createPushReplication(url); push.setContinuous(true); push.setAuthenticator(authenticator); push.start(); Replication pull = database.createPullReplication(url); pull.setContinuous(true); pull.setAuthenticator(authenticator); pull.start(); } |
Navegue até a seção Sincronização no painel de controle do administrador: https://localhost:4985/_admin/db/db/syncUpdate a função Sync com o seguinte:
|
1 2 3 4 5 6 7 8 |
function(doc, oldDoc) { if (doc.type == "profile") { channel(doc.city); access(doc._id, doc.city); } else if (doc.type == "city") { channel(doc.city); } } |
Em seguida, clique no ícone Modo de visualização ao vivo e o banner superior deverá ficar amarelo.
Isso significa que você pode testar a função de sincronização em documentos aleatórios, mas nada foi implantado ainda; isso serve apenas para testar o resultado. Use o botão aleatório para ver a saída do canal/acesso com um documento aleatório como entrada:
Para implementar de fato essa nova função de sincronização, é recomendável interromper o Sync Gateway, atualizar o arquivo de configuração e iniciá-lo novamente. Depois de reiniciar o Sync Gateway, desinstale o aplicativo do dispositivo e execute-o novamente. Você deverá notar que os mesmos 40 documentos estão sendo replicados. Na verdade, você atualizou a Sync Function, mas os documentos que foram mantidos até agora não tiveram a chance de passar pela nova Sync Function. Para corrigir isso, você usará a função ressincronização na seção a seguir.
Ressincronização
Sempre que você modificar a função Sync, certifique-se de chamar a função _resync para recalcular as regras de acesso ao canal para todos os documentos existentes no banco de dados:
|
1 |
$ curl -vX POST https://localhost:4985/db/_resync |
Exclua o aplicativo e reinicie-o. Dessa vez, você verá apenas os 20 locais no LogCat porque o usuário está localizado em Saint-Étienne-de-Tinée e esse usuário não tem acesso ao Londres e, portanto, não obterá esses documentos.
Conclusão
Neste tutorial, você aprendeu a usar um webhook para importar documentos de uma API de terceiros para o Sync Gateway usando um App Server. Você também usou o ressincronização ao alterar sua Sync Function para atualizar o acesso aos canais.