Skip to main content

Command Palette

Search for a command to run...

Building a Robust Notification System in React Native (FCM + Notifee + Real-Time Sync)

Android Push Notifications Architecture

Updated
4 min read
Building a Robust Notification System in React Native (FCM + Notifee + Real-Time Sync)

When I first started implementing push notifications in a React Native app, I assumed it would be straightforward.

"Just integrate Firebase and done."

That assumption lasted about 10 minutes.

What I eventually built was not just push notifications - but a complete notification system that works across:

  • Foreground

  • Background

  • Killed app state

  • Interactive actions (Approve / Reject)

This article walks through that system - what works, what breaks, and what you actually need in production.


The Stack I Used

Before diving in, here's the setup:

  • Firebase Cloud Messaging :  receives notifications from backend

  • Notifee : displays notifications with custom UI and actions

  • Socket.io :  keeps data fresh when app is open


The Architecture (This is where things get interesting)

Push notifications aren't just "receive and show".

They're a pipeline.

Backend → FCM → App → Notifee → User → Action → Backend

Push Notification Architecture

Step 1: Setting Up Firebase (The Missing Piece Most Docs Skip)

Before anything works, you need to connect your app to Firebase.

  1. Go to Firebase Console

  2. Create a project

  3. Add Android app

  4. Use your package name

  5. Download google-services.json

  6. Place it here:

android/app/google-services.json
  1. Then connect it using Gradle:
// android/build.gradle classpath 
'com.google.gms:google-services:4.3.15'
// android/app/build.gradle apply plugin:
'com.google.gms.google-services' 

Without this, nothing else matters. Seriously.


Step 2: Notification Channels (Android Reality Check)

If you've never dealt with Android notification channels, here's the catch:

Once a channel is created, you cannot change it.

So if you mess up sound or importance ... Then, you're stuck.

The workaround?

Version your channel:

default_sound_v0 → default_sound_v1 → default_sound_v2 ... 

Every time you need changes → create a new version.


Notification Settings

Step 3: Custom Notification Sounds

I wanted a branded notification sound, so I added:

notification_sound.wav

Placed here:

android/app/src/main/res/raw/

And this line saved me hours:

noCompress += ['wav', 'mp3', 'ogg']

Without it, the sound randomly fails on real devices.


Step 4: Handling All App States

This is where most implementations break.

Foreground

FCM does NOT show notifications automatically.

You must handle it manually:

onMessage → display notification using Notifee

Background / Killed App

Handled in:

setBackgroundMessageHandler

This runs even when your app is not open.


App Notification Lifecycle

Step 5: Interactive Notifications (Game Changer)

This is where things feel "premium".

Instead of just showing a message, I added:

  • Approve

  • Reject

When user taps:

deliveryAuthorizationService.takeAction(id, { action })

Now notifications are not passive . Now, they're actionable.


Step 6: Event-Based System (Clean Architecture)

Instead of handling everything in one place, I mapped notifications to events:

AUTH_DELIVERY → open modal + refresh data
RATE_UPDATED → update UI
SYSTEM_ALERT → show alert banner

This keeps the system scalable.


Step 7: Token Registration (Don't Skip This)

Notifications won't work unless your backend knows the device.

Flow:

  1. Request permission

  2. Get token

  3. Send to backend

getToken()
registerDeviceToken()

FCM Token Registration Flow

Things I Learned the Hard Way

Duplicate notifications happen if backend sends both notification and data

  • Android channels cannot be edited after creation

  • Foreground notifications must be handled manually

  • Custom sounds break if compressed

  • Missing google-services.json = silent failure


Final Thoughts

Push notifications are not a "feature".

They're a system.

Once you treat them like one, separating:

  • Receiving

  • Displaying

  • Handling actions

  • Syncing data

Everything starts to make sense.

And more importantly, it becomes reliable.


If you're building something similar, focus less on "making it work" and more on "making it predictable".

That's the real difference between a demo and a production system.


Note: A few images in this article are AI-generated to help visualize the concepts more clearly.