Crashlytics のトリアージ — オフライン時の購入情報取得クラッシュと『慌てない』判断
「ぽんっ!」(旧称:でいりんぐ)の Crashlytics を定期的にトリアージしています。クラッシュ一覧は「直すべきもの」と「慌てなくてよいもの」が混ざっているので、見分け方が大事でした。実際に直した1件と、直さないと判断した1件を残します。
背景(当時の状況)
2026-07-13、Crashlytics に溜まっていた直近 30 日の致命的クラッシュと ANR を一通りトリアージしました。一覧には「すぐ直すべきもの」と「環境要因で実ユーザーには起きないもの」が混ざっていて、まずその仕分けから始めたのがこの作業です。
直した: オフライン時の購入情報取得が fatal になる
購読状態を調べる処理で、課金 SDK の getCustomerInfo() を呼んでいました。ここがオフライン時に NetworkError を投げ、それが未捕捉のまま購読状態プロバイダから伝播し、最終的にアプリの onError で fatal として計上されていました。ネットワークが切れているだけでクラッシュ扱いになるのは過剰です。
対処: ネットワーク系だけ握って fail-closed
Future<SubscriptionStatus> getSubscriptionStatus() async {
try {
final info = await _getCustomerInfo();
return _mapToStatus(info);
} on PlatformException catch (e) {
// ネットワーク起因だけは握って「無料」に倒す(fail-closed)
final code = PurchasesErrorHelper.getErrorCode(e);
if (code == PurchasesErrorCode.networkError ||
code == PurchasesErrorCode.offlineConnectionError) {
return SubscriptionStatus.free;
}
rethrow; // それ以外は握りつぶさず投げる
}
}
- ネットワーク系のエラーだけ捕捉して、安全側(無料)に倒す(fail-closed)。オフラインで一時的にプレミアム機能が使えないのは許容範囲で、クラッシュより遥かにマシ。
- それ以外のエラーは握りつぶさず rethrow(本当の不具合を隠さない)。
getCustomerInfoを DI で注入できるようにして、この分岐を単体テスト可能にした。
直さなかった: minSdk のおかげで実ユーザーに起きないクラッシュ
一方で、NoSuchMethodError: getAttributionSource() 系のクラッシュが Firebase 初期化中に報告されていました。これは API 31 未満の端末でのみ起きる既知の事象です。
ぽんっ!は minSdk = 31 なので、Android 11(API 30) 以下には配信・インストールされません。ではなぜ報告されるかというと、Google Play の審査 / Pre-Launch Report のテスト端末プールでの起動によるものでした。Pre-Launch のクラッシュはリリースをブロックしません。
- 慌てて Firebase の BoM を pin しない。 投機的なうえ、全 Firebase 機能に影響し、端末側 GMS 由来のため効くかも不確実。
- Crashlytics の 「OS バージョン(API)」を確認し、
minSdk未満ならテスト端末由来と判断してクローズする。 - 実ユーザー(対象 API 以上)で再発する場合のみ本格対応を検討する。
まとめ
- Crashlytics のトリアージは、「直すべき」と「環境要因で実ユーザーに起きない」を切り分けるのが最初の仕事。API バージョンや発生端末を必ず見る。
- ネットワーク起因の失敗は fail-closed で安全側に倒す。ただしネットワーク系だけを握り、他は rethrow して本当の不具合を隠さない。
- 修正のために 依存を DI 注入しておくと、その場しのぎでなく回帰テストで固定できます。