Guia Passo a Passo: Configurando Flavors no Flutter para Android, IOS e Web
Aprenda como criar múltiplos ambientes separados para seu aplicativo usando uma única base de código.
Fala devs blz? Hoje nós vamos ver como configurar Flavors em nossas aplicações Flutter, algo extremamente importante e útil quando se está trabalhando com desenvolvimento mobile etc. Eu sempre uso flavors em meus aplicativos que vão para produção, pois eles facilitam bastante quando se tem que trabalhar com múltiplos ambientes (development, staging, production) para um mesmo app.
Depois de várias e várias tentativas e erros para fazer os flavors funcionarem, especialmente no iOS, decidi escrever este artigo com meus aprendizados. A ideia que ele seja um guia definitivo passo a passo sobre o assunto, pois pesquisando na internet não vi muitos materiais estruturados sobre o assunto.
Toda vez que eu ia configurar flavors acabava passando algumas horas na construção pois tinha que consultar vários materiais da internet, especificamente para IOS onde dava bastante problema.
O que são Flavors e por que você precisa usar
Flavors, também chamados de sabores em português é quando você tem ambientes separados para seu aplicativo usando a mesma base de código. Normalmente, os tipos mais usados são dev, stag e prod.
Por exemplo você pode ter um flavor para seu aplicativo de produção que aponta para uma API de produção com o endereço meuapp.com.br/api e outra versão de desenvolvimento do app apontando para o host de API em dev.meuapp.com.br/api ou até mesmo ter ícones de aplicativos diferentes para cada configuração.
A maioria das empresas e projetos normalmente têm um ciclo de vida no processo de construção de software bem definido, onde as novas funcionalidades passam pelas mais diferentes etapas, como desenvolvimento, teste, e, finalmente, vão para a produção.
Abaixo dou uma breve explicação sobre esses ambientes/configurações.
development (dev) : usado apenas durante a criação do aplicativo onde os desenvolvedores usam a vontade sem medo de comprometer os dados.
staging (stg) : usado para distribuir versões de teste para a equipe QA e outras partes interessadas no teste do aplicativo. O back-end vai configurado para dados de desenvolvimento, mas não pronto para fins de produção.
production (prod) : usado por todos os usuários que baixaram o aplicativo nas lojas onde os dados do backend já são os reais.
A grande sacada dos flavors é nos possibilitar termos vários ambientes para mexer antes de enviar pra produção nos dando uma garantia que os dados no ambiente de produção não sejam alterados por engano durante o desenvolvimento. 😄
Sem flavors teríamos que mudar os hosts das APIS manualmente em variáveis a cada build o que seria bem chato, sem falar que se quisermos mudar logo, o nome do app seria mais complicado ainda.
Mas se nosso aplicativo Flutter usar um back-end do Firebase, configurá-lo para cada ambiente (dev, stg, prod) sem flavors é também extremamente chato, trabalhoso e custoso ter que toda vez na build fazer isso.
Com a nova versão do Flutter configurar o firebase para os flavors ficou bem mais simples do que no passado, então sem mais delongas vamos lá. 💛
A abordagem para este tutorial
Vou construir um aplicativo de exemplo com duas opções: dev e prod .
Configurando Flavors no Dart para múltiplos ambientes de API.
Vamor criar um arquivo de flavors no dart e com isso poderemos disponibilizá-los em qualquer lugar do nosso código, começaremos criando o arquivo flavors.dart
na pasta lib
do nosso projeto.
enum FlavorTypes { dev, prod }
class Flavor {
Flavor._instance();
static late FlavorTypes flavorType;
static String get flavorMessage {
switch (flavorType) {
case FlavorTypes.dev:
return 'Dev';
case FlavorTypes.prod:
return 'Production';
default:
return 'Dev';
}
}
static String get apiBaseUrl {
switch (flavorType) {
case FlavorTypes.dev:
return 'apiUrlBaseDev';
case FlavorTypes.prod:
return 'apiUrlBaseProd';
default:
return 'apiUrlBaseDev';
}
}
static bool isProduction() => flavorType == FlavorTypes.prod;
static bool isDevelopment() => flavorType == FlavorTypes.dev;
}
Nesse arquivo configurei-o para ser um singleton, pois com isso ele será facilmente acessível em qualquer lugar já que vamos precisar.
No FlavorTypes
enum declaramos os tipos de ambientes que utilizaremos, e o getApiBaseUrl
será responsáveil por selecionar a URL correta para a nossa API conforme o flavor setado quando a aplicação estiver rodando.
Agora para cada ambiente flavor, criamos um arquivo main+flavor onde sera o ponto de entrada usado para iniciar o aplicativo setando o flavor correto
main_dev.dart
: Nosso flavor para o ambiente de desenvolvimento .import 'flavors.dart'; import 'main.dart' as main_common; Future<void> main() async { Flavor.flavorType = FlavorTypes.dev; main_common.main(); }
main_prod.dart
: Do nosso flavor para o lançamento em produção .import 'flavors.dart'; import 'main.dart' as main_common; Future<void> main() async { Flavor.flavorType = FlavorTypes.prod; main_common.main(); }
O main_common é apenas um alias para o main padrão que ja existia antes de configuramos os flavor no projeto, apenas estamos encapsulando-o para ser chamado conforme o flavor (sabor) setado.
Veja abaixo o código do main_common
:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo Flavor',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
Para rodar nosso aplicativo devemos escolher o flavor desejado e em seguida digitar o seguinte comando no terminal:
flutter run -t lib/main_dev.dart
flutter run -t lib/main_prod.dart
O comando acima irá executar nosso aplicativo em modo de depuração, com -t
ou --target
definimos o ponto inicial, ou seja o arquivo principal.
Veja abaixo o aplicativo rodando no emulador Android com o ambiente de development configurado parcialmente pois ainda precisamos fazer alguns ajustes.
Adicionando flavors para Android
Com o nosso aplicativo inicial de exemplo rodando precisamos finalizar a configuração dos nossos flavors para o android, é bem simples e rápido de configurar.
Na pasta android navegue até o arquivo em app/build.gradle e adicione o seguinte código.
O flavor dev usará o applicationId
como org.cajuinaapps.br.dev
com o final indicando ambiente de desenvolvimento e o flavor prod usará o applicationId
padrão org.cajuinaapps.br
sem prefixo no final indicando ambiente de produção.
Também definimos um recurso de string chamado app_name
no resValue que estamos usando em AndroidManifest.xml
isso serve para configurar o nome do aplicativo em tempo de execução conforme o flavor usado.
NOTA: Não se esqueça de adicionar android:label="@string/app_name"
a application
tag no AndroidManifest.
Vamor testar 🧪
Agora podemos executar o aplicativo usando os seguintes comandos um de cada vez e rodar os ambientes.
flutter run --flavor dev --target lib/main_dev.dart
flutter run --flavor prod --target lib/main_prod.dart
Definindo configurações de inicialização para Vs code e Android Studio
Para rodar o nosso app não queremos toda vez ter que digitar o comando no terminal não é mesmo, por isso vamos configurar o nosso editor de código para fazer isso para a gente.
No Vs code, adicione um arquivo launch.json, isso permite que você execute o comando flutter run --flavor [environment name]
.
Defina as configurações de inicialização da seguinte forma:
No diretório raiz do seu projeto, adicione uma pasta chamada .vscode .
Dentro da pasta .vscode , crie um arquivo chamado launch.json .
No arquivo launch.json , adicione uma configuração para cada variação do flavor. Cada configuração possui uma chave name , request , type , program e args.
Veja o exemplo abaixo.
{
"version": "0.2.0",
"configurations": [
{
"name": "Development",
"request": "launch",
"type": "dart",
"program": "lib/main_dev.dart",
"args": [
"--flavor",
"dev",
]
},
{
"name": "Production",
"request": "launch",
"type": "dart",
"program": "lib/main_prod.dart",
"args": [
"--flavor",
"prod",
]
},
]
}
Agora podemos executar com sucesso o nosso app com o ambiente escolhido.
Configurando no Android Studio 🛠️
Agora, temos a capacidade de executar nosso aplicativo tanto no VS code quanto no Android Studio.
Se o seu aplicativo utilizar uma API, você pode simplesmente utilizar o Flavor.apiBaseUrl
para obter a URL Base correspondente ao ambiente, seja ele de desenvolvimento ou produção.
Fácil, não é mesmo 😅 ? Mas Ian, e se estivermos usando o Firebase? Bem, vamos explorar como configurar flavors para múltiplos projetos do Firebase mais adiante nesse artigo.
Configurando Flavors para IOS
Abra a pasta ios do seu projeto no Xcode . Sem configurar nada vamos rodar o comando flutter run --flavor dev
no terminal direcionando a um simulador do IOS e veja o que acontece.
Primeiro, precisamos criar novos Schemes de produtos no XCode. Em nosso exemplo, é prod
e dev
. Um Scheme descreve como o Xcode executa diferentes ações.
Já temos um scheme Runner
, que é o scheme padrão. Iremos renomeá-lo para prod
e criar mais um novo esquema dev
. Vamos lá? veja o passo a passo abaixo.
Agora novamente rodando o comando flutter run --flavor dev
no terminal direcionando a um simulador do IOS e veja o que acontece.
Esse erro nos diz que o Flutter espera uma configuração de build chamada Debug-dev ou similar. Vamos criar essas configurações agora. Depois que os schemes são criados, temos que duplicar as configurações de compilação:
Debug, Release, and Profile de acordo com o scheme, com o nome da configuração de compilação copiado + prefixo como nome. Duplique as configurações de build para diferenciar entre as configurações padrão que já estão disponíveis e as novas configurações do scheme dev
.
Feito isso, teremos 6 configurações, 3 para cada flavor (sabor).
Agora rode flutter run --flavor dev
novamente…
Ainda estamos com erro pois no momento não personalizamos nada no esquema de construção/configuração de compilação, então o aplicativo ainda não irá ser executado com sucesso. Vamos configurar isso agora. ✍🏻
Renomeie o esquema de construção padrão e as configurações de construção para prod.
Como duplicamos as configurações de build, as configurações de dev ainda estão conectadas ao scheme original (que agora se chama prod). Vamos consertar isso também…
Esse é o resultado final abaixo.
Personalizando configurações para cada scheme
Vamos primeiro alterar o identificador do pacote de aplicativos para ser diferente em ambos os esquemas. O applicationId no Android era org.cajuinaapps.br
o identificador do pacote no iOS é paralelo ao applicationId do Android.
Então, vamos alterar nosso identificador de pacote de produtos para org.cajuinaapps.br
conforme o flavor.
Agora, vamos adicionar o identificador do pacote para cada configuração. Em Target
-> Runner
, clique Build Settings
e pesquise Product Bundle Identifier
e altere o identificador do pacote de aplicativos para diferenciar os esquemas.
Quando terminar, você terá algo assim.
Configurando nomes diferentes para o aplicativo conforme o flavor.
No mesmo Target
-> Runner
-> Build Settings
, clique no botão +
Add User Defined Settings e crie uma nova variável definida pelo usuário.
Quando terminar, você terá algo assim.
Depois de concluir a variável definida pelo usuário, você precisa alterar Info.plist
para usar esta variável.
<key>CFBundleDisplayName</key>
<string>$(APP_DISPLAY_NAME)</string>
Vamos testar 🧪
Semelhante ao Android, podemos testar o iOS executando o comando para os ambientes prod e dev.
flutter run --flavor dev --target lib/main_dev.dart
flutter run --flavor prod --target lib/main_prod.dart
Ícones diferentes no aplicativo para Android e iOS
Agora, vamos ter ícones diferentes baseados nos flavors. Teremos 2 ícones diferentes um para cada ambiente.
O ícone verdinho vai ser o de ambiente de desenvolvimento e o outro será o do aplicativo de produção.
Vamos utilizar o package flutter_launcher_icons
para nos ajudar a gerar os ícones para o aplicativo. Instale-o como uma dependência de desenvolvimento.
flutter pub add flutter_launcher_icons --dev
Após a instalação vamos criar um arquivo de configuração para ícones de cada ambiente flavor.
Veja o exemplo abaixo.
flutter_launcher_icons-dev.yaml
flutter_launcher_icons: android: true ios: true remove_alpha_ios: true image_path: "assets/icon_dev.png"
flutter_launcher_icons-prod.yaml
flutter_launcher_icons: android: true ios: true remove_alpha_ios: true image_path: "assets/prod-icon.png"
Agora vamos gerar os ícones com o comando:
flutter pub run flutter_launcher_icons:main -f flutter_launcher_icons-*
Rodando o comando nossos ícones foram gerados com sucesso. No Android dentro de app/src/
foi gerado duas pastas chamadas de prod e dev e dentro delas teremos uma outra pasta chamada res
com os nosso icones respectivos.
Para IOS vá em Runner
-> Assets.xcassets
podemos ver que temos todos os ícones necessários. Podemos remover o AppIcon
, que não é utilizado.
Agora, em Target
-> Runner
-> Build Settings,
procure por Primary App Icon
. defina o sufixo correspondente para cada um.
Testando as duas compilações e o resultado final é esse abaixo:
Configurando Flavors para diferentes projetos do Firebase 🔥
Primeira coisa, você deve ter dois projetos do Firebase criados para cada ambiente flavor.
Para o nosso exemplo nesse artigo teremos um de dev
e outro de prod
.
A integração do Firebase em um projeto costumava ser mais complicada do que é hoje. Você tinha que integrá-lo em um nível nativo, baixar configurações separadas para Android, iOS e web e era bastante sujeito a erros mas hoje temos uma nova maneira mais prática e rápido de configurar isso graças as evoluções da integração entre times do Flutter e Firebase.
Nesse artigo vou mostrar rapidamente como adicionar e configurar seu projeto ao Firebase mas se você quiser algo mais detalhado pode consultar isso em outro artigo onde mostro todo o processo bem detalhado, você pode consultar aqui.
Integrando o Firebase
Abaixo no Firebase console já deixei criado os dois projetos para o nosso aplicativo.
Instale a lib do Firebase core em seu app
flutter pub add firebase_core
Isso adicionará uma linha como esta ao pubspec.yaml
do seu pacote (e executará um comando implícito flutter pub get
):
dependencies:
firebase_core: ^2.31.0
Em seguida, como o novo processo de integração usa FlutterFire CLI, que depende do Firebase CLI, você precisa instalar o Firebase CLI em nível global e fazer login. Se você tiver dúvidas na instalação consulte o meu artigo sobre isso ou a própria documentação aqui.
Em seguida, faça login na sua conta do Firebase com o seguinte comando:
firebase login
Se você ainda não estiver logado, este comando abrirá um navegador para permitir que você faça login em sua conta. Em seguida, execute o seguinte comando no mesmo terminal para instalar o FlutterFire CLI:
dart pub global activate flutterfire_cli
Em seguida, de volta ao terminal, vamos executar o seguinte comando para criar os aplicativos para Android e IOS no console do Firebase para cada projeto.
flutterfire config \
--project=fir-flavor-example-dev \
--out=lib/firebase_options_dev.dart \
--ios-bundle-id=org.cajuinaapps.br.dev \
--android-app-id=org.cajuinaapps.br.dev
Este comando registrará um aplicativo iOS e terá org.cajuinaapps.dev
como identificador de pacote. Ele também registrará um aplicativo Android com o nome do pacote sendo org.cajuinaapps.dev
.
Assim que o comando for concluído, ele irá gerar uma classe de configuração em lib/firebase_options_dev.dart
contendo a uma classe chamada DefaultFirebaseOptions
.
Gerando a configuração para o aplicativo de produção
Com o mesmo comando utlizado anteriormente vamos configurar nossos apps no firebase de prod apenas trocando alguns dados.
Veja como ficou o novo comando abaixo:
flutterfire config \
--project=flavor-firebase-example \
--out=lib/firebase_options_prod.dart \
--ios-bundle-id=org.cajuinaapps.br \
--android-app-id=org.cajuinaapps.br
De volta ao nosso editor de código, abra o arquivo lib/flavors.dart
que nós criamos no início do artigo e adicione o seguinte trecho de código apartir dos comentários abaixo.
import 'package:firebase_core/firebase_core.dart';
//Adicione esses novos imports
import 'package:flavor_example_config/firebase_options_dev.dart' as dev;
import 'package:flavor_example_config/firebase_options_prod.dart' as prod;
enum FlavorTypes { dev, prod }
class Flavor {
Flavor._instance();
static late FlavorTypes flavorType;
static String get flavorMessage {
switch (flavorType) {
case FlavorTypes.dev:
return 'Development';
case FlavorTypes.prod:
return 'Production';
default:
return 'Development';
}
}
static String get apiBaseUrl {
switch (flavorType) {
case FlavorTypes.dev:
return 'apiUrlBaseDev';
case FlavorTypes.prod:
return 'apiUrlBaseProd';
default:
return 'apiUrlBaseDev';
}
}
static bool isProduction() => flavorType == FlavorTypes.prod;
static bool isDevelopment() => flavorType == FlavorTypes.dev;
//Adicione esse novo trecho de código abaixo
static FirebaseOptions get firebaseConfigOptions {
switch (flavorType) {
case FlavorTypes.dev:
return dev.DefaultFirebaseOptions.currentPlatform;
case FlavorTypes.prod:
return prod.DefaultFirebaseOptions.currentPlatform;
default:
return dev.DefaultFirebaseOptions.currentPlatform;
}
}
}
Isso nos fornecerá a configuração correta conforme o flavor que estiver rodando no aplicativo em tempo de execução.
Para demonstrar o sucesso da integração eu ativei o banco de dados Cloud Firestore para cada aplicativo onde estará exibindo dados de seu respectivo ambiente no Firebase.
Atenção!! Não irei mostrar nesse post como configurar e usar o Cloud Firestore
mas tenho esse outro artigo onde explico tudo em detalhes como fazer um crud completo com ele.
Adicionei alguns dados simples de filmes ao banco de dados para ilustrar melhor. Se você quiser aprender mais sobre o assunto, confira este outro artigo onde explico todos os detalhes da integração.
Executando a aplicação podemos ver que o Firebase foi configurado com sucesso!
Nas imagens acima podemos ver nomes diferentes sobre filmes para cada aplicativo conforme cadastrado na base de dados 🎲 !
Rodando o aplicativo na web 👨🏻💻
O uso do Flavors com Firebase funciona perfeitamente na web. Isso ocorre porque o arquivo de configuração do Firebase, na classe DefaultFirebaseOptions
, já possui uma configuração específica para o ambiente web. Portanto, ao compilar nossa aplicação Flutter para a web, não é necessário realizar nenhuma configuração adicional. Isso é ótimo, não é mesmo?
Link do projeto no github aqui. 🔗
Referências
https://codewithandrea.com/articles/flutter-flavors-for-firebase-apps/
https://github.com/invertase/flutterfire_cli/issues/14
Conclusão
Bom é isso 😎.
Autilização de Flavors no Flutter oferece uma solução elegante e eficiente para gerenciar múltiplos ambientes de desenvolvimento, teste e produção dentro de um único código-base.
Com a configuração adequada, é possível personalizar comportamentos, variáveis e até mesmo integrações específicas para cada ambiente, facilitando o desenvolvimento e a manutenção do aplicativo.
Além disso, a integração com serviços como o Firebase é simplificada, garantindo que cada Flavor possa operar com suas próprias configurações sem complicações adicionais.
Em artigos futuros, veremos como usar outros recursos combinado com os flavors para configurar apps como white-label por exemplo.
Espero que você tenha gostado e obrigado por acompanhar até aqui! Compartilhe-o com seus amigos e colegas!
Juntos, vamos construir apps incríveis que transformam o mundo!
Se tiver alguma dúvida ou contribuição, deixe nos comentários!
Me siga para estar sempre por dentro dos próximos artigos 📲 🚀