How to Test Push Notifications on Flutter (Complete Guide)
How to Test Push Notifications on Flutter (Complete Guide)
How to Test Push Notifications on Flutter (Complete Guide)
Push notifications are a critical engagement channel for mobile apps, yet they are also one of the most fragile parts of a Flutter application. A mis‑configured payload, a missing permission check, or an unhandled tap can silently drop messages, crash the app, or expose user data. Because notifications travel outside the Dart VM—through platform‑specific services like Firebase Cloud Messaging (FCM) on Android and Apple Push Notification service (APNs) on iOS—traditional unit tests that only exercise Dart logic miss many failure modes. This guide walks you through a complete, battle‑tested strategy for verifying that push notifications work reliably across devices, personas, and failure conditions. You’ll find a detailed test matrix, step‑by‑step manual procedures, automated approaches that fit into CI pipelines, real‑world code snippets, and a short checklist you can bookmark. Throughout, we show how autonomous, persona‑driven exploration (the kind SUSATest performs) surfaces bugs that scripted tests never think to look for.
How to Test Push Notifications on Flutter (Complete Guide) – Why It Matters
Push notifications sit at the intersection of three layers: the Flutter UI, the platform‑specific messaging plugin, and the backend push service. When any layer misbehaves, the user experience suffers. Common production issues include:
- Silent drops – the app never receives a message because the Flutter plugin failed to register the token or because iOS/Android blocked the notification due to missing permissions.
- Crashes on tap – the notification’s
onLaunchoronResumehandler assumes a payload shape that the backend occasionally deviates from, causing aNullPointerExceptionor a DartTypeError. - ANR / jank – heavy work performed directly in the notification callback blocks the main thread, leading to Android “Application Not Responding” dialogs or iOS watchdog terminations.
- Accessibility gaps – notifications that lack proper
accessibilityLabelor that trigger UI changes not announced by TalkBack/VoiceOver leave users of assistive technology unaware of new content. - Privacy leaks – logging the full payload to console or sending it to analytics without stripping personally identifiable information (PII) can violate GDPR or CCPA.
Because these problems often surface only after a specific combination of device OS version, plugin version, and network condition, a disciplined testing approach is essential. The following sections give you a repeatable process that covers the full spectrum of failure modes.
How to Test Push Notifications on Flutter (Complete Guide) – Test Matrix
A well‑structured test matrix ensures you exercise every relevant path. Below is a comprehensive matrix that you can copy into a test‑management tool or a simple spreadsheet. Each row represents a test scenario; columns indicate the dimension you are validating.
| ID | Category | Sub‑category | Description | Expected Result | Automation Feasibility |
|---|---|---|---|---|---|
| H1 | Happy Path | Foreground | App is in foreground, notification arrives, UI shows banner/in‑app alert | Notification displayed, onMessage handler called, state updated correctly | High (flutter_test + mock FCM) |
| H2 | Happy Path | Background | App in background, notification arrives, user taps it | App opens to correct route, payload parsed, deep‑link handling works | Medium (integration test on device) |
| H3 | Happy Path | Terminated | App not running, notification arrives, user taps it | App launches from cold start, initial route reflects notification data | Medium (requires device automation) |
| E1 | Error Path | Missing Permission | Android: POST_NOTIFICATIONS denied; iOS: user denied alerts | No notification shown, plugin logs permission error, graceful fallback UI shown | Medium (permission mock) |
| E2 | Error Path | Invalid Payload | Backend sends malformed JSON (missing data field) | Plugin does not crash, onMessage receives null or empty map, app shows generic error toast | High (unit test with bad JSON) |
| E3 | Error Path | Expired Token | FCM token revoked, backend sends to old token | Backend receives NotRegistered error, app should refresh token on next launch | Low (needs backend simulation) |
| E4 | Error Path | Network Failure | Device offline when push sent | No notification queued; when connectivity returns, pending notification delivered (if APNs/FCM supports) | Medium (network throttling) |
| E5 | Error Path | Heavy Callback | Notification handler performs 2‑second synchronous work | No frame drops, UI remains responsive (<16ms per frame) | Low (requires performance test) |
| A1 | Accessibility | TalkBack/VoiceOver | Notification triggers UI change (e.g., new badge) | Change announced correctly, focus moves to new element if appropriate | Medium (semantics test) |
| A2 | Accessibility | Font Scaling | User has set largest font size | Notification UI respects scaling, no overflow or clipping | Low (visual test) |
| S1 | Security/PII | Payload Logging | App logs entire RemoteMessage to console | No PII appears in logs; only non‑sensitive identifiers logged | High (unit test with spy logger) |
| S2 | Security/PII | Analytics Tracking | App sends notification click event to analytics | Event payload stripped of personal data before transmission | Medium (mock analytics) |
| S3 | Security/PII | Deep Link Validation | Notification contains a URL that could navigate to external site | App validates URL scheme, blocks navigation to non‑whitelisted domains | Medium (unit test with URL validator) |
*Notes*
- Automation Feasibility is a rough guide: “High” means you can achieve reliable coverage with unit/widget tests and mocked plugins; “Medium” usually requires an integration test on a real device or emulator; “Low” suggests you need manual exploratory testing, performance profiling, or backend simulation.
- The matrix intentionally separates platform‑agnostic Flutter logic (handler code, state updates) from platform‑specific concerns (permissions, token management). This distinction helps you decide where to invest in automated versus manual effort.
How to Test Push Notifications on Flutter (Complete Guide) – Manual Step‑by‑Step Approach
Even with strong automation, a manual exploratory pass catches edge cases that scripts overlook—especially those tied to device‑specific OEM behaviors, battery optimizations, or user‑initiated settings changes. Follow this procedure on both Android and iOS devices (or emulators/simulators) before each release.
1. Prepare the Test Environment
- Install two builds: a debug build with verbose logging enabled and a release‑like profile build to catch performance‑related issues.
- Enable platform logging:
- Android:
adb logcat | grep -i firebase_messaging - iOS:
xcrun simctl spawn booted log show --style compact --predicate 'process == "Runner"' --info
- Clear notification history: On Android, go to Settings → Apps → YourApp → Notifications → Notification history and clear; on iOS, swipe away all notifications in the Notification Center.
- Set device to default battery optimization: Disable any aggressive battery saver that might defer background delivery.
2. Register and Retrieve the Push Token
Run the app and observe the console for the token output from FirebaseMessaging.instance.getToken(). Record it; you’ll need it to send test messages from your backend or from the Firebase Console.
void _initMessaging() async {
final token = await FirebaseMessaging.instance.getToken();
debugPrint('FCM Token: $token');
FirebaseMessaging.instance.onTokenRefresh.listen((newToken) {
debugPrint('Token refreshed: $newToken');
// send newToken to your backend
});
}
3. Trigger Test Notifications
Use one of the following methods to send a notification:
- Firebase Console → Cloud Messaging → Send a test message (paste the token).
- cURL to FCM HTTP v1 API (replace
YOUR_SERVER_KEYandTOKEN). - Backend test endpoint that calls your push service directly.
Send three variations:
- Minimal payload – only
notification.titleandnotification.body. - Data‑only payload – custom JSON under
data:with fields your app expects (e.g.,{"type":"promo","id":"123"}). - Mixed payload – both notification and data sections.
4. Observe Foreground Behavior
With the app in the foreground:
- Verify that a visual cue appears (snackbar, dialog, or inline badge) as defined by your
onMessagehandler. - Check that the payload is correctly parsed: print the received
RemoteMessageto console and compare with the sent JSON. - Ensure no exceptions are thrown; inspect the logs for stack traces.
5. Observe Background and Terminated Behavior
- Background: Press the home button to send the app to the background, then send a notification. When the notification appears in the shade/drawer, tap it. Confirm the app opens to the route you specified in
onBackgroundMessageoronLaunch. - Terminated: Swipe the app away from recent‑apps (Android) or quit it from the app switcher (iOS). Send a notification and tap it. The app should launch from a cold start; check that the initial route reflects the notification data (often handled in
firebase_messaging'sgetInitialMessage()).
6. Test Permission Scenarios
- Deny permission: On Android 13+, go to Settings → Apps → YourApp → Notifications and toggle off. On iOS, decline the permission prompt when the app first asks. Send a notification and verify that the app does not crash and shows an in‑app prompt to enable notifications.
- Grant after denial: Re‑enable permission via settings, then send another notification to ensure recovery works.
7. Stress Test the Callback
Introduce a deliberate delay in your onMessage handler (e.g., await Future.delayed(const Duration(seconds, 2));) and watch for frame drops using Flutter’s performance overlay (flutter run --profile then press p). The UI should remain at 60 fps; if you see jank, move heavy work to an isolate or use WidgetsBinding.addPostFrameCallback.
8. Validate Accessibility
- Enable TalkBack (Android) or VoiceOver (iOS).
- Send a notification that triggers a UI change (e.g., a new badge on the navigation bar).
- Confirm that the change is announced and that focus moves logically if your design dictates it.
- Use the accessibility scanner (Android) or Accessibility Inspector (iOS) to check for missing labels or contrast issues.
9. Check for Privacy Leaks
- Look at the console output while sending a notification that contains a test PII field (e.g.,
"email":"test@example.com"). Ensure your logging code strips or hashes such fields before printing. - If you use an analytics plugin, inspect the network traffic (via Charles Proxy, mitmproxy, or Xcode Network Link Conditioner) to confirm that the payload sent does not contain raw personal data.
10. Document Findings
Create a short test log for each scenario, noting:
- Device model and OS version.
- Build variant (debug/profile/release).
- Observed behavior (pass/fail).
- Any logs or screenshots that illustrate the issue.
Repeat the matrix on at least once on a second physical device (different OEM) to catch manufacturer‑specific quirks (e.g., Xiaomi’s MIUI aggressive background restrictions).
How to Test Push Notifications on Flutter (Complete Guide) – Automated Approaches
Automation gives you repeatable confidence and lets you push verification into CI pipelines. Flutter provides several layers you can exploit: unit tests for pure Dart logic, widget tests for UI reactions, and integration tests for end‑to‑end flows on real devices or emulators.
Unit Testing the Notification Handler
Most of your app’s reaction to a notification lives in a Dart class that processes RemoteMessage. Because this class depends only on the firebase_messaging plugin’s public interface, you can mock it using the mockito package.
// notification_handler.dart
class NotificationHandler {
final FirebaseMessaging _messaging;
NotificationHandler(this._messaging);
Future<void> initialize() async {
await _messaging.requestPermission();
_messaging.onMessage.listen(_onMessage);
_messaging.onBackgroundMessage = _onBackgroundMessage;
}
void _onMessage(RemoteMessage message) {
// Example: show a snackbar with the title
final title = message.notification?.title ?? '';
final body = message.notification?.body ?? '';
// In real app, use a ScaffoldMessenger or state management
debugPrint('Received: $title - $body');
// TODO: update UI via Streams/Bloc/etc.
}
Future<void> _onBackgroundMessage(RemoteMessage message) async {
// Heavy work should be isolated; here we just log
debugPrint('Background message: ${message.messageId}');
}
}
Corresponding test:
// notification_handler_test.dart
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'notification_handler.dart';
@GenerateMocks([FirebaseMessaging])
void main() {
late MockFirebaseMessaging mockMessaging;
late NotificationHandler handler;
setUp(() {
mockMessaging = MockFirebaseMessaging();
handler = NotificationHandler(mockMessaging);
});
test('onMessage prints title and body', () async {
// Arrange
final message = RemoteMessage(
data: {},
notification: RemoteNotification(
title: 'Test Title',
body: 'Test Body',
),
);
// Act
handler._onMessage(message);
// Assert – verify that debugPrint was called with expected string
// Since debugPrint goes to stdout, we can capture it via expectLater
// For brevity, we assert that no exception is thrown.
expect(() => handler._onMessage(message), returnsNormally);
});
}
Run with flutter test. This validates that your handler does not throw when faced with null fields.
Widget Testing UI Reactions
If your app shows a Snackbar or updates a badge upon receiving a message, you can simulate that by invoking the handler directly in a widget test.
// notification_badge_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:your_app/main.dart'; // contains your root widget
import 'notification_handler.dart';
void main() {
testWidgets('Badge increments on message', (WidgetTester tester) async {
// Build app with a provider that holds the handler
await tester.pumpWidget(
Provider<NotificationHandler>(
create: (_) => NotificationHandler(FirebaseMessaging.instance),
child: const MyApp(),
),
);
// Simulate a message
final handler = tester.state<NotificationHandlerProvider>(find.byType(Provider));
final message = RemoteMessage(
data: {},
notification: RemoteNotification(title: 'Hi', body: 'There'),
);
handler.notificationHandler._onMessage(message);
// Pump to allow UI reaction
await tester.pump();
// Expect badge to show '1'
expect(find.text('1'), findsOneWidget);
});
}
Integration Testing End‑to‑End Flows
Integration tests run on a real device or emulator and can actually receive push notifications if you configure the test runner to use a special Firebase project (or a mock FCM server). The integration_test package works well with flutter_driver.
#### Setting Up a Mock FCM Server
For CI, you can run a lightweight mock server that mimics the FCM HTTP endpoint. One popular choice is firebase-mock (Node.js) or the open‑source fcmmock Docker image. Point your app’s google-services.json / GoogleService-Info.plist to a dummy project whose server key is routed to the mock.
#### Sample Integration Test
// integration_test/push_notification_test.dart
import 'package:integration_test/integration_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:your_app/main.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Push Notification Flow', () {
testWidgets('app opens correct screen when tapped', (WidgetTester tester) async {
// Launch app in background state
await tester.pumpWidget(const MyApp());
await tester.pumpAndSettle();
// Simulate receiving a notification while app is backgrounded
// This uses the platform channel to inject a RemoteMessage
const String messageJson = '''
{
"messageId": "msg_123",
"notification": {
"title": "Offer",
"body": "20% off"
},
"data": {
"type": "promo",
"id": "42"
}
}
''';
// On Android we use the FirebaseMessaging plugin's test API
// On iOS we can send a silent push via simulator utilities
if (Platform.isAndroid) {
await tester.binding.defaultBinaryMessenger.handlePlatformMessage(
'plugins.flutter.io/firebase_messaging',
const StringCodec().encodeMessage(messageJson),
(_) => null,
);
} else if (Platform.isIOS) {
// Use simctl to push a notification; omitted for brevity
}
// Pump to allow plugin to deliver the message
await tester.pump(const Duration(seconds, 2));
// Tap the notification (simulated by tapping the notification badge)
await tester.tap(find.byKey(const Key('notificationBadge')));
await tester.pumpAndSettle();
// Verify navigation to promo screen
expect(find.text('Promo Details'), findsOneWidget);
expect(find.textContaining('ID: 42'), findsOneWidget);
});
});
}
Run with:
flutter drive --target=integration_test/push_notification_test.dart -d <device-id>
Performance and Battery Impact Tests
Use Flutter’s built‑in performance overlay to measure UI jank during callback execution. For battery impact, rely on Android’s adb shell dumpsys batterystats or Xcode’s Energy Log. Capture a baseline before sending a burst of notifications (e.g., 10 messages in 10 seconds) and compare the delta.
Continuous Integration Integration
Add the following steps to your CI (GitHub Actions, GitLab CI, Bitrise, etc.):
flutter test– unit & widget tests.flutter build apk --split-per-abi– ensure release build compiles.flutter drive --target=integration_test/push_notification_test.dart– run integration on a Firebase Test Lab device matrix (or local emulator pool).- Upload logs and screenshots as artifacts for later review.
How to Test Push Notifications on Flutter (Complete Guide) – Tooling and Libraries
Having the right tooling reduces friction and surfaces issues early. Below is a comparison of the most useful libraries and utilities for Flutter push‑notification testing.
| Tool / Library | Primary Use | Platform Support | Setup Complexity | Notes |
|---|---|---|---|---|
firebase_messaging | Official FCM/APNs plugin | Android, iOS, Web | Low | Includes onMessage, onBackgroundMessage, getToken. |
flutter_local_notifications | Show in‑app notifications, schedule local alerts | Android, iOS | Low | Useful for testing UI without needing a remote push. |
mockito / mocktail | Mock FirebaseMessaging in unit tests | Dart (VM/Flutter) | Low | Enables deterministic handler testing. |
integration_test | End‑to‑end tests on device/emulator | Android, iOS, Web | Medium | Requires configuring a test Firebase project or mock server. |
firebase_test_lab (via gcloud) | Run instrumentation tests on a matrix of real devices | Android | Medium | Good for catching OEM‑specific issues. |
SUSATest (CLI: susatest-agent) | Autonomous exploratory testing with persona profiles | Android (APK), Web (URL) | Low – install via pip | Generates Appium/Playwright regression scripts from discovered flows; can detect missing notification handling, dead buttons after tap, etc. |
Android Studio Profiler / Xcode Instruments | Monitor CPU, memory, battery, and frame drops during notification handling | Android, iOS | Low | Essential for performance validation. |
Charles Proxy / mitmproxy | Inspect network traffic, including FCM/APNs payloads | Cross‑platform | Medium | Useful to verify that no PII leaves the device. |
flutter_launcher_icons / flutter_native_splash | Not directly related but ensures that the launcher icon appears correctly in the notification shade (Android) | Android, iOS | Low | Visual consistency check. |
How to Choose
- For fast feedback during development, rely on unit + widget tests with
mockito. - For pre‑release confidence, run integration tests on a few device configurations via Firebase Test Lab.
- For continuous discovery of regressions, schedule a nightly run of SUSATest against your release candidate; its persona‑driven exploration will try notification taps from curious, impatient, and power‑user profiles, often uncovering routes that your scripted tests never exercised.
- For performance and battery validation, profile a build while sending a high‑frequency notification stream (use a simple loop that pushes via FCM test tool) and watch for frame drops or excessive wake‑locks.
How to Test Push Notifications on Flutter (Complete Guide) – Autonomous, Persona‑Driven Exploration with SUSATest
Even the most thorough test matrix can miss edge cases that arise only when real users interact with the app in unpredictable ways. SUSATest addresses this by launching an autonomous agent that explores the app without pre‑written scripts, guided by configurable personas that model distinct behaviors.
How It Works
- Input – You provide either an APK (Android) or a web URL.
- Exploration Engine – The agent uses a combination of computer vision (UI element detection) and accessibility heuristics to discover tappable, scrollable, and input‑capable elements.
- Persona Profiles – Each persona has a tuned policy:
- *Curious* – tries every visible button, even if it looks disabled.
- *Impatient* – performs rapid taps and scrolls, often triggering race conditions.
- *Novice* – sticks to obvious primary actions, ignoring hidden menus.
- *Adversarial* – sends malformed inputs, long strings, and attempts to break validation.
- *Elderly* – uses longer press durations, avoids quick gestures.
- *Accessibility* – relies on screen‑reader navigation, ensuring TalkBack/VoiceOver compatibility.
- *Power User* – opens navigation drawers, uses shortcuts, and attempts deep‑link URLs from notifications.
- Observation – While exploring, the agent monitors logs, crash reports, ANR traces, and UI changes. It also records any notification that arrives (via platform hooks) and verifies that the app reacts as expected.
- Reporting – After a run, you receive a JSON report with PASS/FAIL verdicts for each discovered flow, plus screenshots, video, and logs. The agent also generates regression scripts (Appium for Android, Playwright for web) that you can add to your CI.
What It Finds That Scripts Miss
- Notification‑triggered race conditions – A power‑user persona may tap a notification while simultaneously opening the side drawer, leading to a state where two navigation controllers compete. Scripted tests usually serialize actions.
- Permission‑prompt timing – An impatient persona might spam the notification permission dialog, revealing a bug where rapid denials cause the plugin to enter an inconsistent token state.
- Localization‑driven layout overflow – The accessibility persona, using a system‑wide font scale, can expose that a notification‑generated badge overlaps adjacent text in certain languages.
- Deep‑link hijacking – An adversarial persona may craft a notification payload containing a
http://URL; the app inadvertently launches the browser instead of handling it internally, a scenario rarely covered in unit tests. - Battery‑optimization interference – On certain OEM skins (e.g., MIUI, ColorOS), the curious persona’s background‑service checks may trigger aggressive battery restrictions, causing notifications to be silently dropped—something that only appears after prolonged, varied interaction.
Example: Discovering a Dead Button After Notification Tap
During a SUSATest run with the *elderly* persona on a shopping app, the agent observed the following sequence:
- App in background, notification arrives (“Your order shipped”).
- User taps the notification (simulated via a long press to mimic slower motor skills).
- App opens to the order‑details screen, but the “Track Shipment” button is unresponsive.
Investigation revealed that the navigation handler used Navigator.pushNamed with a route name that was only registered after a certain authentication check. The notification deep link bypassed the auth flow, leaving the button’s onPressed callback bound to a null controller. The bug would not appear in a scripted test that always launched the app via the login flow first.
SUSATest automatically generated an Appium test that reproduces the exact tap sequence, which you can now add to your regression suite.
Integrating SUSATest into Your Workflow
# Install the CLI (once)
pip install susatest-agent
# Run a persona‑driven exploration against your Android build
susatest explore \
--apk ./build/app/outputs/flutter-apk/app-release.apk \
--personas curious impatient accessibility \
--output ./susatest-report.json \
--generate-scripts # creates Appium test folder
The generated Appium tests live under susatest_generated/ and can be invoked in your CI pipeline with a standard appium command. Because SUSATest learns from prior runs, each subsequent execution explores new paths while skipping previously verified dead ends, making the process progressively more efficient.
How to Test Push Notifications on Flutter (Complete Guide) – Checklist
Use this concise list as a gate before merging a release candidate or before handing off a build to QA. Tick each item; if any item is red, investigate before proceeding.
| ✅ | Item | How to Verify |
|---|---|---|
| 1 | Token registration succeeds on first launch | Check console for token; ensure no FirebaseException. |
| 2 | Foreground notification shows UI as designed | Send a test message; verify snackbar/dialog/badge appears. |
| 3 | Background tap opens correct route | Send message, background app, tap notification, assert route. |
| 4 | Terminated app cold‑starts correctly from notification | Swipe away app, send notification, tap, verify initial state. |
| 5 | Permission denial handled gracefully | Disable notifications, send message, confirm no crash and optional in‑app prompt. |
| 6 | Invalid or missing payload does not crash | Send malformed JSON, ensure handler logs error and continues. |
| 7 | Heavy work in callback does not cause jank | Profile with performance overlay while inserting delay(2s) in handler. |
| 8 | Accessibility announcements present | Enable TalkBack/VoiceOver, send notification, listen for announcement. |
| 9 | No PII leaked in logs or analytics | Search console/network for raw email, phone, IDs. |
| 10 | Deep link from notification validated | Send notification with external URL, confirm app blocks or sanitizes. |
| 11 | Battery impact acceptable | Run adb shell dumpsys batterystats before/after a burst of 10 notifications. |
| 12 | Regression scripts generated | Run SUSATest (or your integration test suite) and confirm no new FAILs. |
| 13 | Release build passes all automated tests | Execute flutter test and flutter drive on CI; all green. |
| 14 | Manual exploratory pass on at least two device OEMs | Perform the matrix steps on a Samsung and a Xiaomi device (or equivalents). |
If you can check every box, you have high confidence that the push‑notification subsystem will behave correctly for the majority of real‑world users.
How to Test Push Notifications on Flutter (Complete Guide) – Real‑World Examples and Lessons Learned
Example 1: Missing Permission Handling on Android 13
A finance app updated to target Android 33 (API level 33) introduced the runtime POST_NOTIFICATIONS permission. The developers added the permission request but forgot to handle the case where the user permanently denied it (“Don’t ask again”). When a notification arrived, the firebase_messaging plugin threw a PlatformException that was uncaught, causing the app to crash silently (the crash was only visible in Firebase Crashlytics as a Fatal Exception: io.flutter.plugins.firebasemessaging.FirebaseMessagingException).
Lesson: Always provide a fallback UI that explains why notifications are needed and offers a shortcut to Settings. Use FirebaseMessaging.instance.requestPermission() and check the returned AuthorizationStatus. If status is denied and isPermanentlyDenied is true, launch the settings page via openAppSettings().
Example 2: Payload Parsing Crash Due to Unexpected Null
An e‑commerce app expected every push to contain a data field with a JSON‑encoded orderId. The backend occasionally sent a notification with only the notification title/body and no data field. The Dart code performed jsonDecode(message.data['order'] as String) without a null check, resulting in a FormatException that was caught by a generic try/catch but then re‑threw as a state error, causing the UI to show an blank screen.
Lesson: Treat all incoming fields as optional. Use message.data.containsKey('order') before decoding, and provide a default or error UI when required data is missing. Write unit tests that feed a RemoteMessage with an empty data map to verify graceful handling.
Example 3: Notification Tap Leads to Wrong Locale
A travel app localized its strings based on the device locale at startup. A notification deep link contained a route parameter ?lang=es to force Spanish. However, the navigation guard that reads the locale was executed *before* the deep‑link handler could override it, so the UI remained in English despite the user’s explicit request.
Lesson: When supporting locale overrides via query parameters, read and apply the override *early* in the app lifecycle—ideally in main() before any widgets are built. Write a small integration test that sends a notification with a lang parameter and asserts that the localized strings appear in the correct language.
Example 4: Battery Drain from Wake‑Lock Misuse
A productivity app used a WakeLock to keep the CPU awake while processing a long‑running notification task (e.g., uploading a log file). The developer forgot to release the lock in a finally block, causing the device to stay awake for up to 30 minutes after the notification was handled, drastically reducing battery life. The issue only appeared after a user received several notifications in a short span.
Lesson: Always pair WakeLock.acquire() with WakeLock.release() in a try/finally. Use the Android Battery Historian or Xcode Energy Log to verify that wake‑lock duration matches expected work time.
These cases illustrate that
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free