Executando verificação de segurança...
1

Utilizando Python para sincronizar as variáveis de ambiente nos arquivos launch.json e config.json no desenvolvimento de App's Flutter

Alguns dias atrás estava estudando sobre variáveis de ambiente em tempo de compilação utilizando --dart-define em um app Flutter. A documentação do Dart, diz o seguinte:

You can specify compilation environment declarations when building or running a Dart application. Compilation environment declarations specify configuration options as key-value pairs that are accessed and evaluated at compile time.

Ou seja, é possível em tempo de compilação fornecer à aplicação Dart pares de chave-valor. Na empresa que trabalho atualmente, possuímos os seguintes ambientes: o de desenvolvimento, homologação e produção. Cada um desse ambientes possuem seus próprios domínios e um conjuntos de outras configurações.

Imagine o seguinte cenário. Você está desenvolvendo um aplicativo Flutter e este aplicativo precisa listar os produtos através de um determinado end-point, como por exemplo, product-list. No momento que está codificando o app, você acaba conectando o domínio da API do ambiente de desenvolvimento ao end-point product-list para assim realizar a chamada e receber como resposta a tão esperada lista de produtos.

De repente aparece um cara que é responsável pela homologação do aplicativo e pede para você gerar uma versão do seu lindo aplicativo de lista de produtos para o ambiente de homologação. Então você deve pensar, ótimo, basta abrir o arquivo onde faço a chamada da API para a lista de produtos e substituo o domínio de desenvolvimento para o de homologação e voilà! Está aqui o seu app, cara da homologação.

Você super feliz na sua mesa e tomando seu café e do nada aparece uma moça do setor de negócios da empresa e fala que precisa testar o aplicativo em produção. Então as coisas começam a ficar repetitivas e você começa a se perguntar, vou ter que ficar fazendo sempre essas alterações de domínio no aplicativo? E se ao invés de uma variável de ambiente você tivesse 20, 40 ou mais, e ter que mudá-las toda vez que alguém pedir um app para cada ambiente da sua empresa?

Pensando nisso é que o Dart disponibiliza uma opção chamada --define que você pode utilizar assim:

$ dart run --define=DEBUG=true

Onde DEBUG=true é o par chave-valor que é passado para a aplicação em tempo de compilação. Mas como estamos falando de aplicações Flutter, você irá utilizar algo como:

$ flutter run --dart-define=DEBUG=true

Para saber mais, consulte a documentação do Dart, clicando aqui.

Mas voltando para o nosso exemplo da listagem de produtos. Se quiséssemos passar o domínio do ambiente de desenvolvimento para o nosso aplicativo para rodar em um emulador, seria assim:

$ flutter run --dart-define=URL=https://dev.meusite.com.br

Se fosse para gerar um APK, seria assim:

$ flutter build apk --dart-define=URL=https://dev.meusite.com.br

Para executar/buildar o app para produção ou qualquer outro ambiente, basta apenas trocar o valor da variável URL. OK Fernando, e como eu recupero o valor desta variável no meu código? Para isso basta fazer da seguinte forma:

final String urlApi = const String.fromEnvironment('API_KEY');

Além de String, é possível utilizar também para valores booleanos e para inteiros:

final bool urlApi = const bool.fromEnvironment('DEBUG');

final int urlApi = const int.fromEnvironment('COUNT');

Certo, agora já sabemos como utilizar o --dart-define para utilizar as nossas variáveis de ambiente em nosso código. Maravilha! :D Você não precisará mais ficar trocando manualmente o domínio da sua API diretamente no código toda vez que alguém pedir uma versão do seu aplicativo para determinado ambiente. Ufa!

Mas então depois de um tempo o seu aplicativo cresce, novas telas, novas funcionalidades legais e consequentemente novas variáveis de ambiente. Agora seu aplicativo utiliza serviços de terceiros e esses serviços podem oferecer domínios para diferentes ambientes de desenvolvimento. Então você lembra da dica que dei, aaaah é só usar o --dart-define com todas as variáveis de ambinte.

$ flutter run --dart-define=URL=https://dev.meusite.com.br --dart-define=SERVICE_ONE=https://dev.serviceone.com.br --dart-define=SERVICE_TWO=https://dev.servicetwo.com.br --dart-define=SERVICETHREE=https://dev.servicethree.com.br

O que achou? Eficiente? Muito pelo contrário, não é? Com quatro variáveis já achei estranho, imagina ter 20 ou mais sendo passadas em um único comando? É de deixar qualquer um estressado :). Mas essa era a forma que muitos desenvolvedores utilizavam para inserir as variáveis de ambiente em seus aplicativos em tempo de compilação. Você leu certo, era a maneira que utilizavam para fazer isso. Atualmente já temos uma forma mais simples e fácil de manter.

Os desenvolvedores percebendo a dificuldade de manter essa linha de comando começaram a sugerir alterações para facilitar a manutenção e inclusão de novas variáveis de ambiente. Sendo assim, na versão 3.7 do Flutter, foi lançada a propriedade --dart-define-from-file, que recebe como valor um arquivo .JSON. O nosso exemplo anterior ficaria assim:

$ flutter run --dart-define-from-file=config.json

e no arquivo config.json:

{ "DEFINE_APP_NAME": "App produtos", "DEFINE_APP_SUFIX_NAME": "DEV" }

Agora sim o/, muito melhor dessa forma, não?! Você pode conferir alguns PR no repositório do Flutter sobre --dart-define-from-file nos links abaixo:

Então até agora aprendemos como definir as variáveis de ambiente em um arquivo .JSON e utilizá-las em nossos apps. Mas onde entra o Python nessa história? Então, como utilizo o VS Code para programar e para executar/depurar um aplicativo utilizo o arquivo launch.json para definir as variáveis de ambiente, acabo que com essa nova propriedade --dart-define-from-file, tenho dois arquivos para manter: o launch.json e o config.json.

Sendo assim, tenho que manter as variáveis de ambiente atualizadas nos dois arquivos e isso é um problema. Para resolver essa questão utilizei os recursos do Python para criar um CLI (Command-Line Interface), ou uma interface de linha de comando, que ao executar o arquivo no terminal irá exibir isso:

Como você pode ver na image, através do CLI poderei criar/atualizar os arquivos launch.json e config.json, executar aplicativos e fazer os builds para Android e iOS. Mas antes que você possa ver e utilizar o arquivo quero deixar alguns pontos:

  1. O código foi desenvolvido para sanar um problema que estava passando no momento e também para estudo. Então pode ser que isso não faça sentido para você e tudo bem :). Mas se puder ajudar outra pessoa, já valeu a pena.
  2. É a melhor forma de resolver isso? A resposta para isso é que depende do contexto. Se você usa CI/CD, é possível configurar essas variáveis de ambiente na esteira de desenvolvimento para assim gerar o APK/BUNDLE ou o IPA para as respectivas lojas. Você poderia também fazer um makefile sem problemas ;).
  3. Posso melhorar o código? Deve! Mas se possível deixa uma referência de onde pegou o mesmo e se tiver uma melhoria pode abrir uma PR.

Para conferir o código responsável por executar estas ações, basta acessar meu github clicando aqui.

Ao abrir o arquivo, você irá ver isso:

O que temos na imagem acima é o que no Python é chamado de dicionário e é nesta faixa de código que você define uma única vez as variáveis para cada ambiente e depois que estas variáveis estiverem definidas, basta chamar no terminal:

$ python builder.py

Depois digite a opção [1] para Criar/Atualizar launch.json (VS Code)/config.json (Environments) e pronto. Os arquivos launch.json e config.json estão com as variáveis de ambiente definidas e sincronizadas. Com isso, você já pode utilizar as outras opções e o restante do código do builder.py faz todo o trabalho.

Só mais uma coisa, o arquivo builder.py deve ficar da raiz do seu projeto Flutter.

É isso, espero que este código possa ajudar alguém e conto com vocês para aperfeiçoa-lo e se ficou alguma dúvida pode deixar nos comentários que irei responder assim que possível. Valeu e até a próxima ;)

Carregando publicação patrocinada...