はじめに
FlutterでFlavorによる環境分けを行いつつ、FirebaseのCrashlyticsを導入したのですが、エラーに遭遇し、無事解決できたので記事にします。
前提条件
Flavor導入
今回はFlavorを導入したバージョンの手順となります。
一部はFlavorを導入してなくても参考にはなると思います。
Flavorの導入は公式の記事の通りにやりつつ、一部、不明な部分はこの記事を参照して実行してください。
Firebaseを使用する環境
今回はFlavorでlocal,dev,stg,prdを用意し、stgとprdのみFirebaseを使用しています。
Firebase導入
参考記事は下記です。
一部捕捉しつつ、手順を書きます。
- 本番用とステージング用のFirebaseのプロジェクトを作成し、必要に応じてアカウントを追加します
-
npm install -g firebase-toolsでFirebase CLIをインストールします -
firebase loginで、Firebaseのプロジェクトに参加したGoogleアカウントでログインします -
fvm dart pub global activate flutterfire_cliでFlutterFire CLIをインストールする - 以下を実行し、ステージング環境のFirebase構成を行います
--project=firebase_project_stgは作成したFirebaseのプロジェクトのID
--ios-bundle-id=com.example.stgと--android-package-name=com.example.stgは、アプリIDとなります
flutterfire config \
--project=firebase_project_stg \
--out=lib/firebase_options_stg.dart \
--ios-bundle-id=com.example.stg \
--ios-out=ios/flavors/stg/GoogleService-Info.plist \
--android-package-name=com.example.stg \
--android-out=android/app/src/stg/google-services.json
-
You have to choose a configuration typeには、Build configurationを選択します -
Please choose one of the following build configurationsでDebug-stgを選択します - もう一度同じコマンドを実行し、
Please choose one of the following build configurationsでRelease-stgを選択します
二回することで、デバッグビルド時とリリースビルド時用にFirebaseの設定ファイルが更新されます。
- 以下を実行し、本番環境のFirebase構成を行います
flutterfire config \
--project=firebase_project_prd \
--out=lib/firebase_options_prd.dart \
--ios-bundle-id=com.example.prd \
--ios-out=ios/flavors/prd/GoogleService-Info.plist \
--android-package-name=com.example.prd \
--android-out=android/app/src/prd/google-services.json
-
Which platforms should your configuration supportでandroidとiosを選択する -
You have to choose a configuration typeには、Build configurationを選択する -
Please choose one of the following build configurationsでDebug-prdを選択する - もう一度同じコマンドを実行し、
Please choose one of the following build configurationsでRelease-prdを選択する
パッケージ追加
Crashlyticsに必要なパッケージを追加します。
flutter pub add firebase_core firebase_crashlytics
iOSで最低OSバージョンを変更
記事執筆時点では15以上にする必要がありました。
今後上がっていくと思いますがビルドエラーのメッセージでいくつにすればいいかわかるはずです。
Cocoapodsを使用している方はios/Podfileも更新が必要です
platform :ios, '15.0'
Androidで最低OSバージョンを変更
android/app/build.gradle.ktsを変更
plugins {
...
}
android {
defaultConfig {
...
// 記事執筆時点では23以上にする必要がありました。
// 今後は上がっていくと思いますがビルドエラーでどのバージョンにすればいいかわかります
minSdk = 23
...
}
AndroidでFirebaseを使わないFlavorの場合、使用しないようにビルド設定を変更
これはオプションですが、私の環境の場合、localとdevではFirebaseを使用しないので
android/app/build.gradle.ktsを変更します
plugins {
id("com.android.application")
id("kotlin-android")
// こんな感じで自動で追加されますが消します
// START: FlutterFire Configuration
// id("com.google.gms.google-services")
// id("com.google.firebase.crashlytics")
// END: FlutterFire Configuration
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
...省略...
// Firebaseプラグイン適用を条件分岐
val useFirebase = gradle.startParameter.taskNames.none { task ->
// task名に local または dev が含まれていたら Firebase を無効化
task.contains("local", ignoreCase = true) ||
task.contains("dev", ignoreCase = true)
}
// 必要な環境のみ追加
if (useFirebase) {
apply(plugin = "com.google.gms.google-services")
apply(plugin = "com.google.firebase.crashlytics")
}
android/settings.gradle.ktsの方は消さなくて問題ないです
iOSでFirebaseを使わないFlavorの場合の設定
XCodeでTargets - Runner - Build Phaseを開きます。
FlutterFire: "flutterfire upload-crashlytics-symbols"とFlutterFire: "flutterfire bundle-service-file"の先頭に下記を追加します。
Debug-localの部分は必要に応じて、修正してください。
Firebaseを使うビルド構成のみスクリプトを実行できるようにするため、Firebaseが不要なビルド後世の場合はスキップします。
# 本番とステージング以外ではスキップ
if [ "${CONFIGURATION}" == "Debug-local" ] || [ "${CONFIGURATION}" == "Profile-local" ] || [ "${CONFIGURATION}" == "Release-local" ] || [ "${CONFIGURATION}" == "Debug-dev" ] || [ "${CONFIGURATION}" == "Profile-dev" ] || [ "${CONFIGURATION}" == "Release-dev" ] || [ "${CONFIGURATION}" == "Profile-stg" ] || [ "${CONFIGURATION}" == "Profile-prd" ]; then
echo "Skipping upload crashlytics symobols for ${CONFIGURATION}"
exit 0
fi
エントリーファイルを修正する
main.dartを修正し、FirebaseOptionを受け取れるようにします。
// flutterfire configで作成したfirebase_optionsファイルをインポートする
import 'package:your_app/firebase_options_prd.dart';
void main() {
run(
firebaseOptions: DefaultFirebaseOptions.currentPlatform,
);
}
/// runメソッドとして切り出すことで、他のファイルから呼べるようにするう
void run({
FirebaseOptions? firebaseOptions,
}) {
WidgetsFlutterBinding.ensureInitialized();
if (firebaseOptions != null) {
await Firebase.initializeApp(options: firebaseOptions);
}
runApp(
...
);
}
他の環境でも実行できるようにします。
main_stg.dart
// flutterfire configで作成したステージング用のfirebase_optionsファイルをインポートする
import 'package:your_app/firebase_options_stg.dart';
import 'package:your_app/main.dart';
void main() {
// main.dartで定義したrunを呼び出してアプリを実行
run(
firebaseOptions: DefaultFirebaseOptions.currentPlatform,
);
}
main_dev.dart
// firebaseを使わない環境の場合はインポートしない
// import 'package:your_app/firebase_options_dev.dart';
import 'package:your_app/main.dart';
void main() {
// main.dartで定義したrunを呼び出してアプリを実行
run(
// 必要ないので削除
// firebaseOptions: DefaultFirebaseOptions.currentPlatform,
);
}
mainを環境ごとに分けた場合は、flutter run -t lib/main_stg.dart --flavor stgで実行できます。
例外をハンドリングする
void run({
FirebaseOptions? firebaseOptions,
}) {
WidgetsFlutterBinding.ensureInitialized();
if (firebaseOptions != null) {
await Firebase.initializeApp(options: firebaseOptions);
+ // Flutterのフレームワーク内でキャッチされなかった例外
+ FlutterError.onError = (errorDetails) {
+ FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails);
+ // または以下
+ // FirebaseCrashlytics.instance.recordFlutterError(errorDetails);
+ };
+ // 非同期でキャッチされなかった例外
+ // FlutterErrorでは非同期エラーをキャッチすることはできない
+ PlatformDispatcher.instance.onError = (error, stackTrace) {
+ FirebaseCrashlytics.instance.recordError(
+ error,
+ stackTrace,
+ );
+ };
+ // Flutter外部の例外
+ // runApp前や、ネイティブ、低レベルのDartランタイムエラーなど
+ Isolate.current.addErrorListener(
+ RawReceivePort((List<dynamic> errorAndStacktrace) async {
+ FirebaseCrashlytics.instance.recordError(
+ errorAndStacktrace.first,
+ errorAndStacktrace.last is StackTrace
+ ? errorAndStacktrace.last as StackTrace
+ : null,,
+ );
+ }).sendPort,
+ );
例外をハンドリングする
以下のコードでFlutterErrorのログ送信を確認できます。
TextButton(
onPressed: () async {
throw Error();
},
child: const Text('Throw Test Exception'),
)
実行
AndroidとiOSで実行し、上記の例外を発生させて、アプリを再起動した後、Crashlyticsのログが追加されている確認してください
クラッシュログを確認する
クラッシュログを確認するには、アプリをクラッシュさせる必要があります。
以下のコードでクラッシュが可能です。
ElevatedButton(
onPressed: FirebaseCrashlytics.instance.crash,
child: const Text('Crash'),
),
iOSで実行できなかったら
もしiOSで、PhaseScriptExecution FlutterFire:\ \"flutterfire\ upload-crashlytics-symbolsというエラーが出た場合は、Build PhaseのFlutterFire: "flutterfire upload-crashlytics-symbols"を下記のスクリプトで置き換えて再度実行してください。
#!/bin/bash
# 本番とステージング以外ではスキップ
if [ "${CONFIGURATION}" == "Debug-local" ] || [ "${CONFIGURATION}" == "Profile-local" ] || [ "${CONFIGURATION}" == "Release-local" ] || [ "${CONFIGURATION}" == "Debug-dev" ] || [ "${CONFIGURATION}" == "Profile-dev" ] || [ "${CONFIGURATION}" == "Release-dev" ] || [ "${CONFIGURATION}" == "Profile-stg" ] || [ "${CONFIGURATION}" == "Profile-prd" ]; then
echo "Skipping upload crashlytics symobols for ${CONFIGURATION}"
exit 0
fi
PATH="${PATH}:$FLUTTER_ROOT/bin:${PUB_CACHE}/bin:$HOME/.pub-cache/bin"
# Flavorに応じてGoogleService-Info.plistのパスを切り替え
if [[ "${CONFIGURATION}" == *"stg"* ]]; then
GSP_PATH="${PROJECT_DIR}/flavors/stg/GoogleService-Info.plist"
else
GSP_PATH="${PROJECT_DIR}/flavors/prd/GoogleService-Info.plist"
fi
echo GSP_PATH
if [ -z "$PODS_ROOT" ] || [ ! -d "$PODS_ROOT/FirebaseCrashlytics" ]; then
echo "Warning: PODS_ROOT not found or FirebaseCrashlytics pod missing. Trying SPM path."
DERIVED_DATA_PATH=$(echo "$BUILD_ROOT" | sed -E 's|(.*DerivedData/[^/]+).*|\1|')
PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT="${DERIVED_DATA_PATH}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run"
else
PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT="$PODS_ROOT/FirebaseCrashlytics/run"
fi
if [ ! -f "$PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT" ]; then
echo "error: Crashlytics native script not found at $PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT"
exit 1
fi
if [ ! -f "$GSP_PATH" ]; then
echo "error: GoogleService-Info.plist not found at $GSP_PATH"
exit 1
fi
DSYM_PATH="${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}"
if [ ! -d "$DSYM_PATH" ]; then
echo "error: dSYM file not found at $DSYM_PATH"
fi
echo "Using GoogleService-Info.plist from: $GSP_PATH"
echo "Attempting to run native Crashlytics script directly..."
"$PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT" -gsp "$GSP_PATH" -p ios "$DSYM_PATH"
if [ $? -ne 0 ]; then
echo "warning: Native Crashlytics script finished with non-zero exit code. Upload might have failed."
fi
echo "Native Crashlytics script execution initiated."
下記の記事を参考に、Flavorごとにパスを解決するように修正したものとなります。
Crashlyticsにログが表示されない
実行は上手くいったが、ログが表示されない場合は、下記を参照し、デバッグロギングを有効にしてください
iOSで実行はできるが、dSYM ファイルをアップロードしてくださいとCrashylyticsに表示される
Build Settingsで実行対象のビルド設定をDWARF wit dSYM Fileに変更してください。
以下の記事が参考になります。
私の環境では、Strip debug symbols during copyの設定は変えなくても大丈夫でした。

