RORK LABJP
PLAY — Google Play's target API level 36 requirement took effect yesterday, August 31. From today, new apps and updates must target Android 16VISIBILITY — Apps still on API 35 stay listed but disappear for users on newer Android versions. No error is raised; new installs simply fade, which makes the change easy to missEXTENSION — If you missed the deadline, an extension through November 1, 2026 can be requested in Play Console — best filed alongside a concrete migration planAPPLE — On the Apple side, the event lands September 9 and iOS 27 is reported to ship September 14. Testing generated apps on iOS 27 hardware before release week is time well spentEXPO — Expo released expo-paste-input on August 28, a native module that brings image, GIF, and sticker paste to React Native TextInputEAS — EAS Observe reached general availability on August 20, putting crash and performance monitoring on the same EAS platform as builds and updatesPLAY — Google Play's target API level 36 requirement took effect yesterday, August 31. From today, new apps and updates must target Android 16VISIBILITY — Apps still on API 35 stay listed but disappear for users on newer Android versions. No error is raised; new installs simply fade, which makes the change easy to missEXTENSION — If you missed the deadline, an extension through November 1, 2026 can be requested in Play Console — best filed alongside a concrete migration planAPPLE — On the Apple side, the event lands September 9 and iOS 27 is reported to ship September 14. Testing generated apps on iOS 27 hardware before release week is time well spentEXPO — Expo released expo-paste-input on August 28, a native module that brings image, GIF, and sticker paste to React Native TextInputEAS — EAS Observe reached general availability on August 20, putting crash and performance monitoring on the same EAS platform as builds and updates
Articles/Dev Tools
Dev Tools/2026-04-21Intermediate

Building a Settings Screen That Actually Works in Your Rork App — Notifications, Subscription Management, and Legal Requirements

A practical guide to building production-grade settings screens for your Rork app, covering notifications, subscription management, legal disclosures, and support flows — with real code examples.

Rork548Settings ScreenUX5App Development33React Native234Expo194Subscription23

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's requestReview(), 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.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Dev Tools2026-04-02
Adding Native Share Functionality to Your Rork App — A Complete Share Sheet Guide
Learn how to implement native Share Sheet functionality in your Rork app. From sharing text and URLs to images and deep links, this beginner-friendly guide walks you through real code examples for iOS and Android.
Dev Tools2026-08-22
Every bulk replace exited zero. The damage was in the lines I did not delete
Run a bulk replace over generated code and the breakage lands on the neighbouring lines, not the matched ones. Here is what broke in a live project, and a dependency-free guard that checks the invariants a replace must preserve.
Dev Tools2026-07-30
What Renovate may bump in an Expo app, and what it must never touch
Turning on automated dependency updates in a Rork-generated app also hands Renovate the 123 packages Expo SDK 57 pins. Measured on 2026-07-30, six of them sit a full major version behind npm latest. Here is how to generate the ignore list from the SDK instead of maintaining it by hand.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →