ANTIGRAVITY LABEN
記事一覧/アプリ開発
アプリ開発/2026-06-21上級

戻るボタンで広告が出る/出ないが安定しない — 入れ子のifを独立ガードのリストに作り替えた記録

戻るボタン押下時のインタースティシャル表示判定が、入れ子のif文で優先度が暗黙化して壊れていました。各条件を理由つきの独立ガードに分解し、決定表からテストを生成した設計をまとめます。

antigravity385admob13android20app-dev32agent15

プレミアム記事

報酬型動画を見終えた直後にユーザーが戻るボタンを押すと、ごく稀にインタースティシャル広告が続けて出てしまう。個人開発で運用している壁紙アプリの一つで、この症状を Crashlytics ではなくユーザーレビューで知りました。再現率が低く、手元では一度も起きません。

原因はクラッシュではなく、戻るボタン押下時の「広告を出すか出さないか」の判定ロジックでした。頻度上限・クールダウン・課金済みか・広告がロード済みか、という4つの条件が入れ子の if 文で書かれていて、どの条件が優先されるのかがコードの構造に暗黙的にしか現れていなかったのです。本稿は、この判定を「理由を返す独立したガードの並び」に作り替え、決定表からテストを生成して再発を止めた記録です。

入れ子の if は、優先度を構造に隠してしまう

問題のコードは、おおよそこういう形でした。

fun onBackPressed() {
    if (!billing.isAdFree) {
        if (interstitial.isLoaded) {
            if (System.currentTimeMillis() - lastShownAt > COOLDOWN_MS) {
                if (backPressCount % SHOW_EVERY == 0) {
                    interstitial.show(activity)
                    lastShownAt = System.currentTimeMillis()
                }
            }
        }
    }
    super.onBackPressed()
}

一見すると正しく見えます。実際、ほとんどの場合は正しく動きます。問題は、報酬型動画を見た直後の状態でした。報酬視聴で lastShownAt を更新し忘れていた経路があり、クールダウン判定がすり抜けることがあったのです。さらに backPressCount は別の画面でもインクリメントされていて、戻る操作以外の数え上げが混入していました。

入れ子の構造では、こうした「条件同士の関係」が見えません。クールダウンと頻度上限のどちらを先に評価しているか、ad-free のチェックが本当に最優先になっているか、コードを目で追って初めて分かります。条件が4つで済んでいるうちはまだしも、同意状態(UMP)やアプリ内レビュー誘導との競合が増えると、入れ子は急速に追えなくなります。

判定を「理由を返すガードの並び」に分解する

作り替えの方針はシンプルです。「表示してよいか」を一つの大きな条件式で表すのをやめ、それぞれが独立にブロック理由を返す小さなガードの並びにしました。どれか一つでもブロックすれば表示しない。全ガードを通過したときだけ表示する、という構造です。

sealed interface AdDecision {
    data object Allow : AdDecision
    data class Block(val reason: String) : AdDecision
}
 
// 各ガードは「ブロックする理由」を返す。なければ null(=このガードは通過)。
private val guards: List<(AdContext) -> String?> = listOf(
    { ctx -> if (ctx.isAdFree) "ad_free" else null },
    { ctx -> if (!ctx.isLoaded) "not_loaded" else null },
    { ctx -> if (ctx.sinceLastShownMs < COOLDOWN_MS) "cooldown" else null },
    { ctx -> if (ctx.backPressCount % SHOW_EVERY != 0) "frequency" else null },
)
 
fun decideOnBackPress(ctx: AdContext): AdDecision {
    val reason = guards.firstNotNullOfOrNull { it(ctx) }
    return if (reason == null) AdDecision.Allow else AdDecision.Block(reason)
}

呼び出し側は、判定と実行をきれいに分けられます。

fun onBackPressed() {
    when (val d = decideOnBackPress(adContext())) {
        is AdDecision.Allow -> {
            interstitial.show(activity)
            lastShownAt = clock.now()
        }
        is AdDecision.Block -> log.d("interstitial blocked: ${d.reason}")
    }
    super.onBackPressed()
}

この形にして良かったのは、ブロック理由がログに残ることです。以前は「出なかった」という事実しか分かりませんでしたが、いまは cooldown なのか frequency なのかが必ず記録されます。低再現の不具合を、レビューではなくログで追えるようになりました。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
戻るボタンのインタースティシャル表示判定を、入れ子のifから「理由を返す独立ガードの並び」へ作り替える設計
ブロック理由を列挙した決定表を起点に、Antigravity へパラメータ化テストを生成させる具体手順
ad-free 状態・クールダウン・頻度上限・ロード可否を混同しないための責務分離の基準
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

アプリ開発2026-05-13
AntigravityでGoogle ML Kitを試したら、公式ドキュメントより速く動いた — でも1つだけ詰まった
Google ML KitをAntigravityでiOS/Androidアプリに統合する実践ガイド。テキスト認識・顔検出の実装コードとXcode 15バグ回避策、個人開発での使いどころをまとめています。
アプリ開発2026-04-25
Play Console のデータを Antigravity で読み解く — 個人開発アプリの評価と収益を継続的に改善する方法
Google Play Console のデータをただ眺めているだけになっていませんか? Antigravity を使えば、評価・クラッシュ・収益データを自動分析し、次の改善アクションまで導いてくれます。個人開発者向けの実践的な解説です。
アプリ開発2026-04-03
Antigravity × Capacitor ハイブリッドアプリ実装ガイド:WebアプリをiOS/Android両対応ネイティブアプリに変換するAI駆動ワークフロー
長年の個人アプリ運用で得た知見を踏まえ、Capacitor と Antigravity で既存 Web アプリを iOS/Android ネイティブ化する実装手順を解説。カメラ・プッシュ通知・ストレージのネイティブ API 統合から AdMob 最適化、App Store / Google Play 申請までを実測値とともに整理しました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →