The Problem That Wasn’t There… or Was It?
A few months ago, I was working on a mobile app with real-time notifications. The feature seemed straightforward: whenever a new order was placed in the e-commerce app, the user should receive a push notification instantly.
Everything worked perfectly during my development. On my devices, notifications popped up within milliseconds. But when the beta testers started using the app, something strange happened:
Some users received duplicate notifications.
Others got notifications late, sometimes even minutes after the event.
A few users never received them at all.
It felt like chasing a ghost. How could the system behave so inconsistently?
Digging Into the Mystery
The app used Firebase Cloud Messaging (FCM) for push notifications. At first, I suspected the backend, the mobile framework, or even the users’ devices. I went step by step:
Checked the backend logs—all notifications were sent correctly.
Tested on multiple devices—duplicates appeared only on Android.
Checked iOS devices—no duplicates there.
So the problem was platform-specific and subtle.
The Root Cause
After hours of debugging, I realized the culprit: multiple Firebase listeners were being registered unintentionally.
In my Flutter code, I had:
The problem was that this listener was being registered every time a user navigated to the home screen. Each navigation added a new listener, so if a user navigated back and forth three times, the same notification would trigger three times.
The delay issues were caused by race conditions—sometimes the notification listener fired before the app fully initialized, and the notification appeared late or got dropped.
The Fix
The solution was to register the listener once at app startup and ensure all notifications are handled in a single, centralized place.
Additionally, I made showNotification() idempotent, so even if the same message arrived twice, the app would display it only once:
What I Learned
Ghost bugs often come from subtle lifecycle issues.
Listeners and async callbacks need careful management. Multiple registrations can cause duplicate events.
Centralized handling prevents platform-specific surprises.
Testing in real scenarios is critical. Emulators never revealed the duplicates or race conditions.
Final Thoughts
This bug was fascinating because it wasn’t a “broken code” bug—it was a design and lifecycle issue. Once I understood the root cause, the fix was elegant and simple.
It reminded me that in tech, some of the most interesting problems aren’t about complex algorithms—they’re about thinking through how systems interact in the real world.
For developers working with notifications, async events, or real-time systems: always consider how many listeners exist, when they fire, and what happens if the user navigates around your app. It can save hours of chasing ghosts.



Top comments (1)
give me feedback!