Aplicativo Android CI/CD com Flutter

⏱️5 min
Compartilhar:

CI/CD pipeline é parte essencial do desenvolvimento de software. Este artigo explica como construir um CI/CD pipeline para construir um Android usando o Flutter . Construiremos pipeline com base em GitHub Actions e não usaremos fastlane .

1. Construir localmente

Antes de criar um pipeline no GitHub , vamos construir um aplicativo localmente. Você pode preparar o ambiente de construção com o site oficial - Construir e lançar um aplicativo Android. Explicaremos alguns dos conteúdos aqui.

1-1. key.jks e key.properties

Construir um Android requer a chave para assinar. key.jks contém a chave e key.properties contém a senha. key.jks pode ser gerado pelos comandos abaixo.

janelas

bash
1keytool -genkey -v -keystore c:\Users\USER_NAME\key.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias key

Mac / Linux

bash
1keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key

Ao usar esses comandos, você precisa da key password e da store password . Registre essas senhas no arquivo key.properties

properties
1storePassword=<your password>
2keyPassword=<your password>
3keyAlias=key
4storeFile=C:/Users/USER_NAME/key.jks // depends on your environment

Em seguida, modifique os Android para usar esses arquivos para assinar seu aplicativo.

1-2. app / bundle.gradle

Corrija o arquivo bundle.gradle Observe que há dois bundle.gradle no projeto. Usamos o arquivo no diretório do app

gradle
1def keystoreProperties = new Properties()
2def keystorePropertiesFile = rootProject.file('key.properties')
3if (keystorePropertiesFile.exists()) {
4 keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
5}
6
7android {
8 signingConfigs {
9 release {
10 keyAlias keystoreProperties['keyAlias'] ? keystoreProperties['keyAlias'] : "key"
11 keyPassword keystoreProperties['keyPassword'] ? keystoreProperties['keyPassword'] : "$System.env.KEY_PASSWORD"
12 storeFile file("../key.jks")
13 storePassword keystoreProperties['storePassword'] ? keystoreProperties['storePassword'] : "$System.env.STORE_PASSWORD"
14 }
15 }
16
17 buildTypes {
18 release {
19 signingConfig signingConfigs.release
20 }
21 }
22}

O primeiro bloco permite que o aplicativo leia o arquivo key.properties rootProject.file('key.properties') significa ler key.properties no diretório raiz do projeto. Se desejar alterar o nome do arquivo ou diretório, você pode modificar esta expressão.

Em seguida, podemos ver dois blocos sob o bloco android

signingConfigs descreve literalmente a configuração para assinatura. Você pode recuperar o conteúdo de key.properties aqui. Por exemplo, keystoreProperties['keyPassword'] fornece keyPassword valor de key.properties arquivo. $System.env.KEY_PASSWORD significa que você pode obter o valor do ambiente do sistema. file("../key.jks") significa que você pode carregar dados-chave diretamente do arquivo. Você pode escolher qualquer expressão que quiser.

A seguir, vamos construir o aplicativo.

bash
1flutter build apk --release

Você pode gerar o apk por este comando. O comando pode avisá-lo para usar o app bundle ou o apk dividido para evitar um apk , mas ignore-os agora. Eu explicarei isso mais tarde.

2. Registre segredos no GitHub

Registre os segredos necessários no GitHub . Precisamos de pelo menos segredos abaixo. Consulte Segredos criptografados para saber como registrar segredos no GitHub .

  • senha chave
  • senha de armazenamento
  • key.jks

Ao registrar arquivos binários como key.jks , você precisa da codificação base64 para gerar a string secreta. Não se esqueça de decodificá-lo ao usá-lo no pipeline.

Você também pode usar outros serviços de gerenciamento de segredo, como Azure KeyVault e GCP SecretManager . De qualquer forma, você deve separar o key.jks e quaisquer outros segredos do repositório de código-fonte e mantê-los seguros.

3. Adicionar fluxo de trabalho

Adicione o fluxo de trabalho usando o arquivo yaml. Crie um arquivo yaml como .github/workflows/xxx.yaml .

yaml
1name: CICD
2
3on:
4 pull_request:
5 branches: [master]
6 workflow_dispatch:
7
8jobs:
9 dev:
10 runs-on: ubuntu-latest
11
12 steps:
13 # checkout source code
14 - uses: actions/checkout@v2
15 # setup java
16 - name: set up JDK 1.8
17 uses: actions/setup-java@v1
18 with:
19 java-version: 1.8
20 # setup flutter
21 - name: Setup flutter
22 uses: subosito/flutter-action@v1
23 with:
24 flutter-version: "2.0.1"
25 # pub get
26 - run: flutter pub get
27 # analyze
28 - run: flutter analyze
29 # test
30 - run: flutter test
31 # build
32 - name: build dev
33 env:
34 KEY_JKS: ${{ secrets.KEY_JKS }}
35 KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
36 STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
37 GOOGLE_SERVICES: ${{ secrets.GOOGLE_SERVICES }}
38 run: echo $KEY_JKS | base64 --decode --ignore-garbage > android/key.jks && flutter build apk --release

Vou pular a explicação sobre a configuração do Java , configuração do Flutter pub get , analyze , test . A tarefa de construção consiste em alguns comandos conectados com && . Vamos ver um por um.

bash
1echo $KEY_JKS | base64 --decode --ignore-garbage > android/key.jks

Este comando significa gerar o key.jks partir de segredos. Como expliquei, key.jks é registrado como string codificada em base64, portanto, precisamos decodificá-lo e key.jks arquivo key.jks. Ao gerar o arquivo, o aplicativo pode carregar informações importantes do arquivo conforme configuramos o arquivo de armazenamento para ser o file("../key.jks") .

Na tarefa de construção, não key.properties arquivo key.properties. Isso ocorre porque configuramos keyPassword e storePassword como $System.env.KEY_PASSWORD . Definimos o ambiente conforme abaixo, para que o aplicativo possa carregar a senha do ambiente do sistema. Se desejar ler a senha de key.properties , você pode modificar bundle.gradle e gerar o key.properties aqui na tarefa de construção.

yaml
1- name: build dev
2 env:
3 KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
4 STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}

A seguir, vamos verificar o comando build.

bash
1flutter build apk --release

Isso é completamente igual à construção local. Portanto, você verá avisos que informam que você deve usar o app bundle ou o apk dividido para evitar um apk . De acordo com o site oficial Construindo o aplicativo para lançamento, o app bundle é recomendado. Portanto, ao lançar o aplicativo, é melhor criar um app bundle ; no entanto, ao testar o aplicativo em seu dispositivo ou usar o App Distribute do Firebase , você precisa do apk .

Portanto, recomendo escolher a forma de construir seu aplicativo de acordo com o propósito. Por exemplo, como CI diária, você pode gerar apk que permite testar facilmente em seu dispositivo, e como compilação de lançamento, você pode gerar app bundle e registrar o aplicativo na Google Play Store . Os exemplos abaixo fornecem os dois tipos de construção.

Registrar um aplicativo no App Distribution

yaml
1name: CICD
2
3on:
4 pull_request:
5 branches: [master]
6 workflow_dispatch:
7
8jobs:
9 dev:
10 runs-on: ubuntu-latest
11
12 steps:
13 # checkout source code
14 - uses: actions/checkout@v2
15 # setup java
16 - name: set up JDK 1.8
17 uses: actions/setup-java@v1
18 with:
19 java-version: 1.8
20 # setup flutter
21 - name: Setup flutter
22 uses: subosito/flutter-action@v1
23 with:
24 flutter-version: "2.0.1"
25 # pub get
26 - run: flutter pub get
27 # analyze
28 - run: flutter analyze
29 # test
30 - run: flutter test
31 # build
32 - name: build
33 env:
34 KEY_JKS: ${{ secrets.KEY_JKS }}
35 KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
36 STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
37 GOOGLE_SERVICES: ${{ secrets.GOOGLE_SERVICES }}
38 run: echo $KEY_JKS | base64 --decode --ignore-garbage > android/key.jks && echo $GOOGLE_SERVICES > android/app/google-services.json && flutter build apk --release
39 # app distribution
40 - name: Firebase App Distribution
41 uses: wzieba/Firebase-Distribution-Github-Action@v1.2.2
42 with:
43 appId: ${{secrets.FIREBASE_APP_ID}}
44 token: ${{secrets.FIREBASE_APP_TOKEN}}
45 file: build/app/outputs/apk/release/app-release.apk
46 groups: Tester

Registre um aplicativo na Google Play Store

yaml
1name: Release
2
3on:
4 workflow_dispatch:
5
6jobs:
7 dev:
8 runs-on: ubuntu-latest
9
10 steps:
11 # checkout source code
12 - uses: actions/checkout@v2
13 # setup java
14 - name: set up JDK 1.8
15 uses: actions/setup-java@v1
16 with:
17 java-version: 1.8
18 # setup flutter
19 - name: Setup flutter
20 uses: subosito/flutter-action@v1
21 with:
22 flutter-version: "2.0.1"
23 # pub get
24 - run: flutter pub get
25 # analyze
26 - run: flutter analyze
27 # test
28 - run: flutter test
29 # build
30 - name: build
31 env:
32 KEY_JKS: ${{ secrets.KEY_JKS }}
33 KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
34 STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
35 GOOGLE_SERVICES: ${{ secrets.GOOGLE_SERVICES }}
36 run: echo $KEY_JKS | base64 --decode --ignore-garbage > android/key.jks && flutter build appbundle --obfuscate --split-debug-info=build/app/outputs/symbols --release
37 # google play
38 - name: Create service account json
39 env:
40 SERVICE_ACCOUNT_JSON: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON}}
41 run: echo $SERVICE_ACCOUNT_JSON > android/app/service-account.json
42 - uses: r0adkll/upload-google-play@v1
43 with:
44 serviceAccountJson: android/app/service-account.json
45 packageName: your app id
46 releaseFiles: build/app/outputs/bundle/release/app-release.aab
47 track: alpha
48 whatsNewDirectory: release-notes
49 mappingFile: build/app/outputs/mapping/release/mapping.txt

4. Outras opções

4-1. Firebase google-services.json

bash
1echo $GOOGLE_SERVICES > android/app/google-services.json

Para se conectar ao Firebase , você precisa do google-services.json . Você pode gerar o json pelo comando acima. Para obter mais detalhes, consulte FlutterFire - instalação do Android.

4-2. App Distribution

App Distribution é um dos recursos que o Firebase oferece. Você pode entregar o aplicativo para testadores por meio deste recurso. Se você quiser usar este recurso, basta adicionar a tarefa abaixo ao pipeline.

yaml
1- name: Firebase App Distribution
2 uses: wzieba/Firebase-Distribution-Github-Action@v1.2.2
3 with:
4 appId: your app id
5 token: ${{secrets.FIREBASE_APP_TOKEN}}
6 file: build/app/outputs/apk/release/app-release.apk
7 groups: Tester

Consulte Firebase Distribution Github Action para obter mais informações. Observe que apenas o apk é permitido para o App Distribution .

4-3. Product Flavor

Ao usar o product flavor com Flutter , especifique --flavor <flavor name> no comando de construção. Consulte variantes de compilação para ver o product flavor

bash
1flutter build appbundle --obfuscate --split-debug-info=build/app/outputs/symbols --release --flavor jp

Com o flavor , você pode gerar IDs de aplicativos diferentes com o mesmo código.

gradle
1android {
2
3 defaultConfig {
4 applicationId "xxx.yyy.zzz"
5 ...
6 }
7
8 flavorDimensions "targetArea"
9 productFlavors {
10 jp {
11 applicationIdSuffix ".jp"
12 dimension "targetArea"
13 }
14 eu {
15 applicationIdSuffix ".eu"
16 dimension "targetArea"
17 }
18 }

Com o flavor , o id do aplicativo será a combinação de applicationId e applicationIdSuffix . Por exemplo, se você especificar --flavor jp , o id do aplicativo será xxx.yyy.zzz.jp

Você também pode modificar o arquivo de manifesto com flavor . Suponha que você usará o Google AdMob , em seguida, precisará adicionar applicationId ao arquivo de manifesto. Você pode fazer isso definindo arquivos de manifesto como abaixo.

txt
1android/app/src
2 |- main/AndroidManifest.xml
3 |- jp/AndroidManifest.xml
4 |- eu/AndroidManifest.xml

Coloque o arquivo de manifesto de base em android/app/src/main , em seguida, coloque cada arquivo de manifesto dependente de flavor android/app/src/<flavor name> . Em seguida, o aplicativo construído conterá o manifesto principal e o manifesto de sabor.

xml
1<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2 package="xxx.yyy.zzz">
3 <uses-permission android:name="android.permission.INTERNET" />
4 <application>
5 <meta-data
6 android:name="com.google.android.gms.ads.APPLICATION_ID"
7 android:value="your admob app id"/>
8 </application>
9</manifest>

4-4. --dart-define

Esta opção permite que você passe variáveis de ambiente para o aplicativo.

bash
1flutter build appbundle --obfuscate --split-debug-info=build/app/outputs/symbols --release --flavor jp --dart-define=REGION=asia-northeast1

Este comando gera um ambiente denominado REGION que descreve a região do servidor backend, por exemplo. O aplicativo pode ler o ambiente pelo código abaixo.

dart
1String region = String.fromEnvironment('REGION'); // get 'asia-northeast1'

4-5. Registre um aplicativo na Google Play Store

Ao registrar seu aplicativo na Google Play Store , use esta tarefa.

yaml
1- name: Create service account json
2 env:
3 SERVICE_ACCOUNT_JSON: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON}}
4 run: echo $SERVICE_ACCOUNT_JSON > android/app/service-account.json
5- uses: r0adkll/upload-google-play@v1
6 with:
7 serviceAccountJson: android/app/service-account.json
8 packageName: your app id
9 releaseFiles: build/app/outputs/bundle/release/app-release.aab
10 track: alpha
11 whatsNewDirectory: release-notes
12 mappingFile: build/app/outputs/mapping/release/mapping.txt

A primeira tarefa gera service-account.json que é necessário para acessar a Play Store do servidor CI Em seguida, especifique esse arquivo na segunda tarefa. Consulte upload google play para obter mais informações.

Referências

Construir e lançar um aplicativo Android

Segredos criptografados

Construindo o aplicativo para lançamento

FlutterFire - instalação do Android

Firebase CLI

Firebase Distribution Github Action

variantes de compilação

fazer upload do Google Play

Compartilhar:

Artigos relacionados

Como localizar o aplicativo no Flutter
Guides

Como localizar o aplicativo no Flutter

Aprenda a localizar seu aplicativo Flutter usando arquivos arb. Este artigo é baseado no Flutter 2.0.1.

mark241