skip to content

In a React Native habit-tracker app, when should you trigger the notification permission prompt for reminders, and why not at first launch?

level: middleimportance: must knowfreq 55%

answer

  1. the system prompt is a scarce shot
  2. ask when the value is visible
  3. first reminder time picked
  4. own explainer before the system dialog
  5. check before you request

basics

~20 s

Trigger it when the user asks for something that needs it, such as setting their first habit reminder, after a short in-app explanation. A cold prompt at launch gets refused, and iOS shows its alert only once.

solid answer

~40 s

Ask **in context**: in a habit tracker the natural moment is when the user switches on a reminder or picks a reminder time for their first habit, because the benefit is obvious right then. Show your own explainer first ("We'll nudge you at 8:00 so the streak survives"), and only call `Notifications.requestPermissionsAsync()` if they agree. The reason is that the system prompt is scarce: iOS shows it once, and Android 13+ stops showing `POST_NOTIFICATIONS` after repeated denials, after which only Settings can fix it. A decline of your own screen costs nothing, and you can offer it again later. Always call `getPermissionsAsync()` first so an already-granted user never sees the explainer, and a permanently denied one is routed to Settings instead.

code

tsx · 25 lines
tsx
import * as Notifications from 'expo-notifications';
import { Alert } from 'react-native';

type Outcome = 'on' | 'declined' | 'needs-settings';

export async function enableHabitReminder(habitName: string): Promise<Outcome> {
  const current = await Notifications.getPermissionsAsync();
  if (current.granted) return 'on';
  if (!current.canAskAgain) return 'needs-settings';

  const agreed = await new Promise<boolean>((resolve) =>
    Alert.alert(
      'Daily reminder',
      `We will nudge you about "${habitName}" at the time you chose.`,
      [
        { text: 'Not now', style: 'cancel', onPress: () => resolve(false) },
        { text: 'Remind me', onPress: () => resolve(true) },
      ],
    ),
  );
  if (!agreed) return 'declined';

  const answer = await Notifications.requestPermissionsAsync();
  return answer.granted ? 'on' : 'declined';
}

go deeper

for a junior

Remember the rule: ask when the user turns on something that needs notifications, never as the first thing the app does. Know that iOS shows its alert only once.

for a middle

Explain the two-step ask: your own explainer, then requestPermissionsAsync only on a yes, with getPermissionsAsync deciding whether to show the explainer, skip it, or route to Settings.

for a senior

Show that you treat the prompt as a one-shot resource: pick the in-context trigger, design the degraded no-notification path, and add a cooldown so the soft ask never becomes nagging.

for a principal

Discuss opt-in as a product metric: where in onboarding the ask sits, how provisional delivery or a delayed ask trades reach against consent quality, and who owns that experiment.

## Why timing matters more than the code The call that shows the notification prompt is one line. What makes it an interview topic is that the **system prompt is a scarce resource**: - On **iOS** the authorization alert appears once. After the user answers, every later request resolves silently with the stored decision. - On **Android 13+**, `POST_NOTIFICATIONS` is a runtime permission, and after the user refuses it more than once the system stops showing the dialog. - In both cases, once the system has stopped asking, the only way back is the user opening the **Settings** app, which few users do. So an app that fires the prompt on first launch, before the user knows what the app is, spends its best chance at the worst moment. ## The in-context rule Ask when the user is doing something that obviously needs notifications. In a **habit tracker**, candidates are: 1. The user creates their first habit and turns on "Remind me". 2. The user picks a reminder time in a time picker. 3. The user completes a streak screen that offers "Keep me on track" reminders. At these moments the permission has a visible purpose, the user has already invested in the app, and a refusal tells you something real about this feature rather than about a stranger's first impression. ## The two-step ask Most apps put their own screen in front of the system prompt, often called a **pre-permission** or **soft ask**: | Step | Who draws it | What a "no" costs | |---|---|---| | Explainer ("We'll remind you at 8:00") | Your React Native UI | Nothing: you can offer it again later | | System prompt | iOS or Android | Possibly the last chance to ask | Only a "yes" on your screen leads to `Notifications.requestPermissionsAsync()`. A "not now" leaves the system prompt unused, so the habit can still work with in-app reminders and the offer can return after the user has used the app for a few days. ## Check before you request Always read the current state first with `Notifications.getPermissionsAsync()`: - `granted` is true: skip the explainer entirely and save the reminder. - `canAskAgain` is true: show the explainer, then request. - `canAskAgain` is false: a request would show nothing, so switch to a "turn on in Settings" message instead of the explainer. This avoids showing an explainer to someone who already said yes, and avoids promising a dialog the system will not draw. ## What a good explainer says The explainer is ordinary React Native UI, so it can be specific in a way the system prompt cannot. Useful rules: - Name the **concrete benefit** in the user's terms: "a nudge at 8:00 so your reading streak survives", not "enable notifications". - Offer a clear **secondary choice** ("Not now") that does not trigger anything. - Mention what will still work without notifications, so declining does not feel like breaking the app. - Keep it to one screen or one alert; the system prompt follows immediately on a yes, and the two should read as one step. Because the system prompt's text is controlled by the operating system, the explainer is the only place the app can make its case. ## Platform details that affect the moment - On Android 13+, expo-notifications' documentation says the prompt does not appear until at least one **notification channel** exists, so create the reminder channel before the request. - On iOS, **provisional** authorization can deliver quiet notifications with no prompt at all; some apps use it at onboarding and save the real prompt for later. - On Android 12 and below there is no prompt, so the explainer should not promise one; it can simply confirm reminders are on. ## Common mistakes - Prompting in the root component's first effect, on every launch, with no context. - Showing the system prompt without an explainer, then showing the explainer after the refusal. - Re-requesting in a loop after a denial, which on iOS does nothing and on Android soon does nothing either. - Treating a refusal as final for the whole app instead of degrading gracefully: the habit list, streaks and in-app reminders keep working.

  • In the habit tracker, what should the app do if the user taps Not now on your explainer?
    Record the decline, keep the habit working with in-app reminders, and offer again later at another meaningful moment, for example after a week-long streak. Because the system prompt was never shown, nothing is lost. Do not re-show the explainer on every launch; a cooldown stored per user keeps it from turning into nagging.
  • Why should the explainer never appear after the system prompt has already been refused?
    Once iOS has a stored denial, or Android has stopped showing `POST_NOTIFICATIONS`, a request draws nothing. An explainer that ends in a request would then promise a dialog that never comes. When `canAskAgain` is false, the screen should instead explain how to enable reminders in Settings and open it with `Linking.openSettings()`.

It is like asking for someone's phone number: asking at the door before they know you usually earns a no, and a no is hard to reverse. Asking after a good conversation, when there is a clear reason to call, usually works.

saying these in an interview costs you the question

  • Ask for notification permission on first launch so it is out of the way
  • You can keep asking until the user says yes
  • An explainer screen is only needed after the system prompt was refused
  • If the user declines, the whole app should block until they accept
  • Checking the current permission state first is unnecessary overhead