Flutter アプリを Firebase App Distribution へアップロードするまでの流れ

作成日: 2022-07-01 /

作業メモを移行しているため、前提条件が抜けており、一部わかりづらい記載があります。

概要

実機でのアプリの動作確認をしたいのと、ビジネス側にも動作を見てもらいたいのと開発メンバーみんなで QA をしていく体制を取りたい。そのため、Firebase App Distribution にバイナリをアップロードしてダウンロードできるようにしたい。ある程度作業が必要であるため、ここに作業の流れを書いておく。新しいアプリを作るときにこれを見て、スムーズにダウンロードできるようにしたい。

使用するサービスや技術

使っている技術やサービスについては以下の通り。

共通

Firebase (Firebase App Distribution) と Github Actions (Workflow Dispatch)

iOS

Apple Developer Program のアカウント。

Android

Google Play Console のアカウント。

反映するまでの流れ

  1. 開発環境用の Firebase プロジェクトを作
  2. iOS と Android アプリを登録する。
  3. App Distribution のサービス使用開始する。
  4. テスターのグループを作成し、アプリに関わる人をグループに追加する。
  5. (iOS) Xcode で Provisioning File を読み込む。
  6. (iOS) Provisioning File の名前を GitHub Actions に書き込む。
  7. (iOS) 一度ローカルで ipa を作り、ExportOptions.plist を生成する。
  8. Firebase から設定ファイルをダウンロードして、iOS / Android ディレクトリに入れる。
  9. Github Actions の secrets に各 OS で必要な情報を入れていく。
  10. Github Actions の Workflow Dispatch を実行する。
  11. テスターたちにアプリをダウンロードできるかを確認してもらう。

1. 開発環境用の Firebase プロジェクトを作成する

Firebase プロジェクトを新しく作成してもらうように、ビジネス側に依頼する。

2. iOS と Android アプリを登録する

プロジェクトを作成したら自然と出てきますが、こちらのアプリを登録する画面で iOS / Android 両方を登録する。(application id や bundle id などを入力する。)

3. App Distribution のサービス使用開始する

実際にアップロードの作業を始める前に、App Distribution のサービスを開始する必要がある。iOS / Android のアイコンをクリックし、初期設定する。

4. テスターのグループを作成し、そこにアプリに関わる人をグループに追加する

アップロードするときの API の中でグループを指定しないといけないのと、バイナリを公開する人を制限しないといけないことから、テスターのグループを作成する。グループの名前については任意だが、Github Actions の workflow の groups のパラメーターに同じものを渡さないと、404 Not Found とエラーが出てしまう。

5. (iOS) Xcode で Provisioning File を読み込む

デプロイで使用するための Adhoc の Provisioning File を指定する必要がある。作り方はここでは割愛する。 何か iOS の Capabilities を増やした場合や新しいメンバーが増えてデバイスを増やしたい場合は、Provisioning File を再ダウンロードをする。そのあと、Provisioning file を更新するために再度 Xcode でアップロードする。

注意することは下記となる。

  • Development Team: しっかり意図したものになっているか決まっているか
    • Bundle Identifier: しっかり意図したものになっているか決まっているか
    • Signing Certificate: iPhone Distribution になっているか。
  • Provisioning file の中身で許可するデバイスの UID が実際に配布する人のデバイスを含んでいるか。

6. (iOS) Provisioning File の名前を GitHub Actions に書き込む

GitHub Actions 内でビルドを行うのに Provisioning File の名前を指定しないといけないので、アプリごとでファイル名を決める。

.github/workflows/manual_build_and_deploy_for_ios.yml
   - name: Import Provisioning Profile
     run: |
       mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
       echo '${{ secrets.PROVISIONING_PROFILE }}' | base64 -d > ~/Library/MobileDevice/Provisioning\ Profiles/apps\ dev.mobileprovision # ここの .mobileprovision ファイルの名前
       echo '${{ secrets.NOTIFICATION_SERVICE_PROVISIONING_PROFILE }}' | base64 -d > ~/Library/MobileDevice/Provisioning\ Profiles/apps\ ImageNotification.mobileprovision # ここの .mobileprovision ファイルの名前

7. (iOS) 一度ローカルで ipa を作り、ExportOptions.plist を生成する

iOS について、実機で検証できるように ipa ファイルを GitHub Actions で Firebase App Distribution にアップロードする必要がある。そのために GitHub Actions 内で flutter build ipa コマンドを実行するが、export-method を adhoc に選択することを事前に決める必要がある。その情報が入った ExportOptions.plist ファイルを ipa コマンドの --export-option オプションに渡す必要があり、一度ローカルで ipa ファイルを生成する。

