概要
こちらの記事の続きです。
ボリュームがあるので、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から設定に進みます。
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(以下画像の赤枠で囲った箇所)
Base64デコード
コマンドは非常にシンプルですが、ちょうどGoogle Cloudで資料があったので、合わせて添付いたします。
base64 android/app/google-services.json
サービスアカウント作成
AppDistributionにapkファイルをデプロイするには、権限がなければできません。
フィルタに、roles/firebaseappdistro.admin
と入力すると、「Firebase アプリ配布管理者」と選択肢が表示されるはずですので、これを選択します。
対象サービスアカウントを選択して、鍵をダウンロードしてください。
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.properties
とkey.properties
の読み込みに関する設定 -
signingConfig
>release
:key.properties
から読み込む情報を設定 -
buildTypes
>release
:signingConfig
を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.json
やrelease.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へのリリースもやってみたいと思っています。
ありがとうございました。
参考記事