When I shipped my first app, I cut corners on the settings screen. "Notification toggle and a link to the terms of service should be enough," I thought. Three days after launch, the top three support questions I received were all about that screen: "How do I silence notifications during specific hours?", "How do I cancel my subscription?", and "How do I delete my account?" Every one of those could have been self-served with a decent settings screen.
Settings screens are rarely glamorous, but they shape how your app feels to use and whether people stick around. This guide walks through a structure I rely on, and concrete implementation patterns for notifications, subscription management, and the legal plumbing you cannot skip.
Organize Settings Into Three Layers: Legal, Usability, Conversion
If you leave the settings screen to grow organically, it ends up as a bag of toggles nobody can mentally map. In apps I maintain, the cleanest structure I have found groups items into three buckets.
- Legal (required): terms of service, privacy policy, open-source license credits, and — for Japan-specific releases of paid apps — the Tokushoho disclosure
- Usability (experience): notifications on/off, dark mode, language, sign out, account deletion
- Conversion (revenue / feedback): upgrade to paid, rate the app, share with friends, contact support
I typically lay them out top-to-bottom as: App Preferences → Account → Info (with legal) → Feedback. It is tempting to put the "upgrade" button at the very top, but users open settings to change something practical more often than to pay, and honoring that is how you build trust.
Prompting Rork to Generate the Settings Screen
Rork gives you a lot of freedom, which means the quality of the output depends heavily on how specific your prompt is. Here is the skeleton I reuse when asking Rork to build a settings screen.
Create a SettingsScreen with the following requirements:
- Placed at /settings on Expo Router
- Four sections:
1. App Preferences: notifications (Switch), dark mode (Switch), language picker
2. Account: sign out, delete account (with confirmation dialog)
3. Info: terms, privacy policy, version, open-source licenses
4. Feedback: rate the app, contact support
- Visual style: grouped list in the style of iOS Settings
- State lives in the existing useSettingsStore (Zustand) — do not introduce another state library
- accessibilityRole and accessibilityLabel on every interactive element
The critical line is "do not introduce another state library." Without it, Rork will happily pull in Redux, Jotai, or Recoil depending on what it has seen recently, and you end up rewriting half the app. Naming your existing store, colors, and typography in the prompt saves hours of rework.
Notifications: Always Sync the OS Permission With the App Toggle
The most common bug in a settings screen is a desynced notification toggle. Many developers treat the switch as a simple boolean, forgetting that iOS and Android manage notification permission at the OS level independently. The result: the switch is on, but no notifications arrive, and the support tickets start flowing.
// components/NotificationToggle.tsx
// Keeps the app-side toggle in sync with OS permission state.
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);
// On mount, read the OS permission and reflect it in the 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) {
// The user previously denied permission and we can no longer
// show the system dialog. Send them to the OS settings.
Alert.alert(
"Notifications are disabled",
"Enable notifications from your system settings.",
[
{ text: "Cancel", style: "cancel" },
{ text: "Open Settings", onPress: () => Linking.openSettings() },
]
);
}
} else {
// Turn off inside the app without revoking the OS permission.
setEnabled(false);
await Notifications.cancelAllScheduledNotificationsAsync();
}
};
return (
<Switch
value={enabled}
onValueChange={handleToggle}
accessibilityLabel="Toggle notifications"
/>
);
}The canAskAgain branch is what most people forget. Once a user has denied permission, you cannot prompt again from the app — the OS silently ignores requestPermissionsAsync. If your UI does not route them to system settings in that case, flipping the switch appears to do nothing, and this becomes your #1 support complaint.
For quiet-hours behavior, add a TimePicker below the switch and filter scheduled notifications on the client, or use Android notification channels via setNotificationChannelAsync.
Subscription Management: Do Not Hide the Cancel Path
Both App Store Review Guideline 3.1.2 and Google Play Policy 10.6 effectively require that users can reach the subscription management page from inside your app. You are not required to process cancellation yourself — you just need to make the platform's own page one tap away.
// components/SubscriptionManageButton.tsx
// Opens the platform's subscription management page.
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="Manage or cancel subscription"
>
<Text>Manage or cancel subscription</Text>
</Pressable>
);
}Burying this button under nested menus to reduce churn is a short-term trick that rebounds as refund requests, one-star reviews, and rejected updates. I always place it in the Account section where it is easy to find on first glance.
For the subscription flow itself, the Rork + Stripe subscription complete guide covers the server-side and Checkout pieces.
Driving Conversion Without Being Pushy
The settings screen is a touchpoint with people who are already using your app, so conversion rates here tend to be higher than on your landing page. In my apps, roughly 30% of paid signups originate from the settings screen.
Two patterns work well when placed at the end of a section or in a dedicated card, not at the top:
- Upgrade card for free users — show a clean "Upgrade to Pro" card for free-tier users, and hide it entirely for paying users. Continuing to show the upgrade prompt to subscribers is the fastest way to trigger a cancellation
- Review prompt — on iOS use
expo-store-review'srequestReview(), and on Android use the Google Play In-App Review API. Note that both platforms quietly rate-limit these calls, so gate them behind something like "app launched at least 10 times" and "no review prompt shown in the last 30 days"
For timing the review prompt well, see the in-app review prompt design guide for iOS and Android.
Account Deletion: An Apple Requirement You Cannot Ignore
Since iOS apps that offer account creation have been required to offer in-app account deletion (App Store Review Guideline 5.1.1), this has shifted from "nice to have" to a hard gate for approval. Many Rork users I have talked to do not realize this until rejection, and end up scrambling to ship it in a week.
// components/DeleteAccountRow.tsx
// Triggers the account deletion flow with a safety confirmation.
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(
"Delete account?",
"This will permanently remove your account and all associated data. This cannot be undone.",
[
{ text: "Cancel", style: "cancel" },
{
text: "Delete",
style: "destructive",
onPress: async () => {
try {
await deleteAccount();
router.replace("/goodbye");
} catch {
Alert.alert(
"Something went wrong",
"Please try again in a moment or contact support."
);
}
},
},
]
);
};
return (
<Pressable
onPress={confirmDelete}
accessibilityRole="button"
accessibilityLabel="Delete account"
>
<Text style={{ color: "#d9534f" }}>Delete account</Text>
</Pressable>
);
}A few things are easy to get wrong. Use style: "destructive" on the confirmation action — the red text is not decoration, it is how users recognize a dangerous operation. Handle the server error path gracefully: if deleteAccount fails, the user is left in a confusing state unless you surface the failure. And remember to also revoke any active subscriptions before deletion, otherwise the user will continue to be billed.
A Note on Usage Analytics
One more pattern worth mentioning: the settings screen is an excellent place to gauge how well your app is actually configured for each user. Tracking which toggles people flip — without tracking the underlying data — gives you a sense of which defaults are wrong. If 80% of users turn off notifications within a day, that is a signal your notification strategy is too noisy, not that users dislike notifications in general. If almost nobody ever changes the language from the default, you can save yourself the effort of maintaining three locale files. Treat the settings screen as feedback, not just as controls.
Closing Thought
Settings screens are easy to overlook, but they are often where users decide whether to keep an app on their phone. If you do one thing after reading this, open your own app's settings and count how many taps it takes to find the cancel flow. If the answer is three or more, fixing that is probably the highest-leverage UX improvement you can ship this week.