0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FlutterとSupabaseでgoogle認証【CI/CDパイプラインを用いたApp Distributionへのapkアップロードに挑戦】 Part2

Posted at

概要

こちらの記事の続きです。

ボリュームがあるので、2回に分けさせていただきました。今回はその第2回です。

  • OAuthクライアントID作成、Supabaseの設定、apkファイルをAppDistributionへアップロード
  • apkファイルアップロードを手動ではなく、Github Actions経由に切り替え(今回はこちら)

パイプライン内で必要な処理

早速ワークフローのファイルの話をします。
githubActionsで利用するyamlファイルなので、.github/workflowsフォルダの下にyamlファイルを作成します。

以下内容をパイプラインに含める必要がありました。

  • Flutterの環境構築
  • プロジェクトで使用するパッケージのインストール
  • google-services.jsonの復元
  • release.keystoreの復元
  • key.propertiesの作成
  • .env(環境変数ファイル)の作成
  • build runnerの実行(環境変数の読み込み)
  • apkファイルのビルド
  • actions/upload-artifact@v4を用いたapkファイルのアップロード
  • アップロードしたファイルをApp Distributionにデプロイ

これらをyamlファイルで記述していきますが、
その前に、必要になるシークレットの登録から進めます。

Github Actionsのシークレット設定

シークレット情報の扱い
重要な情報も含まれているので、実際に試す際には、値の扱いには注意してください。

これまでシークレットを使う機会はありましたが、
ここまで数が多かったのは初めてだったかもしれません。

設定方法

対象リポジトリのSettingから設定に進みます。

image.png

Google認証関連

  • GOOGLE_CLIENT_ID:OAuth 2.0 クライアント ID、Androidの情報
  • GOOGLE_WEB_SERVER_CLIENT_ID:OAuth 2.0 クライアント IDのウェブ アプリケーションの情報
  • GOOGLE_SERVICES_JSON:Base64 エンコードしたgoogle-services.jsonの情報
  • KEYSTORE_PASSWORD:keytoolコマンドでkeystoreファイルを作成したときのパスワード
  • KEY_ALIAS:keytoolコマンドでkeystoreファイルを作成したときのエイリアス
  • KEY_PASSWORD:keytoolコマンドでkeystoreファイルを作成したときのパスワード
  • RELEASE_KEYSTORE:Base64 エンコードしたrelease.keystoreの情報

Supabase関連

  • SUPABASE_URL:SupabaseプロジェクトのProject URL
  • SUPABASE_ANON_KEY:SupabaseプロジェクトのProject API Keys(AnonKey)

App Distribution関連

  • CREDENTIAL_FILE_CONTENT:FirebaseサービスアカウントのcredentialのJSON文字列
  • FIREBASE_APP_ID:App Distributionの設定画面からまたは、google-services.jsonから取得できるID(以下画像の赤枠で囲った箇所)
    image.png

Base64デコード

コマンドは非常にシンプルですが、ちょうどGoogle Cloudで資料があったので、合わせて添付いたします。

base64 android/app/google-services.json

サービスアカウント作成

AppDistributionにapkファイルをデプロイするには、権限がなければできません。

アカウント名入力して、「作成して続行」をクリックします。
image.png

「ロールを選択」
image.png

フィルタに、roles/firebaseappdistro.adminと入力すると、「Firebase アプリ配布管理者」と選択肢が表示されるはずですので、これを選択します。
image.png

対象サービスアカウントを選択して、鍵をダウンロードしてください。

image.png

yamlファイル作成

Flutterのインストールや、ビルドといった部分については、割愛します。
こちら(github gist)からyamlファイル全体は確認いただけます。
(本記事最後にもリンクあり)

.gitignoreしていた3ファイルの復元

基本的にはシークレットに設定した値を取得して、ファイルに書き込みをします。key.properties以外はbase64のデコードが必要なので、その処理を挟んで復元しました。

  • google-services.jsonの復元
  • release.keystoreの復元
  • key.propertiesの作成
  # google-services.jsonの内容復元
  - name: Load Google Service file
    run: |
      echo "${{ secrets.GOOGLE_SERVICES_JSON }}" | base64 -d > android/app/google-services.json
    
  # キーストアファイルの作成
  - name: Create Keystore
    run: |
      echo "${{ secrets.RELEASE_KEYSTORE }}" | base64 -d > android/app/release.keystore
  # key.propertiesファイルの作成
  - name: Create key.properties
    run: |
      echo "storePassword=${{ secrets.KEYSTORE_PASSWORD }}" >> android/key.properties
      echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/key.properties
      echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/key.properties
      echo "storeFile=release.keystore" >> android/key.properties

アップロードしたファイルをApp Distributionにデプロイ

以下のActionが提供されているので、それを利用してアップロードしました。

- name: Deploy to Firebase App Distribution
  uses: wzieba/Firebase-Distribution-Github-Action@v1
  with:
    appId: ${{ secrets.FIREBASE_APP_ID }}
    serviceCredentialsFileContent: ${{ secrets.CREDENTIAL_FILE_CONTENT }}
    groups: testers
    file: build/app/outputs/flutter-apk/app-arm64-v8a-release.apk      

yamlファイルの中の処理については以上です。
続いて、androidフォルダの中で一部修正が必要なので、その話をしていきます。

androidフォルダの一部ファイルを修正

今回のGoogle認証以外の機能も導入しているので、色々変更箇所はありますが、直接関係する箇所のみ記載します。
プロジェクト作成時と比較して、追加した箇所がグリーンになっています。

android/build.gradle

  • Google認証を行うための記載
  • keystoreの情報読み込みのための設定
    • local.propertieskey.propertiesの読み込みに関する設定
    • signingConfig > releasekey.propertiesから読み込む情報を設定
    • buildTypes > releasesigningConfigをsigningConfigs.releaseに変更
plugins {
    id "com.android.application"
    id "kotlin-android"
    // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
+    id "com.google.gms.google-services"
    id "dev.flutter.flutter-gradle-plugin"
}

+ def localProperties = new Properties()
+ def localPropertiesFile = rootProject.file('local.properties')
+ if (localPropertiesFile.exists()) {
+     localPropertiesFile.withReader('UTF-8') { reader ->
+         localProperties.load(reader)
+     }
+ }
+ 
+ def keystoreProperties = new Properties()
+ def keystorePropertiesFile = rootProject.file('key.properties')
+ if (keystorePropertiesFile.exists()) {
+     keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
+ }
+ 
+ def flutterRoot = localProperties.getProperty('flutter.sdk')
+ if (flutterRoot == null) {
+     throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+ }

android {
    namespace = "com.enoconan.testingappv2"
    compileSdk = flutter.compileSdkVersion
    ndkVersion = flutter.ndkVersion

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_1_8
    }

+     signingConfigs {
+         release {
+             keyAlias keystoreProperties['keyAlias']
+             keyPassword keystoreProperties['keyPassword']
+             storeFile file(keystoreProperties['storeFile'])
+             storePassword keystoreProperties['storePassword']
+         }
+     }

    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId = "com.enoconan.testingappv2"
        // You can update the following values to match your application needs.
        // For more information, see: https://flutter.dev/to/review-gradle-config.
        minSdk = 24
        targetSdk = flutter.targetSdkVersion
        // versionCode = flutter.versionCode
        // versionName = flutter.versionName
    }

    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
+           signingConfig signingConfigs.release
        }
    }
}

flutter {
    source = "../.."
}

android/key.propeties

android/build.gradleで記載したファイルの中身です。パスワードとエイリアスはサンプルですので、各自で設定した値に書き換えてください。

storeFile=release.keystore
storePassword=XXX
keyAlias=XXX_alias
keyPassword=XXX

android/app/build.gradle

Google認証が行うために、数か所記載を追加しています。

buildscript {
    repositories {
+       google()
        mavenCentral()
    }

    dependencies {
+        classpath 'com.google.gms:google-services:4.4.2'  // Google Services の追加
    }
}

+ plugins {
+    id 'com.google.gms.google-services' version '4.4.2' apply false
+ }

allprojects {
    repositories {
+       google()
        mavenCentral()
    }
}

rootProject.buildDir = "../build"
subprojects {
    project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
    project.evaluationDependsOn(":app")
}

tasks.register("clean", Delete) {
    delete rootProject.buildDir
}

もうちょっと具体的になってほしいエラー文

初めてだったので当然ですが、認証に大きく関係する、google-services.jsonrelease.keystoreが適切に復元できるまでに、パイプラインを何度も実行しました。
(ローカルからアップロードしたapkファイルではgoogleログインできるのに、
CI/CDパイプラインを経由すると、googleログインができない!)

都度エラー文を確認しますが、中盤から表示され始めた以下エラーメッセージについて、申し訳ないが、パッとしない印象を持ってしまいました。また若干メッセージが変わるときもあり、困惑しました。

調べる中で、
フィンガープリントに設定が上手くいっていない、と言い換えて作業しました。

PlatformException(sign_in_failed, X2.d: 10: , null, null)
PlatformException(sign_in_failed, l3.b: 10: , null, null)

個人開発だからできることですが、
CI/CDパイプラインを通じてビルドされたapkファイルのSHA-1と、google-services.json
が想定通りか検証するコードも適宜追加しながら確認を行いました。

こういう情報を見つける上で、ClaudeやchatGPTは大変助かります。

- name: Check APK signature
  run: |
    unzip -l build/app/outputs/flutter-apk/app-arm64-v8a-release.apk | grep "META-INF"
    sudo apt-get install -y apksigner
    apksigner verify --print-certs build/app/outputs/flutter-apk/app-arm64-v8a-release.apk | grep "SHA-1"

- name: Detailed google-services.json verification
  run: |
    echo "1. Checking package name:"
    jq '.client[0].client_info.android_client_info.package_name' android/app/google-services.json
  
    echo "2. Checking all OAuth clients:"
    jq '.client[0].oauth_client[]' android/app/google-services.json
  
    echo "3. Checking SHA-1 certificates:"
    jq '.client[0].oauth_client[] | select(.client_type == 1) | .android_info.certificate_hash' android/app/google-services.json    

最終的なyamlファイル

冒頭、「パイプライン内で必要な処理」で記載した処理を全て盛り込んだyamlファイルについては、
こちら(github gist)で確認いただけます。

参考になる箇所がありましたら、ぜひご利用ください。

学びとなったこと・今後

FlutterでUIを作るのみであれば、これまでの経験で活かせる箇所が多かったです。
一方今回は、署名とCI/CDパイプラインがメインだったので、UI作りとは関係しない箇所もあり、そのあたりの学びが多かったです。

SHA-1による署名に関する内容を少し理解できたことが、一番大きな収穫だったかと思います。
CI/CDパイプライン経由でデプロイしたapkファイルを用いてGoogleログインができたときは、ガッツポーツが自然と出ました。

アプリをリリース可能な状態にするための技術・実現方法を理解するためにはそれなりに時間をかける必要があると感じました。
今回はApp Distributionへのデプロイでしたが、いずれはPlay Storeへのリリースもやってみたいと思っています。

ありがとうございました。

参考記事

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?