開発Tips

Crashlytics のトリアージ — オフライン時の購入情報取得クラッシュと『慌てない』判断

「ぽんっ!」(旧称:でいりんぐ)の Crashlytics を定期的にトリアージしています。クラッシュ一覧は「直すべきもの」と「慌てなくてよいもの」が混ざっているので、見分け方が大事でした。実際に直した1件と、直さないと判断した1件を残します。

背景(当時の状況)

2026-07-13、Crashlytics に溜まっていた直近 30 日の致命的クラッシュと ANR を一通りトリアージしました。一覧には「すぐ直すべきもの」と「環境要因で実ユーザーには起きないもの」が混ざっていて、まずその仕分けから始めたのがこの作業です。

直した: オフライン時の購入情報取得が fatal になる

購読状態を調べる処理で、課金 SDK の getCustomerInfo() を呼んでいました。ここがオフライン時に NetworkError を投げ、それが未捕捉のまま購読状態プロバイダから伝播し、最終的にアプリの onErrorfatal として計上されていました。ネットワークが切れているだけでクラッシュ扱いになるのは過剰です。

対処: ネットワーク系だけ握って 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(本当の不具合を隠さない)。
  • getCustomerInfoDI で注入できるようにして、この分岐を単体テスト可能にした。

直さなかった: 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 注入しておくと、その場しのぎでなく回帰テストで固定できます。