Expo — Gerando APKs Localmente
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:
- O nome arquivo
.jks
; - Um alias para a chave a armazenar na keystore;
- 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:
- Fetch da keystore que está nos servidores da Expo;
- Criação do diretório
/dist
com os arquivos do bundle; - Inicia um servidor HTTP via Python para disponibilizar estes arquivos;
- Coloca o servidor em background;
- Inicia uma build usando o Turtle;
- Instala o
.apk
gerado em algum dispositivo ou emulador disponível; - 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:
- Altere o conteúdo das funções
sendToUser
efinished
. 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 ferramentatelegram-send
está sendo utilizada para enviar mensagens e o.apk
para um bot no Telegram - Altere
APP_NAME
ePACKAGE_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. - Altere as credenciais do expo em
EXPO_USER
eEXPO_PASSWORD
, estas são utilizadas para obter a Keystore, caso ela seja gerenciada pela Expo. - Altere
PORT
, caso necessário. Esta variável indica a porta onde o bundle do app será servido durante o processo de build. - Caso a Keystrore não seja gerenciada pelo Expo, troque os valores em
KEYSTORE_PASSWORD
,KEY_ALIAS
,KEY_PASSWORD
ePATH_TO_KEYSTORE
pelas respectivas credenciais, que devem estar no mesmo repositório do app.
Fontes
- Artigo sobre o funcionamento do Expo: https://hackernoon.com/understanding-expo-for-react-native-7bf23054bbcd
- Documentação do Expo sobre o
turtle-cli
: https://docs.expo.io/distribution/turtle-cli/ - Artigo sobre o turtle: https://www.robincussol.com/build-standalone-expo-apk-ipa-with-turtle-cli/
- Artigo sobre o turtle: https://levelup.gitconnected.com/build-your-standalone-expo-app-locally-with-turtle-cli-87de3a487704
- Solução de problemas — Issue relacionada a versão do Java: https://github.com/expo/turtle/issues/240
- Documentação do Expo que cita o turtle: https://docs.expo.io/distribution/building-standalone-apps/
- Solução de problemas — Pergunta no Ask Ubuntu: https://askubuntu.com/questions/885658/android-sdk-repositories-cfg-could-not-be-loaded
- Artigo que mostra a geração de Keystores em detalhes: https://coderwall.com/p/r09hoq/android-generate-release-debug-keystores
- Solução de problemas — Pergunta sobre Keystores no StackOverflow: https://stackoverflow.com/questions/32304046/whats-the-difference-between-key-store-password-and-key-password-in-android-sig
- Pipe to telegram: https://github.com/ajhsu/pipe-to-telegram