RORK LABEN
NATIVE — Rork Max では AR / LiDAR スキャン、Metal を使った 3D、Dynamic Island、Siri Intents、HealthKit、NFC、App Clips、Core ML まで届きますIOS27 — iOS 27 Developer Beta 4 で Siri AI の対象が iPhone 15 Pro / 15 Pro Max・16 シリーズ・17 系へ広がり、応答が初期ベータより明確に速くなりましたDESIGN — iOS・iPadOS・macOS 27 向けの Apple 純正デザインキットが Figma と Sketch 向けに公開されました。新 OS の UI を組む段階で参照できますCANARY — Android 17(Cinnamon Bun)は従来の Developer Preview を廃し、継続更新される Canary ビルドへ移行しました。検証のタイミング設計が変わりますSPARK — Android 17 の Gemini Spark は、アプリを直接操作して配車の予約や注文といった複合的な作業を自動化しますSCALE — Rork は a16z から $2.8M を調達し、月間訪問は74万件規模まで伸びていますNATIVE — Rork Max では AR / LiDAR スキャン、Metal を使った 3D、Dynamic Island、Siri Intents、HealthKit、NFC、App Clips、Core ML まで届きますIOS27 — iOS 27 Developer Beta 4 で Siri AI の対象が iPhone 15 Pro / 15 Pro Max・16 シリーズ・17 系へ広がり、応答が初期ベータより明確に速くなりましたDESIGN — iOS・iPadOS・macOS 27 向けの Apple 純正デザインキットが Figma と Sketch 向けに公開されました。新 OS の UI を組む段階で参照できますCANARY — Android 17(Cinnamon Bun)は従来の Developer Preview を廃し、継続更新される Canary ビルドへ移行しました。検証のタイミング設計が変わりますSPARK — Android 17 の Gemini Spark は、アプリを直接操作して配車の予約や注文といった複合的な作業を自動化しますSCALE — Rork は a16z から $2.8M を調達し、月間訪問は74万件規模まで伸びています
記事一覧/開発ツール
開発ツール/2026-06-16中級

通知を「開かなくても片付く」ものにする — Rork アプリのインタラクティブ通知アクション設計

通知を長押しすると現れるボタンやテキスト入力。Rork で作った Expo アプリにこのインタラクティブ通知アクションを実装し、アプリを開かずに完了できる体験を設計します。バックグラウンド処理の落とし穴まで踏み込みます。

Rork525通知4Expo156Notification ActionsUX設計7React Native218

プレミアム記事

癒し系アプリで「今日のひとこと」を通知したとき、ふと気づいたことがあります。ユーザーはその通知をタップしてアプリを開き、メッセージを読み、また閉じる。たったそれだけのために、わざわざアプリ全体を起動させていたのです。読むだけなら、通知の中で完結させた方が親切ではないか——そう考えて取り組んだのが、インタラクティブ通知アクションでした。

インタラクティブ通知アクションとは、通知を長押し(または下にスワイプ)したときに現れる「ボタン」や「テキスト入力欄」のことです。リマインダー通知に「完了」「あとで」のボタンを付けたり、メッセージ通知に返信欄を出したりするあの仕組みです。アプリを開かずにその場で操作を終えられるので、ユーザーの手数が一気に減ります。

Rork が生成するのは通知を「送る・受け取る」までの基本コードが中心で、アクションの定義やバックグラウンド処理までは含まれないことが多いです。ここでは Expo(React Native)アプリにこの仕組みを足す設計を、App Store と Google Play で実際にアプリを運用してきた個人開発の経験を踏まえて残しておきます。

アクションは「カテゴリ」にまとめて定義する

通知アクションは個別のボタンをバラバラに定義するのではなく、「カテゴリ」という単位でまとめます。たとえば「リマインダー」というカテゴリに「完了」「1時間後に再通知」の2ボタンを束ね、通知を送るときにそのカテゴリを指定する、という構造です。

Expo では expo-notificationssetNotificationCategoryAsync で定義します。

// notificationCategories.ts — アプリ起動時に一度だけ登録する
import * as Notifications from "expo-notifications";
 
export async function registerCategories() {
  await Notifications.setNotificationCategoryAsync("reminder", [
    {
      identifier: "complete",
      buttonTitle: "完了",
      options: { opensAppToForeground: false }, // アプリを開かず裏で処理
    },
    {
      identifier: "snooze",
      buttonTitle: "1時間後に再通知",
      options: { opensAppToForeground: false },
    },
  ]);
 
  await Notifications.setNotificationCategoryAsync("daily_word", [
    {
      identifier: "reply",
      buttonTitle: "感想を書く",
      textInput: {
        submitButtonTitle: "送信",
        placeholder: "今の気持ちを一言…",
      },
      options: { opensAppToForeground: true }, // 入力後にアプリで保存
    },
  ]);
}

opensAppToForeground の値が、この設計の肝になります。false にするとアプリを前面に出さずに処理が走り、true にするとアクション後にアプリが開きます。「完了」ボタンのように一瞬で終わる処理は false、テキスト入力のように後で画面を見せたい処理は true、というのが基本的な使い分けです。

通知を送る側では、対応するカテゴリ ID を categoryIdentifier に指定します。

await Notifications.scheduleNotificationAsync({
  content: {
    title: "水やりの時間です",
    body: "観葉植物に水をあげましょう",
    categoryIdentifier: "reminder", // 上で定義したカテゴリ
  },
  trigger: { hour: 9, minute: 0, repeats: true },
});

アクションを受け取って処理を分岐する

ユーザーがボタンを押した結果は、addNotificationResponseReceivedListener で受け取ります。どのアクションが押されたかは actionIdentifier、テキスト入力の中身は userText に入ります。

// notificationHandler.ts
import * as Notifications from "expo-notifications";
 
export function listenForActions() {
  return Notifications.addNotificationResponseReceivedListener(async (res) => {
    const action = res.actionIdentifier;
    const data = res.notification.request.content.data;
 
    if (action === "complete") {
      await markReminderDone(data.reminderId);
    } else if (action === "snooze") {
      await Notifications.scheduleNotificationAsync({
        content: { title: "再通知", body: data.body, categoryIdentifier: "reminder" },
        trigger: { seconds: 3600 },
      });
    } else if (action === "reply") {
      // テキスト入力の中身は userText に入る
      await saveReflection(res.userText ?? "");
    }
  });
}

ここで注意したいのは、actionIdentifier には OS が予約した特別な値も来る点です。通知本体を普通にタップした場合は Notifications.DEFAULT_ACTION_IDENTIFIER が来ます。自分で定義したアクションだけを処理し、デフォルトタップは通常の画面遷移に回す、という分岐を忘れると、本体タップが無反応になります。私はここを抜かして「通知を普通にタップしても何も起きない」という不具合を一度出してしまいました。

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

この記事の続きを読む

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

この記事で得られること
ボタン型・テキスト入力型アクションをカテゴリ単位で定義する実装
アプリを起動せず裏側で完了させる際の OS 実行時間制限への対処
通知許可率(オプトイン率)を下げないアクション設計の判断基準と実測の手応え
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

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

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

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

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

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

関連記事

開発ツール2026-07-07
アイコンのバッジが「3」のまま消えない — Expoアプリのバッジ数をSource of Truthで作り直す
Expoアプリのアイコンバッジが実際の未読と食い違い、消えなくなる原因を突き止め、バッジ数を状態から再計算して同期するSource of Truth設計を、動くコードと実運用の落とし穴つきで整理します。
開発ツール2026-06-22
Rork(Expo)のトーストは、重なっても読み上げられても崩れない設計にする
Rork が生成した React Native アプリにトースト通知を足すとき、単純な実装は『同時に2つ出ると重なる』『スクリーンリーダーに無視される』『ノッチやホームインジケータに隠れる』の3点で破綻します。ルート1か所に置くキュー設計、本体の再レンダーから切り離すアニメーション、AccessibilityInfo による読み上げ、セーフエリア対応までを動くコードで示します。
開発ツール2026-07-30
Renovate が触ってよい依存と、触ってはいけない依存 — Expo SDK 57 の123パッケージを機械的に切り分ける
Rork 生成アプリに依存の自動更新を入れると、Expo が版を管理している123パッケージまで巻き込まれます。2026年7月30日時点の実測では、うち6件が npm の最新版へメジャー更新される差でした。除外リストを手で書かず SDK から機械生成する手順をまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →