FlutterアプリをGitHub Actionsを使ってwebアプリとandroid apkを同時にbuildし公開するまで

# はじめに
私は普段休みの日の朝を中心に、普段家庭内の生活に使ったり、子供と遊ぶための便利なアプリなどを作ってます。
個人開発なので、あまり手間をかけずにサクッと色んなものを作りたいと思っていたときに
[Flutter](https://flutter.dev/)に出会いました。
Flutterはほぼローコード開発ツールやノーコードツールと同じくらいと言っていいくらい
あまり手間がかからずそれなりのアプリをサクッと開発できます。
はじめは、手元でbuildし、いちいちサーバにdeployするといった作業をしていましたが、貴重な土日の朝の時間
少しでも手間を減らしたいと思っていました。
手元ではコードを書いてテストを走らせ、かんたんにエミュレータで確認すると言った作業だけに集中し、
buildやdeployはGitHub Actionsに任せようと思い色々調査をした結果をメモしています。

# flutter web を使ってwebアプリを gh-pagesに公開する
## 前提
この記事では前提として下記を想定しています。
- Flutterでアプリ開発をしている
- GitHub上にレポジトリが配置されている
- レポジトリ上にgh-pagesというbranchが作成されている

## gh-pagesの設定
まずはweb版をdeployするための下地としてgh-pagesを設定しましょう
https://github.com/[あなたのユーザID]/[対象のレポジトリ]/settings
に行き gh-pagesの設定を下記のようにします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1817/2e75e012-c143-215c-c577-d3e251290526.png)


## GitHub Actionsの設定
 /path/to/[flutterアプリのルート]/.github/workflows/flutter.yaml 
を作成しましょう

```yaml
name: Flutter CI
'on':
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout 🛎️
        uses: actions/checkout@v2
      - name: Flutter setting
        uses: subosito/flutter-action@v1
        with:
          channel: beta
      - name: "Install and Build  🔧"
        run: |
          flutter config --enable-web
          flutter pub get
          flutter test
          flutter build web
      - name: "Deploy 🚀"
        uses: JamesIves/github-pages-deploy-action@releases/v3
        with:
          GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
          BRANCH: gh-pages
          FOLDER: build/web
```
これを**main**にpushしましょう

## web appがdeployされていることを確認しましょう
pushすると自動的にGitHub Actions Flutter CIが走ると思います
こちらが終わったら(自分のレポジトリの場合は3分位かかります^^;)
https://[あなたのgithubカウント].github.io/[レポジトリ名]/
に行ってあなたのFlutterアプリが動くかどうか確認してください

# apkをGitHub Actionsを使ってsignする
## なぜsignが必要か
自分の場合これを調査したきっかけは、[firebase_auth](https://pub.dev/packages/firebase_auth)を使った場合、
ではkeystoreのSHA-1 fingerprintをfirebase consoleに設定しないと
[google_sign_in](https://pub.dev/packages/google_sign_in)がAndroid app版では動かないとう症状に悩まされたことと、
基本的にはbuildはGitHub Actionsを使ってやりたいなと思っていたたため、これを同時に出来る方法が無いか調査していました。
またセキュリティ上の理由から、keystoreファイルや生のパスワードをgithub repositryにpushすることは流石に嫌ですよね。
もしこれをやってしまうと悪意を持った人がリポジトリにアクセスし、
Google Play にアプリをあなたになりすまして公開ができるようになってしまいます。
以下の章では[Signing Flutter Android apps for release in GitHub Actions](https://dev.to/cddelta/signing-flutter-android-apps-for-release-in-github-actions-2892)を参考にさせていただき
GitHub Actionsを使ってflutter build apkを使って安全にAndroidをリリース署名を行う方法を解説します。

## keystoreを作成する
[Signing the app](https://flutter.dev/docs/deployment/android#signing-the-app) にある方法を使って
keystoreを作成してください
例えばWindowsであれば

```bash
$ keytool -genkey -v -keystore c:\Users\USER_NAME\key.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias [ALIAS]
```
このようにして作成します。
また作成時に利用した**KEY_STORE_PASSWORD**, **KEY_PASSWORD**, **ALIAS**は忘れないで覚えるかメモしてください

## SIGNING_KEYを取得する
opensslコマンドを使えば **SIGNING_KEY**を取得できます

```bash
$ openssl.exe base64 -A -in c:\Users\USER_NAME\key.jks
/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.....xxxxxxxxxxxxxx=
```
出力されたながーい文字列をコピペしてメモしておきましょう
## (option) SHA-1 fingerprintをfirebase consoleのAndroidアプリに設定しましょう
自分がもともとハマっていたのはここで、先程つくったkeystoreを下記のコマンドを使ってSHA-1 fingerprintを得て
それをメモしておきましょう

```bash
$ keytool.exe  -list -v --alias [ALIAS] -keystore c:\Users\USER_NAME\key.jks
Enter keystore password:  [KEY_STORE_PASSWORD]
Alias name: [ALIAS]
Creation date: YYYY/MM/DD
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: C=US, O=Android, CN=Android Debug
Issuer: C=US, O=Android, CN=Android Debug
Serial number: 1
Valid from: Fri Sep 25 20:27:37 JST 2020 until: Sun Sep 18 20:27:37 JST 2050
Certificate fingerprints:
         MD5:  XX:XX:XX..
         SHA1: XX:XX:XX..
         SHA256: XX:XX:XX..
Signature algorithm name: SHA1withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 1
```
上記に表示されるSHA1 XX:XX:XXとSHA256XX:XX:XXをコピーして
[Firebase console](https://console.firebase.google.com/)に行き設定しましょう
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1817/52198311-d863-d042-cfd0-8981859a283e.png)


## 手元でbuildする準備をする
### key.propertiesを作成
 /path/to/[flutterアプリのルート]/android/key.properties
を作成します

```
storePassword=[先程メモったKEY_STORE_PASSWORD]
keyPassword=[先程メモったKEY_PASSWORD]
keyAlias=[先程メモったALIAS]
storeFile=[c:\Users\USER_NAME\key.jks　など]
```
### key.propertiesを.gitignoreに忘れずに入れておきましょう
key.propertiesを公開してしまったら元も子もないので
ちゃんと.gitignoreに入れておきましょう

```bash
$ echo android/key.properties >> .gitignore
```
### build.gradleの編集
 /path/to/[flutterアプリのルート]/android/app/build.gradle
を編集します
編集する箇所は2つ
まずは key.propertiesがあったら
先程設定した key.propertiesから**KEY_STORE_PASSWORD**, **KEY_PASSWORD**, **ALIAS**と**KEY_PATH**を読み込むように編集します
GitHub Actions上では環境変数から読み込むのでelseの中身はGitHub Actions用です

```gradle
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
} else {
    keystoreProperties.setProperty('storePassword', System.getenv('KEY_STORE_PASSWORD'));
    keystoreProperties.setProperty('keyPassword', System.getenv('KEY_PASSWORD'));
    keystoreProperties.setProperty('keyAlias', System.getenv('ALIAS'));
    keystoreProperties.setProperty('storeFile', System.getenv('KEY_PATH'));
}
```
次にsignできるようにbuildTypesの上にsiningConfigsを追加し、
更にbuildTypesもsigningConfigs.releaseに変えましょう

```gradle   
signingConfigs {
        release {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
            storePassword keystoreProperties['storePassword']
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
    /*
    buildTypes {
        release {
            // TODO: Add your own signing config for the release build.
            // Signing with the debug keys for now, so `flutter run --release` works.
            signingConfig signingConfigs.debug
        }
    }*/
```

##　手元でbuildしてみましょう

```bash
$ flutter build apk
You are building a fat APK that includes binaries for android-arm, android-arm64, android-x64.
If you are deploying the app to the Play Store, it's recommended to use app bundles or split the APK to reduce the APK size.
    To generate an app bundle, run:
        flutter build appbundle --target-platform android-arm,android-arm64,android-x64
        Learn more: https://developer.android.com/guide/app-bundle
    To split the APKs per ABI, run:
        flutter build apk --target-platform android-arm,android-arm64,android-x64 --split-per-abi
        Learn more: https://developer.android.com/studio/build/configure-apk-splits#configure-abi-split
Running Gradle task 'assembleRelease'...                           19.0s
√ Built build\app\outputs\flutter-apk\app-release.apk (7.6MB).
```
このようにbuildできたら成功です

## GitHubのレポジトリのsecretsにこれらを設定していきます
https://github.com/[あなたのユーザID]/[対象のレポジトリ]/settings/secrets
に行って
右上の**New secret**を押下してメモした**KEY_STORE_PASSWORD**, **KEY_PASSWORD**, **ALIAS**と**SIGNING_KEY**
を設定しましょう

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1817/dc7bd69e-a582-ed7a-5ced-38ed024ddab3.png)
## .github/workflows/flutter.yamlを編集
さあ　後少しで終わりです
 /path/to/[flutterアプリのルート]/.github/workflows/flutter.yaml 
を下記のように書き換えましょう

```yaml
name: Flutter CI
'on':
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout 🛎️
        uses: actions/checkout@v2
      - name: Flutter setting
        uses: subosito/flutter-action@v1
        with:
          channel: beta
      - name: "Install and Build  🔧"
        run: |
          echo $SIGNING_KEY | base64 -d > android/app/key.jks
          flutter config --enable-web
          flutter pub get
          flutter test
          flutter build apk
          flutter build web
          mkdir build/web/apks
          cp build/app/outputs/flutter-apk/*.apk build/web/apks/
        env:
          SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
          KEY_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
          ALIAS: ${{ secrets.ALIAS }}
          KEY_PATH: key.jks
      - name: "Deploy apk 🚀"
        uses: actions/upload-artifact@v1
        with:
          name: release-apk
          path: build/app/outputs/flutter-apk/app-release.apk
      - name: "Deploy to gh-pages 🚀"
        uses: JamesIves/github-pages-deploy-action@releases/v3
        with:
          GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
          BRANCH: gh-pages
          FOLDER: build/web
```
これをまた**main**にpushしましょう
成功すると
https://[あなたのgithubカウント].github.io/[レポジトリ名]/apks/app-release.apk にアプリが配置されます

# デモアプリはこちら
![Demo](https://freddiefujiwara.com/Osaifu-Minimalist/images/ezgif.com-gif-maker.gif)

[Demo App](https://github.com/freddiefujiwara/jpy_minimize_your_change)


# おわりに
ちょっと長かったですが、なんとかこれで
web appとAndroid apk(野良アプリ)を gh-pages上に同時公開することができました。
すごく極端な話、Android Studioがなくても GitHub上でかんたんなbug fixとか編集してしまえば
自動的にGitHub Actionsが走り、ビルドされ、Deployまでできます