RORK LABJP
MAX — Rork Max bills itself as the first web Swift app builder, publishing to the App Store in two clicks with no Xcode requiredAPPLE — It generates native Swift apps for iPhone, iPad, Apple Watch, Apple TV, and Vision ProEXPO — The standard tier builds native iOS and Android apps on React Native (Expo) from a plain-English descriptionFUNDING — Rork raised $2.8M from a16z, strengthening its position in AI no-code mobile developmentPRICE — Free to start, with paid plans from $25/month — an accessible entry point for solo developersWWDC — WWDC 2026 pushes Apple Intelligence forward, raising the value of native features and widening AI integration options for no-code appsMAX — Rork Max bills itself as the first web Swift app builder, publishing to the App Store in two clicks with no Xcode requiredAPPLE — It generates native Swift apps for iPhone, iPad, Apple Watch, Apple TV, and Vision ProEXPO — The standard tier builds native iOS and Android apps on React Native (Expo) from a plain-English descriptionFUNDING — Rork raised $2.8M from a16z, strengthening its position in AI no-code mobile developmentPRICE — Free to start, with paid plans from $25/month — an accessible entry point for solo developersWWDC — WWDC 2026 pushes Apple Intelligence forward, raising the value of native features and widening AI integration options for no-code apps
Articles/Dev Tools
Dev Tools/2026-06-14Intermediate

Stop Burning Your One Push-Permission Shot on App Launch — Pre-Prompt Priming for Rork Apps

If your Rork (Expo) app fires the OS push-permission dialog at launch, every 'Don't Allow' tap closes that channel forever — iOS won't let you ask again. Here's how a self-built pre-permission screen lifts your opt-in rate, with the Expo code to do it.

Rork400Push Notifications6Expo72Retention10Solo Dev3

The first thing I quietly lost after shipping a Rork-built app was push permission. I had wired it so the OS dialog — "'App' would like to send you notifications" — appeared the instant the app opened. Faced with that on first launch, most users tapped "Don't Allow" without a second thought.

The nasty part: on iOS, once someone taps "Don't Allow," your app can never bring that dialog back. Even if the value of notifications becomes obvious later, the user has to dig into the Settings app and flip the switch themselves. Almost nobody does. So if you get that single first ask wrong, you lose the notification channel to that user permanently. Running wallpaper and relaxation apps for years, I learned that simply not leaving that "first ask" up to the OS moves the opt-in rate more than anything else. Here's the design.

Why "Right at Launch" Wastes the Most

The OS push dialog comes with two constraints you can't touch. You can't change its wording, and it's a binary choice — "Allow" or "Don't Allow" — where the second answer ends the conversation for good.

First launch is the moment when the user hasn't experienced anything in your app yet. Asking "may I send notifications?" before they've felt any value gives them nothing to decide with. When people hesitate, they pick the option that feels safer: "Don't Allow." That isn't users being cold — it's the natural response to a binary demand made without any context.

This is where pre-permission priming helps. Before the OS dialog, you show your own screen that explains, just once, why notifications are useful, and you only forward people to the OS dialog after they've thought "yeah, I might want that." Your own screen can appear as many times as you like, with whatever copy you choose. Show the OS dialog only to people who warmed up here, and you dramatically cut the instant-death "Don't Allow" taps.

The Core Idea: Demote the OS Dialog to a Final Confirmation

The trick is to flip the framing. Instead of treating the OS dialog as the first gate, position it as the last confirmation for someone who has already made up their mind.

The flow looks like this. First, your own priming screen makes the case. Anyone who taps "Later" there does not see the OS dialog (showing it would spend your one precious shot). Only people who tap "Turn on notifications" get the OS dialog — and since they're already on board by then, it sails through.

The point I most want to land here is this: splitting it into two steps isn't a numbers trick for inflating opt-in rates. The real value is that you give the user's freedom to decline a safe place to land — the "Later" button on your own screen. A decline at the OS dialog can never be retried; a "Later" on your screen can be re-asked when a better context comes along. Moving the decline to a recoverable place is the heart of the design.

Implementing It in Expo

Rork generates apps on Expo (React Native) under the hood. Push permission is handled by expo-notifications. The first rule: only call the permission request from the "Turn on notifications" button on your own screen.

The helper below checks the current permission state and shows the OS dialog only if we've never asked. For people who are already denied, it skips the dialog and signals that we need to route them to Settings instead.

import * as Notifications from 'expo-notifications';
import { Linking } from 'react-native';
 
type PrimeResult = 'granted' | 'denied' | 'needs-settings';
 
// Call this when "Turn on notifications" is tapped on your priming screen
export async function requestPushAfterPriming(): Promise<PrimeResult> {
  const current = await Notifications.getPermissionsAsync();
 
  // Already granted — do nothing
  if (current.status === 'granted') {
    return 'granted';
  }
 
  // On iOS, once denied, requestPermissionsAsync won't show a dialog —
  // it returns denied immediately. Routing to Settings is the only path.
  if (current.status === 'denied' && !current.canAskAgain) {
    return 'needs-settings';
  }
 
  // Only show the OS dialog while still undetermined
  const result = await Notifications.requestPermissionsAsync();
  return result.status === 'granted' ? 'granted' : 'denied';
}
 
