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:
http://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-sincronização-portal/exemplos/básico-morsa-balde.json /caminho/para/projeto/sincronização-portal-configuração.json |
Inicie o Sync Gateway:
1 |
$ ~/Downloads/couchbase-sincronização-portal/caixa/portais de sincronização |
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": ["*"], "bancos de dados": { "db": { "bucket" (balde): "default", "servidor": "http://localhost:8091", "usuários": { "CONVIDADO": { "desativado": falso, "admin_channels": ["*"] } }, "event_handlers": { "document_changed": [ { "handler": "webhook", "url": "http://localhost:8000/sync_request", "filtro": `função(doc) { se (doc.tipo == "profile" (perfil)) { retorno verdadeiro; } retorno falso; }` } ] } } } } |
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-sincronização-portal/caixa/sincronização_portal ./sincronização-portal-configuração.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 instalar expresso corpo-analisador --salvar |
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 expresso = exigir("expresso); var bodyParser = exigir('body-parser'); var aplicativo = expresso(); aplicativo.uso(bodyParser.json()); /** * Tratar a solicitação do webhook do Sync Gateway. * O corpo da solicitação contém o documento. */ aplicativo.postagem('/sync_request', função (req, res) { var documento = req.corpo; console.registro('Manipular webhook com documento :: %s', JSON.stringify(documento)); res.sendStatus(200); }); var servidor = aplicativo.ouvir(8000, função () { var hospedeiro = servidor.endereço().endereço; var porto = servidor.endereço().porto; console.registro('App listening em http://%s:%s', hospedeiro, porto); }); |
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 |
$ enrolar -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 instalar solicitação rx --salvar |
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.get
você 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 = exigir('./requestRx.js'); var Rx = exigir('rx'); const chave api = 'AIzaSyD4e6ZUIc9G2AxKansIUKa0enFzWZy5h8w'; const url = 'https://maps.googleapis.com/maps/api/place'; const portal = 'http://localhost:4985/db'; var SyncRequest = { syncRequest: função(cidade) { console.registro('cidade para sincronizar com o %s', cidade); // 1. pesquisar locais requestRx.obter(`${url}/pesquisa de texto/json?chave=${chave api}&lificador;consulta=restaurantes+em+${cidade}`) .assinar((res) => { var lugares = JSON.analisar(res.corpo).resultados; lugares = lugares.mapa(função (lugar) { lugar.cidade = cidade; lugar._id = lugar.place_id; excluir lugar.place_id; retorno lugar; }); var placesStream = Rx.Observável.fromArray(lugares); // Enviar os locais em massa para o Sync Gateway requestRx({uri: `${portal}/_bulk_docs`, método: 'POST', json: {documentos: lugares}}) .flatMap((docsRes) => { var docsStream = Rx.Observável.fromArray(docsRes.corpo); // Mesclar a fotorreferência do local com o ID e a revisão do documento retorno Rx.Observável.zíper(placesStream, docsStream, (lugar, doc) => { retorno { id: doc.id, rev: doc.rev, ref: lugar.fotos ? lugar.fotos[0].foto_referência : 'CmRdAAAAA6MaWi5PIUhFumEYbHiM8IWHhJJvw1ss11QH1prE_x0PnUgyiyIiQmSNvfMu1lztLAA0mNdZa5Mr32ho5hE5nOKDAdOfVKcw4kLe0LKdDoYFENmRR1FE4AosTUhBvNCvEhB5HYf69MG389U27lkhrcPqGhSDbG7UhU9buWSEn2DRpy8E_R3oAg' } }); }) .flatMap((doc) => { // Obtenha a foto jpg binária usando a propriedade ref (ou seja, fotoreferência) var opções = { uri: `${url}/foto?chave=${chave api}&lificador;largura máxima=400&lificador;fotorreferência=${doc.ref}`, codificação: nulo }; retorno requestRx.obter(opções) .flatMap((foto) => { // Salve a foto como um anexo no documento correspondente retorno requestRx({ uri: `${portal}/${doc.id}/foto?rev=${doc.rev}`, método: 'PUT', cabeçalhos: {'Content-Type': 'image/jpg'}, corpo: foto.corpo }) }) }) .assinar((res) => { }); }); } }; módulo.exportações = 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 expresso = exigir("expresso); var bodyParser = exigir('body-parser'); var aplicativo = expresso(); aplicativo.uso(bodyParser.json()); var sincronização = exigir('./sync'); /** * Tratar a solicitação do webhook do Sync Gateway. * O corpo da solicitação contém o documento. */ aplicativo.postagem('/sync_request', função (req, res) { var documento = req.corpo; console.registro('Manipular webhook com documento :: %s', JSON.stringify(documento)); sincronização.syncRequest(documento.cidade); res.sendStatus(200); }); var servidor = aplicativo.ouvir(8000, função () { var hospedeiro = servidor.endereço().endereço; var porto = servidor.endereço().porto; console.registro('App listening em http://%s:%s', hospedeiro, porto); }); |
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 instalar babel-nó -g |
Agora você pode executar o sync.js de forma independente com o seguinte comando:
1 |
babel-nó -e "require('./sync.js').syncRequest('London')" |
Abra o Admin Dashboard para monitorar os documentos que foram salvos no Sync Gateway.
http://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 |
compilar '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 |
protegida sincronizado vazio criar cliente GoogleApi() { mGoogleApiClient = novo Cliente GoogleApi.Construtor(este) .addConnectionCallbacks(este) .addOnConnectionFailedListener(este) .addApi(Serviços de localização.API) .construir(); mGoogleApiClient.conectar(); } |
Em seguida, você fará Atividade principal implementar o GoogleApiClient.ConnectionCallbacks e GoogleApiClient.OnConnectionFailedListener e recuperar o local em onConnected:
1 2 3 4 5 |
@Substituir público vazio onConnected(Pacote feixe) { Localização mLastLocation = Serviços de localização.FusedLocationApi.getLastLocation(mGoogleApiClient); Geocodificador geocodificador = novo Geocodificador(este, Local.getDefault()); Lista |
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 |
// solução alternativa para o problema de "arquivos duplicados durante o empacotamento do APK // consulte https://groups.google.com/d/msg/adt-dev/bl5Rc4Szpzg/wC8cylTWuIEJ packagingOptions { excluir 'META-INF/ASL2.0' excluir 'META-INF/LICENSE' excluir 'META-INF/NOTICE' } |
Adicione o pacote do Couchbase Lite para Android em build.gradle:
1 |
compilar '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 |
público classe Gerenciador de sincronização { privado estático final Cordas NOME DO BANCO DE DADOS = "cityexplorer"; privado estático final Cordas SYNC_URL = "http://localhost:4984/db/"; privado estático final Cordas VISÃO DAS CIDADES = "getCities"; privado Contexto contexto; privado Gerente gerente; privado Banco de dados banco de dados; público Gerenciador de sincronização(Contexto contexto) { este.contexto = contexto; openDatabase(); } privado vazio openDatabase() { tentar { gerente = novo Gerente(novo AndroidContext(contexto), Gerente.DEFAULT_OPTIONS); } captura (IOException e) { e.printStackTrace(); } tentar { banco de dados = gerente.getDatabase(NOME DO BANCO DE DADOS); } captura (CouchbaseLiteException e) { e.printStackTrace(); } startSync(); } privado vazio startSync() { URL url = nulo; tentar { url = novo URL(SYNC_URL); } captura (ExceçãoURLE malformada e) { e.printStackTrace(); } Replicação empurrar = banco de dados.createPushReplication(url); empurrar.setContinuous(verdadeiro); empurrar.iniciar(); Replicação puxar = banco de dados.createPullReplication(url); puxar.setContinuous(verdadeiro); puxar.iniciar(); } público Banco de dados getDatabase() { retorno banco de dados; } público vazio setDatabase(Banco de dados banco de dados) { este.banco de dados = banco de dados; } } |
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 |
privado vazio registrar visualizações() { Ver cidadesVer = banco de dados.getView(VISÃO DAS CIDADES); cidadesVer.setMapReduce(novo Mapeador() { @Substituir público vazio mapa(Mapa<Cordas, Objeto> documento, Emissor emissor) { se (documento.obter("name" (nome)) != nulo) { Lista<objeto largura="300" altura="150"> chave = novo ArrayList<objeto>(); chave.adicionar(documento.obter("cidade")); emissor.emitir(chave, nulo); } } }, novo Redutor() { @Substituir público Objeto reduzir(Lista<objeto> chaves, Lista<objeto> valores, booleano Reduzir) { retorno novo Inteiro(valores.tamanho()); } }, "7"); } Para consulta que visualização, adicionar outro método chamado <forte>consultarCidades</forte> como assim: <pré> privado vazio consultarCidades() { Consulta consulta = banco de dados.getView(VISÃO DAS CIDADES).createQuery(); consulta.setGroupLevel(1); tentar { QueryEnumerator enumeração = consulta.executar(); para (QueryRow fila : enumeração) { Sistema.fora.println("Row is " + fila.getValue() + " e a chave " + fila.getKey()); } } captura (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 |
privado vazio consultarCidades() { final Consulta consulta = banco de dados.getView(VISÃO DAS CIDADES).createQuery(); consulta.setGroupLevel(1); LiveQuery Consulta ao vivo = consulta.toLiveQuery(); Consulta ao vivo.addChangeListener(novo LiveQuery.ChangeListener() { @Substituir público vazio alterado(LiveQuery.ChangeEvent evento) { tentar { QueryEnumerator enumeração = consulta.executar(); para (QueryRow fila : enumeração) { Registro.d("CityExplorer", "Row is " + fila.getValue() + " e a chave " + fila.getKey()); } } captura (CouchbaseLiteException e) { e.printStackTrace(); } } }); Consulta ao vivo.iniciar(); } |
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 |
$ enrolar -vX POST :4985/db/_usuário/ -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 |
privado vazio startSync() { URL url = nulo; tentar { url = novo URL(SYNC_URL); } captura (ExceçãoURLE malformada e) { e.printStackTrace(); } Autenticador autenticador = novo BasicAuthenticator("james", "letmein"); Replicação empurrar = banco de dados.createPushReplication(url); empurrar.setContinuous(verdadeiro); empurrar.setAuthenticator(autenticador); empurrar.iniciar(); Replicação puxar = banco de dados.createPullReplication(url); puxar.setContinuous(verdadeiro); puxar.setAuthenticator(autenticador); puxar.iniciar(); } |
Navegue até a seção Sincronização no painel de controle do administrador: http://localhost:4985/_admin/db/db/syncUpdate a função Sync com o seguinte:
1 2 3 4 5 6 7 8 |
função(doc, oldDoc) { se (doc.tipo == "profile" (perfil)) { canal(doc.cidade); acesso(doc._id, doc.cidade); } mais se (doc.tipo == "cidade") { canal(doc.cidade); } } |
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 |
$ enrolar -vX POST http://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.