1. xrarchive の生成

まずはこちらのコマンドで xrarchive ファイルを生成する。

$ cd path/to/app repository
$ fvm flutter build ipa --release --dart-define=ENVIRONMENT=STAGING

2. ExportOptions.plist のオプションたちを決める

まず、xrarchive ファイルを finder で見つける。

$ open /path/to/apps/build/ios/archive

そのあと、xrarchive ファイルをダブルクリックすると、下の xrachive 一覧が表示される。「Distribute App」をクリックし、オプションたちを決めていく。その後、method に Adhoc を選択する。そのあと証明書と Provisioning Files も選択する。最後に export をクリックして、ipa を生成する。

3. ipa 生成時の ExportOptions.plist をアプリリポジトリへ移動

ipa が生成されるディレクトリの中に ExportOptions.plist が入っているので、それをアプリディレクトリの ios ディレクトリに入れる。

$ cat ~/path/to/ipa ios/ExportOptions.plist

出力された結果がこんな感じになったら問題なし。

ExportOptions.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>compileBitcode</key>
  <true/>
  <key>destination</key>
  <string>export</string>
  <key>method</key>
  <string>ad-hoc</string>
  <key>provisioningProfiles</key>
  <dict>
    <key>bundle.id</key>
    <string>app.dev</string>
    <key>bundle.id.ImageNotification</key>
    <string>app.dev.ImageNotification</string>
  </dict>
  <key>signingCertificate</key>
  <string>hash</string>
  <key>signingStyle</key>
  <string>manual</string>
  <key>stripSwiftSymbols</key>
  <true/>
  <key>teamID</key>
  <string>WCPA535FQM</string>
  <key>thinning</key>
  <string>&lt;none&gt;</string>
</dict>
</plist>

それをアプリの ios ディレクトリに入れる。

$ cp ~/path/to/app/ExportOptions.plist ~/path/to/app/repository/ios/ExportOptions.plist

8. Firebase から設定ファイルをダウンロードして、iOS / Android ディレクトリに入れる

Firebase プロジェクトから設定ファイルをダウンロードし、各 OS で正しいパスに配置する。

iOSAndroid
/path/to/app/repository/ios/Runner/GoogleService-Info.plist/path/to/app/repository/android/app/google-services.json

9. Github Actions の secrets に各 OS で 必要な情報を入れていく

Github Actions の Workflow Dispatch を実行する前に、色々な情報を secrets にいれていく必要があるので、どんなキーにどんな値を入れるかをまとめる。

共通で使うもの

iOS と Android のアプリを反映するのに使うのがこちら。

キー説明値がどこにあるか
FIREBASE_TOKENFirebase CLIで発行できるトークン下の FIREBASE_TOKEN を発行する流れで作成する。
SUBMODULE_ACCESSKEYアプリのリポジトリから module を取得するのに必要なキー公開鍵/秘密鍵を作成する

※ key をコピーする際にうまく設定できない場合がある、$ pbcopy でダメだったら cat してそれをコピーするなど改行を含めるなど試してみてください。

FIREBASE_TOKEN を発行する流れ

(詳しくは Firebaseのドキュメントを参照)

npm で firebase-tools のパッケージをインストールし、ログインを済ませる。

$ npm install -g firebase-tools
$ firebase login:ci

その後、 firebase login:ci を実行すると、トークンが発行される。

$ firebase login:ci

Visit this URL on this device to log in:
https://accounts.google.com/o/oauth2/auth?xxx=yyy

Waiting for authentication...

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

access token (これがトークンの値)

iOS

キー説明値がどこにあるか
FIREBASE_APP_ID_IOSFirebase にアプリを登録したときに発行されるIDFirebase Console
CERTIFICATES_P12証明書Apple Developer Program
CERTIFICATES_P12_PASSWORD証明書を作成したときのパスワードApple Developer Programで証明書を作成するときに入力したもの
PROVISIONING_PROFILEプロビジョニングファイルApple Developer Program
NOTIFICATION_SERVICE_PROVISIONING_PROFILE通知サービス用のプロビジョニングファイルApple Developer Program
CERTIFICATES_P12

セキュアな画面であり量が多くなるため割愛するが、証明書の TYPEiOS Distribution になっているかを確認する。特に新しく作成する必要がないのであれば、一旦 expired が 2022/12/29 がものにする。p12 ファイルに対して、 base64 をかけたものを secrets に入れる。

