Applicazione Android CI/CD con Flutter

⏱️5 min
Condividi:

CI/CD pipeline è una parte essenziale dello sviluppo del software. Questo articolo spiega come costruire una CI/CD pipeline per creare un'applicazione Android Flutter . Costruiremo la pipeline in base alle GitHub Actions e non useremo fastlane .

1. Crea localmente

Prima di creare una pipeline su GitHub , creiamo un'app in locale. Puoi preparare l'ambiente di compilazione con il sito web ufficiale - Crea e rilascia un'app Android. Spiegheremo alcuni dei contenuti qui.

1-1. key.jks e key.properties

La creazione di Android richiede la chiave per firmare. key.jks contiene la chiave e key.properties contiene la password. key.jks può essere generato dai seguenti comandi.

finestre

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

Quando si utilizzano questi comandi, è necessaria la key password e la store password . Registra queste password nel file key.properties

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

Quindi, modifica Android file gradle di Android per utilizzare questi file per firmare la tua app.

1-2. app / bundle.gradle

Correggi il file bundle.gradle Nota che ci sono due bundle.gradle nel progetto. Usiamo il file nella directory 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}

Il primo blocco consente all'app di leggere il file key.properties rootProject.file('key.properties') significa leggere key.properties nella directory root del progetto. Se si desidera modificare il nome del file o la directory, è possibile modificare questa espressione.

Successivamente possiamo vedere due blocchi sotto il blocco android

signingConfigs descrive letteralmente la configurazione per la firma. Puoi recuperare il contenuto di key.properties qui. Ad esempio, keystoreProperties['keyPassword'] fornisce keyPassword valore dal key.properties file. $System.env.KEY_PASSWORD significa che puoi ottenere il valore dall'ambiente di sistema. file("../key.jks") significa che puoi caricare direttamente i dati della chiave dal file. Puoi scegliere qualunque espressione ti piaccia.

Successivamente, creiamo l'app.

bash
1flutter build apk --release

Puoi generare un apk con questo comando. Il comando potrebbe avvisarti di utilizzare app bundle o split apk per evitare fat apk , ma ignorali subito. Lo spiegherò più tardi.

2. Registra i segreti su GitHub

Registra i segreti necessari su GitHub . Abbiamo bisogno almeno di segreti sotto. Consulta Encrypted Secrets per scoprire come registrare i segreti su GitHub .

  • password chiave
  • memorizzare la password
  • key.jks

Quando registri file binari come key.jks , hai bisogno della codifica base64 per generare una stringa segreta. Non dimenticare di decodificarlo quando lo usi nella pipeline.

Puoi anche usare altri servizi di gestione dei segreti come Azure KeyVault e GCP SecretManager . Ad ogni modo, devi separare key.jks e qualsiasi altro segreto dal repository del codice sorgente e tenerli al sicuro.

3. Aggiungi flusso di lavoro

Aggiungi flusso di lavoro utilizzando il file yaml. Crea file yaml come .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

Salterò la spiegazione sulla configurazione di Java , la configurazione di Flutter pub get , l' analyze , il test . L'attività di compilazione consiste in alcuni comandi collegati a && . Vediamoli uno per uno.

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

Questo comando significa generare il key.jks dai segreti. Come ho spiegato, key.jks è registrato come stringa codificata in base64, quindi qui dobbiamo decodificarlo e key.jks file key.jks. Generando il file, l'app può caricare le informazioni chiave dal file mentre configuriamo il file di file("../key.jks") .

Nell'attività di compilazione, non ho generato il file key.properties Questo perché configuriamo keyPassword e storePassword come $System.env.KEY_PASSWORD . Abbiamo impostato l'ambiente come di seguito, quindi l'app può caricare la password dall'ambiente di sistema. Se vuoi leggere la password da key.properties , puoi modificare bundle.gradle e generare il key.properties qui nell'attività di compilazione.

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

Quindi, controlliamo il comando di compilazione.

bash
1flutter build apk --release

Questo è completamente lo stesso della build locale. Quindi vedrai avvisi che ti dicono che dovresti usare app bundle o split apk per evitare fat apk . Secondo il sito ufficiale Building the app for release, si consiglia il app bundle Quindi, quando rilasci l'app, è meglio creare un app bundle , tuttavia quando provi l'app sul tuo dispositivo o usi App Distribute di Firebase , hai bisogno di apk .

Quindi consiglio di scegliere il modo in cui costruire la tua app in base allo scopo. Ad esempio, come CI giornaliera, puoi generare apk che ti consente di testare facilmente sul tuo dispositivo e, come build di rilascio, puoi generare app bundle e registrare l'app su Google Play Store . Gli esempi seguenti forniscono entrambi i tipi di build.

Registra un'app con 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

Registra un'app con 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. Altre opzioni

4-1. Firebase google-services.json

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

Per connetterti con Firebase , hai bisogno di google-services.json . Puoi generare il json con il comando sopra. Per maggiori dettagli, vedere FlutterFire - installazione Android.

4-2. App Distribution

App Distribution è una delle funzionalità fornite da Firebase Puoi consegnare l'app ai tester tramite questa funzione. Se si desidera utilizzare questa funzione, è sufficiente aggiungere l'attività seguente alla 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

Per ulteriori informazioni, vedere Azione Github di distribuzione Firebase. Tieni presente che solo il apk è consentito per la App Distribution .

4-3. Product Flavor

Quando usi product flavor con Flutter , specifica --flavor <flavor name> nel comando build. Vedi varianti di build per il product flavor

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

Con flavor , puoi generare diversi ID app con lo stesso codice.

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 }

Con flavor , l'id dell'app sarà la combinazione di applicationId e applicationIdSuffix . Ad esempio, se specifichi --flavor jp , l'ID dell'app sarà xxx.yyy.zzz.jp

Puoi anche modificare il file manifest con flavor . Supponi di utilizzare Google AdMob , quindi devi aggiungere applicationId al file manifest. Puoi farlo impostando i file manifest come di seguito.

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

Metti il file manifest di base in android/app/src/main , quindi metti ogni file manifest dipendente dal flavor android/app/src/<flavor name> . Quindi l'app creata conterrà sia il manifest principale che il manifest dell'aroma.

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

Questa opzione consente di passare le variabili di ambiente all'app.

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

Questo comando genera un ambiente denominato REGION che descrive la regione del server di backend, ad esempio. L'app può leggere l'ambiente tramite il codice sottostante.

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

4-5. Registra un'app su Google Play Store

Quando registri la tua app su Google Play Store , utilizza questa attività.

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

La prima attività genera service-account.json necessario per accedere a Play Store dal server CI Quindi specificare questo file nella seconda attività. Per ulteriori informazioni, vedere upload google play.

Riferimenti

Crea e rilascia un'app Android

Segreti crittografati

Creazione dell'app per il rilascio

FlutterFire - installazione Android

Firebase CLI

Azione Github di distribuzione Firebase

varianti build

carica google play

Condividi:

Articoli correlati

Come localizzare l'app in Flutter
Guides

Come localizzare l'app in Flutter

Scopri come localizzare la tua app Flutter usando i file arb. Questo articolo è basato su Flutter 2.0.1.

mark241