// Open the Settings app (for the needs-settings case)
export function openNotificationSettings() {
  Linking.openSettings();
}

The crucial bit is the canAskAgain check. On iOS, once a user has declined, status becomes denied and canAskAgain becomes false, and calling requestPermissionsAsync() shows nothing. If you don't know this and just write "not granted, so let's ask again" loops, you fall into a silent failure: the user sees nothing while your code keeps getting denied on every call. I missed this at first and spent a while puzzled over why my opt-in rate wouldn't budge.

The priming screen that calls it can be as simple as a value statement plus two buttons, "Turn on notifications" and "Later":

import { View, Text, Pressable } from 'react-native';
import { requestPushAfterPriming } from './push';
 
export function PushPrimingScreen({ onDone }: { onDone: () => void }) {
  const handleAllow = async () => {
    const result = await requestPushAfterPriming();
    // Whether granted or denied, continue the normal flow from here
    onDone();
  };
 
  return (
    <View style={{ flex: 1, justifyContent: 'center', padding: 24 }}>
      <Text style={{ fontSize: 22, fontWeight: '700', marginBottom: 12 }}>
        We'll let you know when new wallpapers arrive
      </Text>
      <Text style={{ fontSize: 15, lineHeight: 22, color: '#555', marginBottom: 32 }}>
        A few times a week, just the new picks our editors love. You can change the frequency anytime in settings.
      </Text>
 
      <Pressable onPress={handleAllow} style={{ backgroundColor: '#111', padding: 16, borderRadius: 12, alignItems: 'center' }}>
        <Text style={{ color: '#fff', fontWeight: '600' }}>Turn on notifications</Text>
      </Pressable>
 
      <Pressable onPress={onDone} style={{ padding: 16, alignItems: 'center', marginTop: 8 }}>
        <Text style={{ color: '#888' }}>Later</Text>
      </Pressable>
    </View>
  );
}

Notice that tapping "Later" never calls requestPushAfterPriming. Because you don't fire the OS dialog here, you keep the chance to try again another day.

When to Ask — Timing Beats Copy

Before you polish the wording, fix the timing. In my experience, the ask lands best not at launch but right after the user has had one good experience in the app.

For a wallpaper app, that's the moment they've actually finished setting their first image as the device wallpaper. For a relaxation app, it's right after they've finished their first session and feel good. In other words, you aim for the moment when the value of notifications connects naturally to what just happened: "when a new piece arrives, you can have this experience again." That bridge forms on its own in the user's head.

The three moments to avoid: right at launch, right after a paywall, and right after an error. Launch has no context, post-paywall feels like "another demand," and post-error simply leaves a bad taste.

Recovering After a Decline

For people who tapped "Later" on your own screen, re-ask a fixed number of times, well spaced out. My rule of thumb is up to three times, with plenty of gap between each. Instead of repeating the same screen, anchor each ask to something that actually happened ("a sequel to the piece you saved was just published"); that makes it feel less pushy.

For people who reached denied at the OS level, place a single, unobtrusive "Turn on notifications" entry point somewhere in the app, and let it open the Settings app via Linking.openSettings(). The important thing is not to nag. Repeatedly pushing someone who already declined at the OS level only erodes the experience for little return — that's what years of operating these apps have taught me.

Done well, notifications become a quiet ally for retention; done wrong, they're a fragile channel where one bad first ask ends the relationship. Demote the OS dialog to a final confirmation, and keep the freedom to decline inside your own screen. That single bit of care should noticeably cut what you lose. I hope it helps anyone stuck at the same spot.

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 $10 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-06-13
You Only Get to Ask Once — Implementing a Notification Soft-Ask in Your Rork App to Lift Opt-In
On iOS, once a user denies the notification prompt you can never show it again. In a Rork (Expo) app, instead of firing the system prompt on launch, we add our own soft-ask screen and only request permission once the value has landed. Built with expo-notifications, covering Android 13 POST_NOTIFICATIONS, a recovery path after denial, and opt-in measurement.
Dev Tools2026-04-29
Why Your Rork Android App Shows a White Square Notification Icon (and How to Fix It)
Your Rork or Expo app's notification icon shows up as a white square or blob on Android. Here's the underlying Android spec, the correct transparent icon recipe, the right app.json fields, and the cache traps that make fixes appear to do nothing.
Dev Tools2026-06-14
Actually Delivering 'It Updates Without Opening' in Expo — A Realistic Background Task Design
Building 'content refreshes every morning' into a Rork-generated Expo app runs into iOS background execution being far less dutiful than you expect. Here is a minimal expo-background-task setup plus a design that doesn't break when the task never runs.
📚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 →