ローカル通知とプッシュ通知、何が違うの?
アプリに通知機能を追加したいと思ったとき、まず理解しておきたいのが「ローカル通知」と「プッシュ通知」の違いです。
ローカル通知は、アプリ自身がデバイス上でスケジュールして送る通知です。サーバーが不要なため、インターネット接続がなくても機能します。習慣トラッカーの毎日リマインダー、タイマー完了通知、アラームアプリなど、「デバイス内で完結する通知」に最適です。
一方、プッシュ通知はサーバー(APNs / FCM)を経由して送る通知で、外部のイベント(新着メッセージ、セール情報など)をユーザーに届けるために使います。
Rork で作るアプリの多くは、まずローカル通知から実装を始めると開発がスムーズです。サーバー設定が不要で、expo-notifications というライブラリ一つで完結するからです。
- 通知パーミッションの取得
- 即時通知の送信
- 特定の日時に送るスケジュール通知
- 毎日繰り返す習慣リマインダー
- 通知のキャンセルと一覧管理
expo-notifications のセットアップ
パッケージのインストール
Rork のプロジェクトターミナル(または AI チャット)で次を依頼します。
# Rork の AI に対して自然言語で依頼する例
# 「expo-notifications パッケージを追加して」
# または直接 Rork のパッケージ管理から追加
npx expo install expo-notifications expo-deviceRork は 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"]
}
]
]
}
}icon と sounds はオプションです。アプリのブランドカラーに合わせた通知アイコンを用意するとユーザー体験が向上します。
通知パーミッションの取得
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-xxxxxxxxxxxxtrigger: 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 の通知周りをさらに体系的に