Push Notifications

Push notifications re-engage users with timely updates, but they're one of the trickier mobile features to get right: messages travel through platform services (FCM/APNs), permission must be requested thoughtfully, and your app has to handle notifications in three different states — foreground, background, and killed — each behaving differently. And they must be tested on real devices, since simulators have limitations.

The cross-platform standard is Firebase Cloud Messaging (FCM), which routes to Apple's APNs on iOS and Google's infrastructure on Android. The three-state handling is the part most teams underestimate — a notification that opens the right screen from a killed app is meaningfully harder than one shown while the app is open.

TL;DR

Quick Example

Request permission, then register the device's FCM token with your server:

Core Concepts

Notification types: remote/push (sent from a server via FCM/APNs), local (scheduled by the app), data (silent, triggers code without UI), and display (shows notification UI).

The flow and the three app states:

React Native Setup

Configure native projects: iOS — enable Push Notifications capability, add an APNs key, and GoogleService-Info.plist; Android — add google-services.json and the Google Services Gradle plugin.

Then handle all three states:

Flutter Setup

Local Notifications

For reminders and scheduled alerts, no server needed:

Best Practices

Common Mistakes

Requesting permission on first launch

Ignoring the killed/background states

FAQ

What's the difference between FCM and APNs?

APNs (Apple Push Notification service) is Apple's low-level delivery system for iOS; FCM (Firebase Cloud Messaging) is Google's cross-platform service that delivers to Android directly and to iOS by relaying through APNs. Using FCM gives you one API and one server integration for both platforms — which is why it's the common choice. On iOS you still configure APNs credentials so FCM can deliver through it.

Why do I have to handle three app states separately?

Because the OS treats notifications differently depending on whether your app is in the foreground, background, or killed. In the foreground, no system notification appears automatically — you decide whether/how to show it. In the background, the system displays it and your handler runs on tap. From killed, tapping cold-starts the app and you must check for the launching notification explicitly. Handling only the foreground case (the easy one) breaks taps from the other two states.

When should I ask for notification permission?

Not on first launch. Ask after the user has experienced enough value to want notifications — e.g. after they create something that warrants updates — ideally preceded by a custom pre-prompt explaining the benefit. On iOS especially, a denial is sticky (the user must change it in Settings), so a poorly-timed request permanently loses that channel. Earn the permission rather than demanding it upfront.

Why must I test push notifications on real devices?

Simulators and emulators have real limitations — iOS Simulator historically couldn't receive remote pushes at all (newer versions have partial support), and behavior around background/killed states, token generation, and delivery timing differs from hardware. Since the three-state handling and platform delivery are exactly what you need to verify, a real device is the only reliable test. Always validate the full flow (server → device → tap → correct screen) on hardware.

Related Topics

References