CERTIFICATES_P12_PASSWORD

これは p12 ファイルへ出力されるときにパスワードを決められるので、それを覚えておく。

PROVISIONING_PROFILE

5 のステップで作った Provisioning File を base64 にかけたものを環境変数に入れる。5 のステップで書いたように、何か Provisioning file に更新がかかったときは再度ダウンロードして、base64 をかけたものを環境変数に入れる。

$ base64 -i path/to/provisioningfile.mobileprovision
NOTIFICATION_SERVICE_PROVISIONING_PROFILE

5 のステップで作った Provisioning File を base64 にかけたものを環境変数に入れる。

$ base64 -i path/to/ImageNotificationprovisioningfile.mobileprovision

Android

キー説明値がどこにあるか
FIREBASE_APP_ID_ANDROIDfirebase にアプリを登録したときに発行されるIDFirebase Console
ANDROID_KEY_JKSjks ファイルを base64 化したもの--
ANDROID_KEY_PROPERTIESandroid/key.properties に入れるもの--
FIREBASE_APP_ID_ANDROID

Firebase Console に書いてあるので、そこから取得する。

ANDROID_KEY_JKS

ファイルをダウンロードし、下のコマンドを実行する。

$ base64 -i path/to/jks_file.jks
ANDROID_KEY_PROPERTIES

このようなフォーマットで環境変数にいれる必要がある。

storePassword=<storePassword>
keyPassword=<keyPassword>
keyAlias=<keyAlias>
storeFile=/home/runner/work/<repository name>/<repository name>/android/upload_key.jks

10. Github Actions の Workflow Dispatch を実行する

環境変数を入れ終わったら、実際に Github Actions を動かしていく。

manual build & deploy for iOS と manual build & deploy for Android を選択する。Workflow Dispatch の中で決められるいくつかの選択肢を埋めて、実行する。

keyvalue
branchビルドをしたいコードが入っているブランチ
release notesリリースノート。App Distribution のページで、アップロードされたバイナリの近くに記載される文言。何か確認したいことだったり、最後リリース前の検証などのメモをかけるものになっている。何も埋めなかったら、そのブランチの最新のコミットメッセージが入る。

実行時間としては、 iOS は30 ~ 40分、Android は20分ほどで終わる。他にも iOS の機能だったり使用するライブラリが増えたりすると、さらに時間がかかる。

deploy_staging_ios.yml
name: Deploy Staging iOS

on:
  workflow_dispatch:
    inputs:
      branche:
        description: "Set the branch to build."
        required: true
        default: "develop"
      releaseNotes:
        description: "release notes"
        default: ""

jobs:
  build:
    runs-on: macos-14
    timeout-minutes: 60
    steps:
      - name: Select Xcode version
        run: sudo xcode-select -s '/Applications/Xcode_15.3.app/Contents/Developer'

      - uses: actions/checkout@v3
        with:
          fetch-depth: 1
          ref: ${{ github.event.inputs.branche }}

      - name: make ssh directory
        run: |
          mkdir -p $HOME/.ssh/

      - name: checkout module-core
        env:
          TOKEN: ${{ secrets.SUBMODULE_ACCESSKEY }}
        run: |
          echo -e "$TOKEN" > $HOME/.ssh/id_module_core_rsa
          chmod 600 $HOME/.ssh/id_module_core_rsa
          export GIT_SSH_COMMAND="ssh -i $HOME/.ssh/id_module_core_rsa"
          git submodule update --init --force --recursive lib_core

      - name: Create dot env file
        run: |
          touch .env
          echo "${{ secrets.SECRETS_ENV }}" >> .env

      - name: Import Provisioning Profile
        run: |
          mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
          echo '${{ secrets.PROVISIONING_PROFILE }}' | base64 -d > ~/Library/MobileDevice/Provisioning\ Profiles/apps\ dev.mobileprovision
          echo '${{ secrets.NOTIFICATION_SERVICE_PROVISIONING_PROFILE }}' | base64 -d > ~/Library/MobileDevice/Provisioning\ Profiles/dev\ ImageNotification.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 }}

      - uses: kuhnroyal/flutter-fvm-config-action@v2
        id: fvm-config-action

      - uses: subosito/[email protected]
        with:
          channel: ${{ steps.fvm-config-action.outputs.FLUTTER_CHANNEL }}
          flutter-version: ${{ steps.fvm-config-action.outputs.FLUTTER_VERSION }}

      - name: flutter dependencies install
        run: flutter pub get

      - name: build ipa
        run: flutter build ipa --export-options-plist=ios/ExportOptions/stg.plist --release --dart-define-from-file=flavor/STAGING.json

      - name: Upload artifact
        uses: actions/[email protected]
        with:
          name: ios
          path: /Users/runner/work/${{ github.event.repository.name }}/${{ github.event.repository.name }}/build/ios/ipa/module_app.ipa
          if-no-files-found: error

  deploy:
    needs: [build]
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 1

      - name: Download artifact
        uses: actions/[email protected]
        with:
          name: ios
          path: ios

      - name: Firebase App Distribution
        uses: wzieba/[email protected]
        with:
          appId: ${{secrets.FIREBASE_APP_ID_IOS}}
          token: ${{secrets.FIREBASE_TOKEN}}
          groups: groups
          file: ios/module_app.ipa
          releaseNotes: ${{ github.event.inputs.releaseNotes || github.event.head_commit.message }}

      - name: Delete artifact
        uses: geekyeggo/delete-artifact@v1
        with:
          name: ios
deploy_staging_android.yml
name: Deploy Staging Android

on:
  workflow_dispatch:
    inputs:
      branche:
        description: "Set the branch to build."
        required: true
        default: "develop"
      releaseNotes:
        description: "release notes"
        default: ""

jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 1
          ref: ${{ github.event.inputs.branche }}

      - name: make ssh directory
        run: |
          mkdir -p $HOME/.ssh/

      - name: checkout module-core
        env:
          TOKEN: ${{ secrets.SUBMODULE_ACCESSKEY }}
        run: |
          echo -e "$TOKEN" > $HOME/.ssh/id_module_core_rsa
          chmod 600 $HOME/.ssh/id_module_core_rsa
          export GIT_SSH_COMMAND="ssh -i $HOME/.ssh/id_module_core_rsa"
          git submodule update --init --force --recursive lib_core

      - name: Create dot env file
        run: |
          touch .env
          echo "${{ secrets.SECRETS_ENV }}" >> .env

      - name: Create key.properties
        run: |
          echo ${{ secrets.ANDROID_KEY_JKS }} | base64 -d > android/upload_key.jks

          touch android/key.properties
          echo "${{ secrets.ANDROID_KEY_PROPERTIES }}" >> android/key.properties

      - name: install java 17.x
        uses: actions/setup-java@v4
        with:
          distribution: "zulu"
          java-version: "17.x"

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

      - uses: kuhnroyal/flutter-fvm-config-action@v2
        id: fvm-config-action

      - uses: subosito/[email protected]
        with:
          channel: ${{ steps.fvm-config-action.outputs.FLUTTER_CHANNEL }}
          flutter-version: ${{ steps.fvm-config-action.outputs.FLUTTER_VERSION }}

      - name: flutter dependencies install
        run: flutter pub get

      - name: build apk
        run: flutter build apk --release --dart-define-from-file=flavor/STAGING.json
        env:
          JAVA_OPTS: "-Xmx7G"

      - name: Upload artifact
        uses: actions/[email protected]
        with:
          name: android
          path: build/app/outputs/flutter-apk/app-release.apk

  deploy:
    needs: [build]
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 1

      - name: Download artifact
        uses: actions/[email protected]
        with:
          name: android
          path: android

      - name: upload artifact to Firebase App Distribution
        uses: wzieba/[email protected]
        with:
          appId: ${{secrets.FIREBASE_APP_ID_ANDROID}}
          token: ${{secrets.FIREBASE_TOKEN}}
          groups: groups
          file: android/app-release.apk
          releaseNotes: ${{ github.event.inputs.releaseNotes || github.event.head_commit.message }}

      - name: Delete artifact
        uses: geekyeggo/delete-artifact@v1
        with:
          name: android

10. テスターたちにアプリをダウンロードできるかを確認してもらう

iOS と Android の両方とも App Distribution のページでダウンロードする。

iOS

招待メールの中のリンクを踏み、基本的にこの iOS Firebase App Distribution を使用して iOS アプリを配布するの記事に書いてあることに従う。ダウンロードしたプロファイルがスマホの中に入れば問題なし。

[iOS] Firebase App Distributionを使用してiOSアプリを配布する | DevelopersIO

[iOS] Firebase App Distributionを使用してiOSアプリを配布する | DevelopersIO

https://dev.classmethod.jp

[iOS] Firebase App Distributionを使用してiOSアプリを配布する | DevelopersIO

Android

特にやることなく、メールの中にリンクを踏むだけ問題なし。

11. おわり

お疲れ様でした。