RORK LABEN
TOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しましたTOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しました
記事一覧/開発ツール
開発ツール/2026-04-05初級

Rork × expo-notifications ローカル通知 実装ガイド — スケジュール・繰り返し・リマインダーの作り方

Rork × expo-notifications でローカル通知を実装する完全ガイド。スケジュール通知・繰り返しアラーム・リマインダーの作り方をコード例付きで丁寧に解説します。

expo-notifications7ローカル通知3スケジュール通知React Native209Rork515リマインダー3

ローカル通知とプッシュ通知、何が違うの?

アプリに通知機能を追加したいと思ったとき、まず理解しておきたいのが「ローカル通知」と「プッシュ通知」の違いです。

ローカル通知は、アプリ自身がデバイス上でスケジュールして送る通知です。サーバーが不要なため、インターネット接続がなくても機能します。習慣トラッカーの毎日リマインダー、タイマー完了通知、アラームアプリなど、「デバイス内で完結する通知」に最適です。

一方、プッシュ通知はサーバー(APNs / FCM)を経由して送る通知で、外部のイベント(新着メッセージ、セール情報など)をユーザーに届けるために使います。

Rork で作るアプリの多くは、まずローカル通知から実装を始めると開発がスムーズです。サーバー設定が不要で、expo-notifications というライブラリ一つで完結するからです。

  • 通知パーミッションの取得
  • 即時通知の送信
  • 特定の日時に送るスケジュール通知
  • 毎日繰り返す習慣リマインダー
  • 通知のキャンセルと一覧管理

expo-notifications のセットアップ

パッケージのインストール

Rork のプロジェクトターミナル(または AI チャット)で次を依頼します。

# Rork の AI に対して自然言語で依頼する例
# 「expo-notifications パッケージを追加して」
# または直接 Rork のパッケージ管理から追加
npx expo install expo-notifications expo-device

Rork は Expo ベースのため、expo install を使うことでバージョンの整合性が自動的に保たれます。

app.json の設定

ローカル通知を使うためには app.json(または app.config.js)に通知プラグインを追加します。

{
  "expo": {
    "plugins": [
      [
        "expo-notifications",
        {
          "icon": "./assets/notification-icon.png",
          "color": "#ffffff",
          "sounds": ["./assets/notification.wav"]
        }
      ]
    ]
  }
}

iconsounds はオプションです。アプリのブランドカラーに合わせた通知アイコンを用意するとユーザー体験が向上します。


通知パーミッションの取得

iOS では、通知を送るにはユーザーの許可が必要です。Android 13 以降も同様です。アプリ起動時や「通知を有効にする」ボタンを押したタイミングで許可を求めます。

import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import { Platform } from 'react-native';
 
// 通知ハンドラーの設定(フォアグラウンド表示のルールを決める)
Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,   // バナー表示する
    shouldPlaySound: true,   // サウンドを鳴らす
    shouldSetBadge: false,   // バッジは更新しない
  }),
});
 
export async function requestNotificationPermission(): Promise<boolean> {
  // シミュレーターでは通知が動作しないため実機チェック
  if (!Device.isDevice) {
    console.warn('実機でテストしてください');
    return false;
  }
 
  const { status: existingStatus } = await Notifications.getPermissionsAsync();
  let finalStatus = existingStatus;
 
  // まだ許可を求めていない場合はダイアログを表示
  if (existingStatus !== 'granted') {
    const { status } = await Notifications.requestPermissionsAsync();
    finalStatus = status;
  }
 
  if (finalStatus !== 'granted') {
    console.warn('通知の許可が得られませんでした');
    return false;
  }
 
  // Android の場合は通知チャンネルを設定
  if (Platform.OS === 'android') {
    await Notifications.setNotificationChannelAsync('default', {
      name: 'デフォルト',
      importance: Notifications.AndroidImportance.MAX,
      vibrationPattern: [0, 250, 250, 250],
      lightColor: '#FF231F7C',
    });
  }
 
  return true;
}

ポイント: requestPermissionsAsync() は一度しかダイアログを表示できません。ユーザーが「許可しない」を選んだ場合、次回からは getPermissionsAsync() で状態を確認し、設定アプリへ誘導する必要があります。


即時通知の送信

パーミッションが取れたら、まず最もシンプルな「即時通知」を実装しましょう。

import * as Notifications from 'expo-notifications';
 
export async function sendImmediateNotification(
  title: string,
  body: string,
  data?: Record<string, unknown>
): Promise<string> {
  const notificationId = await Notifications.scheduleNotificationAsync({
    content: {
      title,
      body,
      data: data ?? {},
      sound: true,
    },
    trigger: null, // null = 即時送信
  });
 
  // notificationId を保存しておくとあとでキャンセルできる
  console.log('通知ID:', notificationId);
  return notificationId;
}
 
// 使用例
await sendImmediateNotification(
  'タスク完了!',
  '「毎日ウォーキング」を達成しました 🎉',
  { taskId: 'walk-20260405' }
);
// 期待する出力: 通知ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

trigger: null にするだけで即座に通知が送られます。フォアグラウンド(アプリが前面)でも setNotificationHandler の設定があればバナーが表示されます。


スケジュール通知の実装

特定の日時に通知を送る

リマインダーやイベント通知など、「〇月〇日の〇時に通知したい」場合に使います。

import * as Notifications from 'expo-notifications';
 
export async function scheduleNotificationAt(
  title: string,
  body: string,
  date: Date
): Promise<string> {
  const notificationId = await Notifications.scheduleNotificationAsync({
    content: {
      title,
      body,
      sound: true,
    },
    trigger: {
      type: Notifications.SchedulableTriggerInputTypes.DATE,
      date, // JavaScript の Date オブジェクトをそのまま渡せる
    },
  });
 
  return notificationId;
}
 
// 使用例: 明日の午前9時に通知
const tomorrow9am = new Date();
tomorrow9am.setDate(tomorrow9am.getDate() + 1);
tomorrow9am.setHours(9, 0, 0, 0);
 
const id = await scheduleNotificationAt(
  '朝のリマインダー',
  '今日のタスクを確認しましょう!',
  tomorrow9am
);
// 期待する出力: 通知ID が返され、明日の9時に通知が届く

毎日繰り返す習慣リマインダー

習慣トラッカーやメディテーションアプリでよく使われる「毎日同じ時刻に通知」です。

import * as Notifications from 'expo-notifications';
 
export async function scheduleDailyReminder(
  title: string,
  body: string,
  hour: number,
  minute: number
): Promise<string> {
  const notificationId = await Notifications.scheduleNotificationAsync({
    content: {
      title,
      body,
      sound: true,
    },
    trigger: {
      type: Notifications.SchedulableTriggerInputTypes.DAILY,
      hour,    // 0〜23
      minute,  // 0〜59
    },
  });
 
  return notificationId;
}
 
// 使用例: 毎日20時に水分補給リマインダー
const reminderId = await scheduleDailyReminder(
  '💧 水分補給の時間です',
  '今日は十分な水を飲めていますか?',
  20, // 20時
  0   // 0分
);
 
console.log('繰り返し通知を設定しました:', reminderId);
// 期待する出力: 毎日20:00に通知が届き続ける

曜日を指定した週次リマインダー

「毎週月曜日にレビューする」など、週次のリマインダーも簡単に作れます。

export async function scheduleWeeklyReminder(
  title: string,
  body: string,
  weekday: 1 | 2 | 3 | 4 | 5 | 6 | 7, // 1=日曜, 2=月曜...7=土曜
  hour: number,
  minute: number
): Promise<string> {
  const notificationId = await Notifications.scheduleNotificationAsync({
    content: { title, body, sound: true },
    trigger: {
      type: Notifications.SchedulableTriggerInputTypes.WEEKLY,
      weekday,
      hour,
      minute,
    },
  });
  return notificationId;
}
 
// 毎週月曜日の朝9時に週間レビューリマインダー
await scheduleWeeklyReminder(
  '📋 週間レビューの時間です',
  '今週の振り返りをしましょう',
  2, // 月曜日
  9,
  0
);

通知の管理(キャンセル・一覧表示)

スケジュール済み通知の一覧を取得

import * as Notifications from 'expo-notifications';
 
export async function getScheduledNotifications() {
  const notifications = await Notifications.getAllScheduledNotificationsAsync();
 
  notifications.forEach(notification => {
    console.log({
      id: notification.identifier,
      title: notification.content.title,
      trigger: notification.trigger,
    });
  });
 
  return notifications;
}

特定の通知をキャンセル

export async function cancelNotification(notificationId: string): Promise<void> {
  await Notifications.cancelScheduledNotificationAsync(notificationId);
  console.log(`通知 ${notificationId} をキャンセルしました`);
}
 
// 全ての通知をキャンセル(ユーザーがアラームをオフにした場合など)
export async function cancelAllNotifications(): Promise<void> {
  await Notifications.cancelAllScheduledNotificationsAsync();
  console.log('すべての通知をキャンセルしました');
}

通知タップ時の処理

ユーザーが通知をタップしたときにアプリ内の特定画面へ遷移させる方法です。

import { useEffect, useRef } from 'react';
import * as Notifications from 'expo-notifications';
import { useRouter } from 'expo-router';
 
