Flutterの自動ビルドをGithub ActionsとFirebaseで行う

公開:2020/12/20
更新:2020/12/23
7 min読了の目安(約6900字TECH技術記事

Flutter #3 アドベントカレンダー 2020 - Qiita の20日目の記事です。

Flutterで開発中はホットリロードが使えるため、ビルドに困ることはありません。

しかし、いざ実機で確認しようと思うとビルド手順を忘れたり、ビルドに時間がかかって作業の手が止まることが多々あるかと思います。

今回はそれを解消するために、FlutterのCD環境をGithub ActionsとFirebase App Distributionを活用して準備しました。

前提

  1. コードのリポジトリ管理をGitHubで行っている
  2. FlutterにFirebaseを組み込んでいる

Firebaseを組み込んでない場合には、Bitriseを使うほうが良いかもしれません。

結論

実際にサンプルプロジェクトとして作ったコードを載せておきます。

iOSの自動ビルド

ios.yml
name: iOS CD

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    # iOSビルド用にmacOSを使う(消費ビルド時間に注意)
    runs-on: macos-latest

    steps:
      - uses: actions/checkout@v2

      # ProvisioningProfile をシークレットから取り出して、プロジェクトに無理やりセットする
      - name: Import Provisioning Profile
        run: |
          mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
          echo '${{ secrets.PROVISIONING_PROFILE }}' | openssl base64 -d -out ~/Library/MobileDevice/Provisioning\ Profiles/flutter_app_store.mobileprovision

      # 証明書を設定
      - name: Import Code-Signing Certificates
        uses: Apple-Actions/import-codesign-certs@v1
        with:
          p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
          p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}

      - name: install flutter
        uses: subosito/flutter-action@v1
        with:
          flutter-version: '1.24.0-10.2.pre'
          channel: 'beta'

      - name: flutter dependencies install
        run: flutter pub get

      - name: build ipa
        run: flutter build ios --release --no-codesign

      - name: iOS Build Action
        uses: yukiarrr/ios-build-action@v0.5.0
        with:
          project-path: ios/Runner.xcodeproj
          p12-base64: ${{ secrets.CERTIFICATES_P12 }}
          mobileprovision-base64: ${{ secrets.PROVISIONING_PROFILE }}
          code-signing-identity: Apple Distribution
          team-id: ${{ secrets.TEAM_ID }}
          workspace-path: ios/Runner.xcworkspace
          certificate-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
          output-path: app-release.ipa

      - name: Upload artifact
        uses: actions/upload-artifact@v1.0.0
        with:
          name: ios
          path: app-release.ipa

  deploy:
    needs: [build]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      # モジュールのダウンロード
      - name: Download artifact
        uses: actions/download-artifact@v1.0.0
        with:
          name: ios

      # Firebase にデプロイ
      - name: Firebase App Distribution
        uses: wzieba/Firebase-Distribution-Github-Action@v1.2.1
        with:
          appId: ${{secrets.FIREBASE_APP_ID_IOS}}
          token: ${{secrets.FIREBASE_TOKEN}}
          groups: testers
          file: ios/app-release.ipa

Androidの自動ビルド

android
name: Android CD

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
      - name: install java 8.x
        uses: actions/setup-java@v1
        with:
          java-version: '8.x'

      - name: setup cache
        uses: actions/cache@v1
        with:
          path: /Users/runner/hostedtoolcache/flutter
          key: ${{ runner.OS }}-flutter-install-cache

      - name: install flutter
        uses: subosito/flutter-action@v1
        with:
          flutter-version: '1.24.0-10.2.pre'
          channel: 'beta'

      - name: flutter dependencies install
        run: flutter pub get

      - name: build apk
        run: |
          echo '${{ secrets.KEYSTORE_BASE64 }}' | base64 -d > android/release.keystore
          export KEYSTORE_PASSWORD='${{ secrets.KEYSTORE_PASSWORD }}'
          export KEY_ALIAS='${{ secrets.KEY_ALIAS }}'
          export KEY_PASSWORD='${{ secrets.KEY_PASSWORD }}'
          flutter build apk --release

      - name: Upload artifact
        uses: actions/upload-artifact@v1.0.0
        with:
          name: android
          path: build/app/outputs/flutter-apk/app-release.apk

  deploy:
    needs: [build]
    runs-on: ubuntu-latest
    steps:

      - uses: actions/checkout@v2

      # モジュールのダウンロード
      - name: Download artifact
        uses: actions/download-artifact@v1.0.0
        with:
          name: android

      # Firebase にデプロイ
      - name: Firebase App Distribution
        uses: wzieba/Firebase-Distribution-Github-Action@v1.2.1
        with:
          appId: ${{secrets.FIREBASE_APP_ID_ANDROID}}
          token: ${{secrets.FIREBASE_TOKEN}}
          groups: testers
          file: android/app-release.apk

なぜGithub ActionsとFirebase App Distributionなのか

以前は Bitrise を利用していたのですが、

  • Xcodeのアップデート時にビルドが止まる(対応はすぐにしてくれました)
  • FlutterにFirebaseを組み込んだ時にビルド時間が大幅にかかって料金が上がってしまう

という点があったのでBitrise以外の以外のサービスを探したところ

  1. リポジトリの管理にGitHubを使っていた
  2. Firebaseをアプリに組み込んでいた

という状況で、GitHub ActionsとFirebase App DistributionでCD環境が構築できることを知り、この2つを利用することにしました。

Flutterのバージョンについて

今は beta チャンネルで開発を進めているので、ビルドも beta を使っています。

stable が良い人は install flutter の箇所で適宜適切な環境を設定すると良いでしょう。

flutter-version: '1.24.0-10.2.pre'
channel: 'beta'

Firebase App Distribution利用のうえでの注意

Firebase App Distributionを利用するうえで通常のビルドとは違う環境変数が必要になるので、記載しておきます。

FIREBASE_TOKEN

こちらは、Firebase CLI でログインしたときにtokenが表示されるので、

firebase login:ci

を実行してtokenを取得すれば大丈夫です。

Visit this URL on this device to log in:

Waiting for authentication...

✔  Success! Use this token to login on a CI server:

(ここにtokenが表示されます)

Example: firebase deploy --token "$FIREBASE_TOKEN"

FIREBASE_APP_ID_IOS, IREBASE_APP_ID_ANDROID

こちらはFirebaseとiOS,Androidのアプリを連携したときに表示されるIDです。(Bundle IDやPackage Nameではありません)

Firebaseコンソールの設定画面から確認することができます。

開発環境の切り替え

サンプルには記載していませんが、実際のプロジェクトでは、開発環境、ステージング環境、本番環境を切り替える必要があったので、monoさんの記事を参考に flavor で環境を切り分けられるようにしました。

そのため、実際のプロジェクトでは build apk build ipa で実行するビルドコマンドを

flutter build apk --release --flavor staging

というように設定していています。

各環境へのデプロイは main のところを対応するブランチ名に変えることで対応しました。

  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

課題

この方法でCD環境を作ったときに困ったことです。

  1. iOSの場合、macOSでのビルドであり、privateリポジトリの場合にはすぐにビルド時間を消費してしまう(masOSはビルド消費時間がubuntuの10倍のため)
  2. 同様の理由で、iOSのCDのテストをしようと思ったときにはactが使えないため、都度GitHub Actionsを実行する必要がある

これらの課題が解決または新たな課題が出てき次第更新していこうと思います。

最後まで読んでいただいてありがとうございました!