Expo — Gerando APKs Localmente

André L. R. Estevam
10 min readDec 15, 2020

--

Problemas com o Expo?

Apesar da recente depreciação do Managed Workflow do Expo, fazer a atualização para o Bare Workflow nem sempre é algo trivial. Em alguns casos ainda é necessário conviver com o antigo fluxo de desenvolvimento.

Neste modo, o Expo tenta abstrair o máximo de complexidade de desenvolvimento e building de aplicações, nele, não é necessário programar em linguagem nativa.

Seguindo esse modelo de pensamento, o building aplicativo é feito nos servidores da Expo.

Para executar esse processo, geralmente rodamos comandos como:

Esta série de comandos garante que o usuário está logado na conta correta e então requisitará uma nova build.

Nesse processo, o Expo CLI utilizará sua máquina para gerar um bundle otimizado do projeto e então o enviará aos servidores.

Uma vez na Expo, o pedido de build entra em uma fila de processamento, contendo pedidos de todo o mundo.

Ao finalizar a build, o Expo CLI e a página do projeto no site da Expo exibirão o link para o artefato gerado, ou seja, no Android, recebemos um link para um arquivo .apk.

Esse fluxo, apesar de facilitar a build, também causa alguns problemas e, principalmente, atrasos durante o desenvolvimento.

Em um dia bom, uma build pode demorar cerca de 30 minutos.

Some a isso a perda de foco no desenvolvimento e a morosidade de checar o avanço da build.

Isso não é um problema tão grande em fluxos de build e testes automatizados, porém, é preciso lembrar de uma particularidade do desenvolvimento em React Native:

Projetos em React Native são executados a partir de diferentes bundles e engines dependendo do ambiente de execução.

Isso significa que, dado tempo suficiente, é certo que um desenvolvedor precisará resolver um bug que acontece somente em produção.

Considerando que é uma tarefa complexa descobrir a origem de uma falha que ocorre apenas em um .apk instalado, desenvolvedor precisará, a cada pequena mudança a ser testada, fazer uma nova build, e, como dito, em um dia bom cada uma levará no mínimo 30 minutos.

Alguns outros problemas nesse processo são:

  • É necessário fazer upload do projeto para os servidores da Expo e download do .apk` para a máquina. Internet lenta vira sinônimo de “hora do cafezinho”;
  • Não temos controle dos recursos de hardware utilizados durante a build. Não importa quantos leds piscantes seu computador tenha, a build acontecerá (provavelmente) em alguma instância EC2 da AWS ou container com hardware limitado;
  • Alguns usuários podem pagar para “furar a fila” de build, atrasando execuções comuns;
  • Acompanhar a build, interrompê-la e reiniciá-la são processos morosos: geralmente é preciso interagir com o site da Expo. Ao reiniciar a build, por exemplo, o processo recomeça do princípio — de volta no final da fila.

Turtle

A aplicação que faz a construção dos aplicativos nos servidores da Expo é o Turtle. Existe um painel que mostra a fila de builds em: https://expo.io/turtle-status.

A boa notícia é que essa ferramenta pode ser executada localmente para gerar builds sem depender de serviços externos.

É importante notar que: como o Expo lida com uma série de minúcias da construção do projeto, assim, não é interessante usar o Turtle para gerar builds de produção.

Mas sim utilizá-lo para gerar builds de testes de forma mais rápida. E principalmente quando se fala em defeitos que ocorrem somente em produção ou de bugfixes sucessivos que só podem ser validados em builds como as de produção.

Mão na massa

Instalação e Setup — Java

Confira se o Java 8 está instalado na sua máquina e se a variável de ambiente JAVA_HOME aponta para o diretório com a versão correta do Java.

Instalação e Setup —Turtle CLI

Para instalar o Turtle CLI com Yarn ou NPM, rode:

Na instalação com o Yarn, caso o CLI não apareça como uma opção na linha de comando, rode:

yarn global bin

O comando irá imprimir o caminho para os binários instalados via Yarn. Adicione este diretório ao PATH do sistema para tornar o comando turtle-cli disponível e todos os demais pacotes instalados globalmente via Yarn.

Gerando e servindo o Bundle

O no fluxo comum, o comando expo build:<plataforma> gera um bundle do app, o publica na Expo que então faz a build.

Nas duas primeiras etapas, os arquivos gerados na criação do bundle, pode-se especular que um processo bastante similar deva acontecer ao fazer a build na Expo. O bundle é gerado na máquina do usuário e então é enviado para a nuvem AWS da Expo e aguardamos em uma fila que o Turlte seja executado.

Ou seja, o Turtle acessa estes arquivos como se eles fossem disponibilizados por um servidor na internet.

Podemos replicar esse processo localmente rodando:

Ao executar o comando, um diretório chamado dist será criado, ele contém os bundles para Android e iOS e os assets do projeto.

dist
├── android-index.json
├── ios-index.json
├── assets
│ ├── 140c53a7643ea949007aa9a282153849
| └── 764a7aa9a283ea900140c53215384949
└── bundles
├── android-0d2eb108fcf8b4015c36718ff6556ff4.js
└── ios-93c994cb24cc6ed3dd007d5d45b11908.js

A flag --dev permite que o servidor seja http. Em um ambiente de integração contínua, onde o https é utilizado, esta flag pode ser desativada.

Com o comando finalizado, os arquivos serão criados, mas não ainda não estão sendo servidos, para isso pode-se utilizar o HTTPServer, do Python.

Um servidor HTTP será iniciado na porta 8000 expondo o conteúdo do diretório de execução do comando.

Para testar, rode:

curl http://127.0.0.1:8000/android-index.json

É importante deletar este diretório entre diferentes builds, ele não é atualizado de forma automática. Pode-se resumir este processo aos comandos:

Gerando o APK

A geração do .apk com o Turtle é dividida em duas etapas, o setup e então a build em si, sendo que caso a primeira não seja executada, a segunda a executará.

Rode o processo de setup (não obrigatório):

E então o de build:

A primeira etapa é um pouco longa na primeira execução e pode parecer que o terminal congelou, mas basta esperar que o processo termine.

Note que na primeira etapa não é necessário ter a keystore ainda, então é possível avançar para a etapa de sua obtenção (próxima seção) enquanto o setup acontece.

E então a construção do APK será inciada. Esta é a etapa mais demorada do processo.

Ao finalizar a execução, os logs finais serão semelhantes a:

[...]
Aug 26 16:33:34 turtle[421656] INFO: jar verified.
platform: "android"
buildPhase: "verifying apk"
source: "stdout"
Aug 26 16:33:34 turtle[421656] INFO: copying build to fake upload directory
platform: "android"
buildPhase: "copying build artifact"
Aug 26 16:33:34 turtle[421656] INFO: copied build to /home/andre/expo-apps/@anonymous__mobile-app-x-cf78b8111d9a44ceaa1a8961f45c3e2b-signed.apk
platform: "android"
buildPhase: "copying build artifact"
>_

O último log indica o diretório onde o .apk foi salvo. /home/andre/expo-apps/@anonymous__mobile-app-x-cf78b8111d9a44ceaa1a8961f45c3e2b-signed.apk.

Todos os .apk gerados com o turtle são salvos em ~/expo-apps/ por padrão.

Notas:

No meu caso em específico, encontrei alguns problemas nestas etapas e os resolvi da seguinte forma:

Para o erro: o arquivo ~/.android/repositories.cfg não foi encontrado.

Basta criar o arquivo, sem nenhum conteúdo:

touch ~/.android/repositories.cfg

Para erros genéricos relacionados ao Android SDK:

Bastou atualizar a versão do SDK e aceitar a licença de uso.

Para exeptions ao rodar turtle build, onde a ferramenta não chega a iniciar:

A versão do Node.JS não era suportada pelo Turtle. Lembre-se que o Expo te avisará se você estiver com a versão errada, o Turtle não.

Basta usar o nvm (Node Version Manager) para trocar a versão do Node utilizada pelo sistema por uma suportada pelo Turtle (no meu caso, versão 14, sendo que a original era a 10):

node --version
> v10.19.0
nvm install 14
nvm use 14
> v14.8.0

Obtendo a Keystore

Para fazer o build para Android, é necessário assinar o aplicativo e para isso utiliza-se uma keystore.

A keystore é importante para postar o .apk na loja da Google pois este arquivo está associado com o aplicativo na PlayStore.

Isto é, para adicionar uma nova versão de um app através de um .apk, o arquivo deve ser assinado pela mesma keystore das versões anteriores.

Se o projeto já passou por builds nos servidores da Expo e foi selecionada a opção de deixar que o Expo lide com a keystore, basta logar na conta da Expo e, com o seguinte comando, baixar a keystore.

Este comando baixará um arquivo .jks e mostrará algumas informações na tela:

Keystore credentials
Keystore password: fd12...a1d2 # <keystore-password>
Key alias: AQH...Xppbmdc= # <key-alias>
Key password: a195..497bcfa # <keypass>
Path to Keystore: /home/<user>/<diretório_do_app>/<nome-app>.jks # <key-file-name>

Para builds que não vão para a loja da Google ou para projetos que ainda não passaram pelo deploy, é possível gerar uma keystore “dummy”:

Uma keystore, como o nome sugere, é um arquivo que armazena diversas keys. Portanto, no keytool é necessário especificar:

  1. O nome arquivo .jks ;
  2. Um alias para a chave a armazenar na keystore;
  3. Uma senha para acessar a chave na keystore.

Ao executar o comando, uma senha para a própria keystore (<keystore-password>) será requerida. Ela deve possuir no mínimo 6 dígitos.

Instalando o APK via linha de comando

Com o .apk gerado, resta instalar e executar o aplicativo em um dispositivo físico ou emulador.

Para iniciar um emulador avd, que são geralmente instalados e configurados pelo Android Studio, rode:

Tanto para emuladores quanto para dispositivos físicos, use os seguintes comandos para realizar a instalação:

Note que, caso o app já esteja instalado, o comando irá falhar. Para fazer a desinstalação, use:

Aqui, o processo de encontrar o nome de pacote do app no dispositivo é feito automaticamente a partir de uma pista desse nome. Se o nome do app é xyz, o nome do pacote provavelmente será br.com.xyz, para obter esse nome, procuramos por pacotes que contenham xyz em seu nome e então selecionamos o primeiro.

Conclusão

Executar builds dependendo de servidores da Expo costuma ser uma tarefa demorada.

Eventualmente um desenvolvedor precisará gerar essas sucessivamente e a demora irá desacelerar consideravelmente o desenvolvimento, testes e correção de bugs.

Poder gerar builds localmente, apesar de não ser a opção ideal para enviar aplicativos para ambientes de produção, pode ser utilizado para acelerar estes processos.

Em um determinado aplicativo, foram realizados os testes de builds sucessivas no Expo e localmente:

  • Nos servidores da Expo: 1 hora (fila com 20~40 pedidos)
  • Localmente, na primeira execução: 20 minutos
  • Localmente, nas seguintes execuções: 5~10 minutos

Uma parte considerável do tempo de build, tanto local quanto nos servidores é gasto com a geração do bundle otimizado, a tendência é de redução neste tempo nas execuções subsequentes.

No caso da execução local, esse ganho de tempo passa a ser bastante significativo, comparando ao tempo total.

Além disso, rodando localmente, caso o processo seja interrompido após a geração do bundle otimizado, não é necessário fazer a sua reconstrução, visto que o bundle já estará em /dist.

Script de build

O seguinte script é uma tentativa de automatizar esse processo para a build e instalação de um determinado App.

Ainda é algo bruto, mas mostra alguns dos desafios de criar um script com essa finalidade.

Este script faz:

  1. Fetch da keystore que está nos servidores da Expo;
  2. Criação do diretório /dist com os arquivos do bundle;
  3. Inicia um servidor HTTP via Python para disponibilizar estes arquivos;
  4. Coloca o servidor em background;
  5. Inicia uma build usando o Turtle;
  6. Instala o .apk gerado em algum dispositivo ou emulador disponível;
  7. Envia mensagens e o .apk para uma conta do Telegram.

O script ainda possui uma função clean, executada no começo do script e sempre assim que ele finaliza.

Também é possível alterar o conteúdo da função sendToUser, esta função é acionada sempre que uma nova atualização ocorre. Uso o telegram-send para encaminhar essas mensagens para uma conta no Telegram através de um bot, dessa forma é possível receber atualizações e o próprio .apk mesmo longe do computador.

Veja como utilizar o telegram-send ou o pipe-to-telegram no post: https://medium.com/@andre.lr.estevam/do-terminal-ao-telegram-com-pipe-to-telegram-e-telegram-send-92815384338c

Para cada projeto, altere o script da seguinte maneira:

  1. Altere o conteúdo das funções sendToUser e finished. A primeira é chamada durante a execução para manter o desenvolvedor atualizado sobre o estado da build, a segunda é chamada quando a build termina e recebe como parâmetro o path do arquivo .apk. Por padrão a ferramenta telegram-send está sendo utilizada para enviar mensagens e o .apk para um bot no Telegram
  2. Altere APP_NAME e PACKAGE_NAME, elas correspondem ao nome do aplicativo no dispositivo Android e o nome do pacote. Estas variáveis são utilizadas para desinstalar automaticamente versões antigas do app e instalar a nova build.
  3. Altere as credenciais do expo em EXPO_USER e EXPO_PASSWORD, estas são utilizadas para obter a Keystore, caso ela seja gerenciada pela Expo.
  4. Altere PORT, caso necessário. Esta variável indica a porta onde o bundle do app será servido durante o processo de build.
  5. Caso a Keystrore não seja gerenciada pelo Expo, troque os valores em KEYSTORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD e PATH_TO_KEYSTORE pelas respectivas credenciais, que devem estar no mesmo repositório do app.

Fontes

--

--