export function useNotificationHandler() {
  const router = useRouter();
  const responseListener = useRef<Notifications.EventSubscription>();
 
  useEffect(() => {
    // 通知をタップしたときのイベント
    responseListener.current = Notifications.addNotificationResponseReceivedListener(
      response => {
        const data = response.notification.request.content.data;
 
        // data に遷移先情報を埋め込んでおく
        if (data.screen) {
          router.push(data.screen as string);
        }
      }
    );
 
    return () => {
      if (responseListener.current) {
        responseListener.current.remove();
      }
    };
  }, [router]);
}

通知の data フィールドに { screen: '/tasks/123' } のような情報を入れておくことで、タップ時に適切な画面へディープリンクできます。

サーバーを使ったプッシュ通知のセグメント配信やオートメーションに興味が出てきたら、Rork プッシュ通知 高度なセグメント配信・自動化ガイド も参考にしてみてください。


よくあるエラーと対処法

エラー1: 「Simulator is not supported」

シミュレーターでは expo-notifications の一部機能(特にプッシュ通知)が動作しません。実機(iPhone / Android 端末)でテストしてください。Rork の場合、TestFlight 経由でインストールするか、Expo Go アプリを使うと手軽です。

エラー2: 通知が届かない(iOS)

iOS 18 以降、バックグラウンド処理の制限が強化されています。以下を確認してください。

  • 設定アプリ → 通知 → アプリ名 で「通知を許可」がオン
  • Notifications.getPermissionsAsync()status === 'granted' であること
  • setNotificationHandler が正しく設定されていること

エラー3: 繰り返し通知が重複して届く

通知のキャンセルをせずに再度スケジュールすると、同じ内容の通知が複数登録されます。ユーザーが「通知設定を変更した」タイミングで cancelAllScheduledNotificationsAsync() を呼んでから再スケジュールする習慣をつけましょう。

// 設定変更時のパターン
async function updateDailyReminder(hour: number, minute: number) {
  // まず全キャンセル
  await Notifications.cancelAllScheduledNotificationsAsync();
  // 新しい時刻で再スケジュール
  await scheduleDailyReminder('リマインダー', '時間です!', hour, minute);
}

ローカルデータにユーザーの通知設定を保存しておく方法については、Rork AsyncStorage / MMKV / SQLite ローカルストレージ完全ガイド が参考になります。


全体を振り返って

expo-notifications を使ったローカル通知の実装は、思ったよりシンプルです。要点をまとめます。

  • パーミッション取得requestPermissionsAsync() で確認・要求
  • 即時通知trigger: null でその場で送信
  • スケジュール通知DATE トリガーで特定日時に送信
  • 繰り返し通知DAILY / WEEKLY トリガーで定期送信
  • 管理cancelScheduledNotificationAsync() で個別キャンセル

習慣トラッカー、タスク管理、瞑想アプリ、水分補給リマインダーなど、ローカル通知はユーザーのエンゲージメントを高めるうえで非常に効果的な機能です。アプリのリテンション改善に取り組んでいる方は、さらに詳しい実装テクニックについて Rork アプリ パフォーマンス最適化完全ガイド もぜひ参照してみてください。

React Native × Expo の通知周りをさらに体系的に

シェア

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

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

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

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

開発ツール2026-06-25
海外に行くと毎朝の通知が9時に来ない — Expoの定時ローカル通知をタイムゾーンと夏時間に強くする設計
Rork(Expo)で作った定時リマインダーが、出張やサマータイムで現地の9時に届かなくなる問題。timeIntervalトリガーの落とし穴と、ローカル時刻基準で再スケジュールする設計を実装コード付きで整理します。
開発ツール2026-06-22
毎朝違う一言を出す日替わりリマインダーが、ある日から鳴らなくなる——iOSの保留64件という見えない上限
Rork(Expo)で作った日替わりリマインダーが、しばらく経つと無言で止まる原因はiOSの保留通知64件上限です。固定文言のカレンダートリガーと、日替わりコンテンツのローリング再スケジュールを動くコードで設計し、サマータイムや複数リマインダー併用でも狂わない通知を組みます。
開発ツール2026-07-17
許可を聞かずに通知を届ける — Rork アプリの provisional 認可と、Expo の定番コードが握りつぶすもの
iOS には許可ダイアログを出さずに通知を配り始める provisional 認可があります。ところが Expo 公式が載せている定番の登録コードをそのまま使うと、provisional で通った端末が「拒否」として扱われ、避けたかったはずのダイアログが出ます。granted が false になる仕組みから、5値で許可状態を扱うフック、静かな配信を前提にした通知設計、昇格を求める判断、計測の分離までを実装込みで整理しました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →