RORK LABEN
PUBLISH — Rork Maxはコード署名やプロビジョニングを肩代わりし、2クリックでApp Storeへ申請できますSIMULATOR — ブラウザ内のストリーミングシミュレータで実機に近い操作感を試せ、QRでTestFlightなしに実機インストールできますMAX — Rork MaxはネイティブSwiftアプリを生成し、Claude Code・Claude Opus 4.6で駆動しますNATIVE — Rork MaxはAR/LiDAR・Metalの3D・ウィジェット・Live Activities・HealthKit・Core MLなどXcode相当の領域まで到達しますFUNDING — Rorkが$15Mを調達し、App Store起業家世代の後押しを掲げていますPRICE — 料金は無料から用意され、有料プランは月$25から、Rork Maxは月$200ですPUBLISH — Rork Maxはコード署名やプロビジョニングを肩代わりし、2クリックでApp Storeへ申請できますSIMULATOR — ブラウザ内のストリーミングシミュレータで実機に近い操作感を試せ、QRでTestFlightなしに実機インストールできますMAX — Rork MaxはネイティブSwiftアプリを生成し、Claude Code・Claude Opus 4.6で駆動しますNATIVE — Rork MaxはAR/LiDAR・Metalの3D・ウィジェット・Live Activities・HealthKit・Core MLなどXcode相当の領域まで到達しますFUNDING — Rorkが$15Mを調達し、App Store起業家世代の後押しを掲げていますPRICE — 料金は無料から用意され、有料プランは月$25から、Rork Maxは月$200です
記事一覧/開発ツール
開発ツール/2026-07-07中級

アイコンのバッジが「3」のまま消えない — Expoアプリのバッジ数をSource of Truthで作り直す

Expoアプリのアイコンバッジが実際の未読と食い違い、消えなくなる原因を突き止め、バッジ数を状態から再計算して同期するSource of Truth設計を、動くコードと実運用の落とし穴つきで整理します。

Expo131React Native196通知4バッジRork489

プレミアム記事

リリースから数週間後、レビュー欄に「アイコンの赤いバッジが3のまま、アプリを開いても消えない」という報告が並びました。私が個人開発で運営している習慣化アプリでの出来事です。中を開けば未読は0件なのに、ホーム画面のアイコンだけが「3」を主張し続ける。ユーザーからすれば、アプリが嘘をついているように見えます。

原因を追ってみると、コードのどこにも「バッジを3にする」とは書いていませんでした。バッジを増やしていたのは通知そのもので、それを減らす責任を誰も持っていなかったのです。以下では、その食い違いがなぜ起きるのかを分解し、バッジ数を状態から再計算して同期する設計へ作り直すまでを、動くコードとともに追っていきます。

バッジが「ズレる」本当の原因

Expo(React Native)アプリのバッジ数は、実は二つの主体が別々に書き込んでいます。

一つは通知システムです。ローカル通知やプッシュ通知に badge の値が含まれていると、OSが受信時にアイコンバッジを自動で書き換えます。もう一つはアプリ本体で、Notifications.setBadgeCountAsync() を呼んだときだけ書き換わります。

問題は、この二つが互いの存在を知らないことです。通知が届くたびにOSがバッジを1つ増やしても、ユーザーがアプリ内でその項目を読んだとき、アプリ側は「バッジを減らす」処理を明示的に呼ばない限り何もしません。結果として、通知が積み上げた数値だけが取り残されます。

さらにやっかいなのは、通知を配信したときの badge 値が「その通知が来た瞬間の未読数」で固定される点です。ユーザーが通知を無視している間に状態が変わっても、過去に予約した通知の badge は古い数値のまま発火します。バッジは「今の未読数」ではなく「予約した時点のスナップショット」を映してしまうのです。

Source of Truth を1つに決める

この問題は、バッジ数を「誰が書き込むか」で考えている限り解けません。書き込む主体が複数あることが原因だからです。

発想を変えて、バッジ数は「アプリの状態から一意に計算されるもの」と定義し直します。未読の通知、未完了のタスク、確認待ちのリマインダー——アプリが持つ状態のうち、バッジに反映すべきものを1本の関数にまとめ、その関数だけがバッジの真実を決めます。通知の badge フィールドには一切頼りません。

私はこの方針を、以前 AdMob の広告解放状態を扱ったときと同じ考え方で組みました。複数の判定材料があるものは、必ず1本の合成関数を経由させ、そこ以外では状態を組み立てない。バッジもまったく同じ構造で扱えます。

まず、バッジに寄与する状態を集めて数値を返す純粋関数を用意します。

// badge/computeBadgeCount.ts
// アプリの状態からバッジ数を「計算」する唯一の関数。
// ここ以外の場所でバッジ数を組み立ててはいけない。
 
import type { AppState } from "../store/types";
 
export function computeBadgeCount(state: AppState): number {
  // バッジに反映したいものだけをここで合算する。
  // 例: 未読のお知らせ + 期限切れの未完了リマインダー
  const unreadNotices = state.notices.filter((n) => !n.read).length;
  const dueReminders = state.reminders.filter(
    (r) => !r.completed && r.dueAt <= Date.now()
  ).length;
 
  // 合計が負やNaNにならないよう最後に正規化する
  const total = unreadNotices + dueReminders;
  return Number.isFinite(total) && total > 0 ? total : 0;
}

この関数はストアの状態だけを入力に取り、副作用を持ちません。テストも書きやすく、「バッジが何を意味するか」がコード1箇所に集約されます。

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

この記事の続きを読む

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

この記事で得られること
アイコンバッジが「3」のまま消えないユーザー報告に悩んでいた人が、その日のうちに再計算式の同期コードを手に入れられる
通知が設定するバッジとアプリが設定するバッジの二重管理をやめ、状態から一意に決まる1本の関数へ統合できる
フォアグラウンド復帰・通知受信・iOS/Androidの挙動差まで含めて、バッジが二度とズレない再同期設計を実装できる
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

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

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

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

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

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

関連記事

開発ツール2026-06-16
通知を「開かなくても片付く」ものにする — Rork アプリのインタラクティブ通知アクション設計
通知を長押しすると現れるボタンやテキスト入力。Rork で作った Expo アプリにこのインタラクティブ通知アクションを実装し、アプリを開かずに完了できる体験を設計します。バックグラウンド処理の落とし穴まで踏み込みます。
開発ツール2026-07-04
「続きを読む」を出すべきか、テキストの実測で決める — Rork(Expo)の折りたたみ表示設計
商品説明やレビュー本文を3行で畳み「続きを読む」を出す実装で、短い文にまでトグルが付いてしまう問題を解決します。onTextLayout で実際の行数を測り、はみ出す時だけトグルを出す設計を、iOS と Android の差、展開アニメーション、文字サイズ拡大への配慮まで動くコードでまとめます。
開発ツール2026-06-29
Rork のテキスト入力にキーボード上のツールバーを足す — iOS の InputAccessoryView と Android の自前バーを1つの部品に揃える
Rork が生成した React Native アプリのテキスト入力に、キーボードの上へ固定する「完了」やクイック挿入ボタンのツールバーを足す方法です。iOS の InputAccessoryView と、Android で自前に組むバーを、1つの再利用コンポーネントへ畳む実装を実コードで残します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →