Skip to main content
Tutorial

Push Notifications in Expo: The Complete Guide I Wish Existed When I Started

4 min read

Expo notifications in React Native: battle-tested steps and real-world gotchas I learned shipping apps for UAE clients.

react-nativeexpopush-notificationsmobile-devtutorial

Last year, at 2:47 AM in my Abu Dhabi flat, I stared at a notification icon on my React Native test device that refused to show anything. I'd spent 14 hours straight trying to make a construction client's project manager app deliver push notifications. Expo's documentation? Full of holes. Tutorial videos? Outdated by three SDK versions. The client needed real-time alerts for site supervisors in Arabic — and I had 12 hours before their morning standup in Dubai.

Here's everything I wish I'd had that night.

The Setup Dance (And What They Don't Tell You)

Expo hides complexity behind nice abstractions until it doesn't. For notifications, you'll need:

  1. Your project set up with expo-notifications and expo-device
  2. Proper configuration in app.json (more on that later)
  3. An Android device with Google Play Services — simulator won't work

I spent six hours one time discovering we'd forgotten the package field in app.json. Not the projectId or slug — literal Android package name. The error? Zero indication. Took me digging into native logs with adb to find that the JS layer wasn't even loading the notifications module because of a basic manifest typo.

For iOS: You need APNs auth key + Provisioning Profile. No ifs. Android? Firebase Cloud Messaging (FCM) credentials. But Expo abstracts most of this — until you go to production.

Writing the Actual Code — Don't Copy StackOverflow

The basic flow:

Import modules:

javascript
import * as Notifications from "expo-notifications";
import { Platform } from "react-native";

Ask for permissions:

javascript
Notifications.requestPermissionsAsync({
  ios: {
    allowAlert: true,
    allowBadge: true,
    allowSound: true,
  },
});

Handle notifications:

javascript
const subscription = Notifications.addNotificationReceivedListener((notification) => {
  // Your UI logic here
});

But wait — your notification handler dies when the app is closed. To wake it up, you need two things:

  • Notifications.setNotificationHandler
  • Background service setup (in eas.json if you're using updates)

One time, I coded for 9 hours straight believing addNotificationReceivedListener would work on background launches. Spoiler: It doesn't. You need Headless JS tasks for Android and performFetchWithCompletionHandler on iOS.

The 15-Hour SDK 54 Error That Broke Me

This client had a plant care app going through Expo SDK 54. Everything worked perfectly — until we added Pushwoosh as a third-party provider. The notification tokens kept invalidating themselves.

Turns out Expo Notifications module conflicts with Pushwoosh when both request FCM tokens. Solution? Fork the expo-notifications package. Not the answer I wanted, but the only one that worked.

Never assume third-party libraries play nice. Check the AndroidManifest.xml for permission clashes:

xml
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.VIBRATE" />

You'll also need those if scheduling local alarms.

Testing Sucks Because Devices Lie

Simulator ≠ Real World.

One night before a Tawasul Limo release, local notifications triggered fine on simulators but failed silently on physical Android devices in Jeddah. Reason? ForegroundService requirements for heads-up notifications. The app wasn't whitelisted in battery settings on Saudi devices.

Test on physical hardware. Then test again on another manufacturer. Samsung handles doze mode differently from Xiaomi. No way around it.

Also learn this NOW: Silent notifications (content-available: 1 in APNs) don't work reliably with managed Expo builds. You'll need to eject or use custom dev clients.

Making it Work for UAE Clients (Because Timezones Matter)

At 4 AM debugging with a client's marketing team in Riyadh, I realized we'd hardcoded notification timezones to UTC+4. During Ramadan, their push reminders went out 5 hours late. Not a cool look when you're messaging about Iftar discounts.

For regional apps:

  • Store user timezone in your backend
  • Use moment-timezone in Node.js lambdas
  • Always convert server timestamps to user's local time before sending

One of my projects, Greeny Corner, delivers Arabic plant care reminders. Had to fix the titleLocKey field for translations — FCM doesn't handle non-Latin character substitution out of the box. Had to pre-render the actual message on the backend.

Beyond the Basics: Scheduling and Deep Links

Most tutorials stop at "Hey Look It Works." Real users want:

  • Scheduling recurring notifications (daily plant watering reminders for Greeny Corner)
  • Deep linking from notifications (open specific booking ID in Tawasul Limo app)
  • Background data fetches (update real estate property listings in Reach Home)

For scheduling, use:

javascript
Notifications.scheduleNotificationAsync({
  content: {
    title: "Water your plant 😊",
    body: "Don't forget!",
    data: { plantId: 123 },
  },
  trigger: {
    hour: 9,
    minute: 0,
    repeats: true,
  },
});

Deep linking: Make sure your Linking setup supports the custom URI scheme in notification data payloads. iOS requires extra config in Info.plist.

Closing Thoughts... And The Part Where You Don't Panic

The hardest part of shipping push notifications isn't writing the code — it's the 3AM debugging when stakeholders expect miracle deliveries. You'll forget some permission on one Android version. Firebase console will show tokens as active when they clearly aren't.

When that happens, breathe. Double-check the .p8 auth key expiry for iOS. Make sure the EXPO_ANDROID_KEY in your env really matches the one from Firebase Cloud Messaging console.

I once forgot to enable the Google Cloud Messaging API on a new Firebase project. Took 8 hours of yelling at Samsung devices until I noticed the HTTP 403 in the logs.

Now, go make your notifications work. Then come complain about Firebase in the DMs.

You can reach me at sarahprofile.com/contact if you hit a wall nobody should climb alone.

S

Sarah

Senior Full-Stack Developer & PMP-Certified Project Lead — Abu Dhabi, UAE

7+ years building web applications for UAE & GCC businesses. Specialising in Laravel, Next.js, and Arabic RTL development.

Work with Sarah