私は自分のアプリを最初にリリースしたとき、設定画面をほとんど手抜きで作ってしまいました。「通知のON/OFFと利用規約リンクさえあれば十分だろう」と考えていたのです。ところが公開から3日でサポート窓口に届いた質問の上位3つは、すべて設定画面に関するものでした。「通知を特定の時間だけオフにしたい」「有料プランの解約方法がわからない」「アカウントを削除したい」— どれも、きちんとした設定画面があればユーザーが自力で解決できることばかりです。
設定画面は地味ですが、アプリの使われ方と継続率を大きく左右します。ここではRork で作ったアプリに「実用レベル」の設定画面を実装するための構成の考え方と、通知・購読管理・法定表示まわりの具体的なコードパターンをまとめました。
設定画面に入れる項目は『法定』『利便性』『コンバージョン』の3分類で考える
設定画面は放っておくとどんどん項目が増え、最終的に誰も全体像を把握できなくなります。私が運用しているアプリでは、以下の3分類でセクションを組むと整理しやすいという経験則があります。
- 法定表示(必須): 利用規約、プライバシーポリシー、特定商取引法に基づく表記(国内向けに有料機能を提供する場合)、オープンソースライセンス表示
- 利便性(ユーザー体験): 通知の ON/OFF、ダークモード切替、言語切替、アカウント削除、ログアウト
- コンバージョン(収益・評価): 有料プランへのアップグレード、レビュー依頼、他のアプリの紹介、サポート連絡
セクションの並び順は、上から「アプリ設定」→「アカウント」→「情報(法定表示含む)」→「フィードバック」の順にすることが多いです。コンバージョン系の項目は最上部に置きたくなりますが、利便性項目のほうが使用頻度が高いため、ユーザーが迷わない配置を優先するのが誠実だと感じています。
Rork に設定画面を作ってもらうときのプロンプトの書き方
Rork は自由度が高いぶん、指示の粒度で出力の質が大きく変わります。設定画面を作ってもらうときに私がよく使うプロンプトの骨子を紹介します。
設定画面(SettingsScreen)を作ってください。以下の要件を満たしてください。
- Expo Router の /settings ルートに配置
- セクションは以下の4つ:
1. アプリ設定: 通知のON/OFF(Switch)、ダークモード切替(Switch)、言語切替
2. アカウント: ログアウト、アカウント削除(確認ダイアログあり)
3. 情報: 利用規約リンク、プライバシーポリシーリンク、バージョン表示、ライセンス
4. フィードバック: アプリを評価する、サポートに問い合わせる
- 見た目は iOS 標準の Settings アプリ風(グループ化されたリスト)
- 状態は既存の useSettingsStore(Zustand)に合わせる — 新しい状態管理は入れないこと
- accessibilityRole / accessibilityLabel を各項目に設定
ポイントは「どの既存コードに合わせるか」を明示することです。これを書かないと、Rork は勝手に新しい状態管理ライブラリを導入してしまったり、デザインシステムを無視したスタイルを生成してしまうことがあります。既存の色・フォント・ストアの名前をプロンプトに添えておくと、手戻りが減ります。
通知設定の実装 — OS の許可状態とアプリ内トグルを必ず同期する
設定画面で最もバグが出やすいのが通知のトグル処理です。多くの開発者が見落とすのが「OS レベルの許可状態」と「アプリ内の ON/OFF 設定」の同期で、ここを雑に実装するとユーザーは「トグルが ON なのに通知が来ない」という状態に陥ります。
// components/NotificationToggle.tsx
// 役割: OS の通知許可状態と同期しながら、アプリ内の通知トグルを管理する
import * as Notifications from "expo-notifications";
import { Switch, Alert, Linking } from "react-native";
import { useEffect, useState } from "react";
export function NotificationToggle() {
const [enabled, setEnabled] = useState(false);
// マウント時に OS の許可状態を確認し、UI と同期する
useEffect(() => {
Notifications.getPermissionsAsync().then((status) => {
setEnabled(status.granted);
});
}, []);
const handleToggle = async (value: boolean) => {
if (value) {
const result = await Notifications.requestPermissionsAsync();
if (result.granted) {
setEnabled(true);
return;
}
if (\!result.canAskAgain) {
// ユーザーが過去に「許可しない」を選んでいる場合、
// アプリからは二度と許可ダイアログを出せないため OS 設定へ誘導する
Alert.alert("通知が無効です", "OS の設定から通知を有効にしてください。", [
{ text: "キャンセル", style: "cancel" },
{ text: "設定を開く", onPress: () => Linking.openSettings() },
]);
}
} else {
// アプリ内だけ OFF にする(OS レベルの許可は保持)
setEnabled(false);
await Notifications.cancelAllScheduledNotificationsAsync();
}
};
return (
<Switch
value={enabled}
onValueChange={handleToggle}
accessibilityLabel="通知のON/OFF"
/>
);
}このコードで重要なのは canAskAgain の判定です。ユーザーが過去に通知を拒否していると、このフラグが false になり、アプリ側からは二度と OS の許可ダイアログを出せません。ここを省略して「ボタンを押しても何も起きない」状態にしてしまうのが、サポート問い合わせが最も増える落とし穴です。
なお、時間帯別の通知制御(「夜22時から朝7時はオフ」など)を実装する場合は、トグルの下に TimePicker を置いて expo-notifications の setNotificationChannelAsync(Android)や通知スケジュールのフィルタで対応します。
購読プラン管理 — 解約導線を隠さない実装にする
App Store と Google Play は、どちらも「アプリ内からサブスクリプションの管理画面に遷移できること」を事実上要求しています(App Store Review Guideline 3.1.2、Google Play Policy 10.6)。
// components/SubscriptionManageButton.tsx
// 役割: OS のサブスクリプション管理ページを開く
import { Linking, Platform, Pressable, Text } from "react-native";
export function SubscriptionManageButton() {
const openManagePage = () => {
if (Platform.OS === "ios") {
Linking.openURL("https://apps.apple.com/account/subscriptions");
} else {
Linking.openURL("https://play.google.com/store/account/subscriptions");
}
};
return (
<Pressable
onPress={openManagePage}
accessibilityRole="button"
accessibilityLabel="プランを管理・解約する"
>
<Text>プランを管理・解約する</Text>
</Pressable>
);
}「解約を隠したほうが解約率が下がる」という短期的な思考でボタンを深い階層に埋めると、審査でリジェクトされるだけでなく、返金申請や低評価レビューという形でコストが跳ね返ってきます。私は設定画面の「アカウント」セクションの目に入る位置に置くことをおすすめします。
サブスクリプション自体の実装については Rork で Stripe サブスクリプションを実装する完全ガイド に基本フローをまとめています。あわせて参照してください。
設定画面からのコンバージョン導線 — 押し付けないための距離感
設定画面は「すでにアプリを使ってくれている」ユーザーとの接点なので、ランディングページよりもコンバージョン率が高い傾向があります。私のアプリでは、設定画面からの有料プラン申込が全体の約30%を占めています。
次の2つの導線を、ユーザーの邪魔にならないようにセクション末尾やカード形式で配置しています。
- 有料プランのカード型導線: 無料ユーザーには「Pro 版にアップグレード」ボタンを目立つカードで表示し、すでに Pro 版のユーザーには非表示にする条件分岐を忘れないこと。課金済みの人に「アップグレード」を出し続けるのは最も印象が悪い
- レビュー依頼: iOS なら
expo-store-reviewのrequestReview()、Android は Google Play In-App Review API。ただし無制限に呼ぶと OS 側で無視されます。「アプリを10回以上起動」かつ「直近30日でレビュー未表示」など条件を絞るのが実用的です
レビュー依頼の適切なタイミング設計については、Rork アプリ内レビュー依頼の実装ガイド(iOS / Android 両対応) にまとめています。
アカウント削除 — Apple の必須要件を満たす実装
iOS の App Store Review Guideline 5.1.1(v) により、アカウント作成機能を持つアプリは アプリ内からアカウントを削除できる導線 を提供することが必須になっています(2022年6月以降)。これを省略すると審査でリジェクトされるため、設定画面に必ず実装する必要があります。
// components/DeleteAccountRow.tsx
// 役割: 確認ダイアログを挟んでアカウント削除フローを起動する
import { Alert, Pressable, Text } from "react-native";
import { useRouter } from "expo-router";
import { deleteAccount } from "../services/account";
export function DeleteAccountRow() {
const router = useRouter();
const confirmDelete = () => {
Alert.alert(
"アカウントを削除しますか?",
"このアカウントと関連するすべてのデータが完全に削除されます。この操作は取り消せません。",
[
{ text: "キャンセル", style: "cancel" },
{
text: "削除する",
style: "destructive",
onPress: async () => {
try {
await deleteAccount();
router.replace("/goodbye");
} catch {
Alert.alert(
"エラーが発生しました",
"しばらくしてから再度お試しいただくか、サポートまでご連絡ください。"
);
}
},
},
]
);
};
return (
<Pressable
onPress={confirmDelete}
accessibilityRole="button"
accessibilityLabel="アカウントを削除する"
>
<Text style={{ color: "#d9534f" }}>アカウントを削除する</Text>
</Pressable>
);
}実装で気を付けたい点がいくつかあります。確認ボタンには style: "destructive" を必ず指定して赤文字で表示すること(危険な操作の視覚的シグナルです)。サーバー側の削除処理が失敗したときにユーザーが迷子にならないよう、エラー時のフォールバック表示を用意すること。そしてアクティブなサブスクリプションがある場合は、削除前に解約処理を走らせること — これを忘れると、アカウントが消えた後も課金が続く最悪のクレームに発展します。
使われ方の分析 — 設定画面はユーザーからのフィードバック
最後にひとつお伝えしたいのは、設定画面は「機能を操作する場所」であると同時に「ユーザーがどう感じているかを教えてくれる場所」でもあるということです。どのトグルが切り替えられているか(中身の値は追わず、切り替え頻度だけで十分です)を観察すると、アプリのデフォルト設定が適切かどうかが見えてきます。
たとえば公開初日で8割のユーザーが通知を切っていれば、通知の頻度が多すぎるサインです。言語設定を変えるユーザーがほぼゼロなら、多言語対応の優先度は実は低いのかもしれません。設定画面は操作するためだけの UI ではなく、ユーザーからの一次情報が集まる場所として捉えるのが良いと感じています。
全体を振り返って
設定画面は目立たない画面ですが、ユーザーが「このアプリを使い続けるかどうか」を決める場所でもあります。リリース前にもう一つだけ手を加えるなら、今ご自身のアプリの設定画面を開いて「解約導線まで何タップかかるか」を数えてみてください。3タップ以上かかっているなら、そこを直すのがいちばん費用対効果が高い改善になるはずです。
コードそのものだけでなく、ユーザーに届くあらゆる「地味な部分」を丁寧に仕上げることが、長く使われるアプリを作る近道だと感じています。