Skip to content

Commit d27b24d

Browse files
authored
Merge pull request #184 from Resgrid/develop
RU-T39 Trying to fix foreground push notification issue.
2 parents 8f359b3 + a977d8d commit d27b24d

12 files changed

+1018
-408
lines changed

.DS_Store

0 Bytes
Binary file not shown.

app.config.ts

Lines changed: 2 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -104,68 +104,6 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
104104
'expo-localization',
105105
'expo-router',
106106
['react-native-edge-to-edge'],
107-
[
108-
'expo-notifications',
109-
{
110-
icon: './assets/notification-icon.png',
111-
color: '#2a7dd5',
112-
permissions: {
113-
ios: {
114-
allowAlert: true,
115-
allowBadge: true,
116-
allowSound: true,
117-
allowCriticalAlerts: true,
118-
},
119-
},
120-
sounds: [
121-
'assets/audio/notification.wav',
122-
'assets/audio/callclosed.wav',
123-
'assets/audio/callupdated.wav',
124-
'assets/audio/callemergency.wav',
125-
'assets/audio/callhigh.wav',
126-
'assets/audio/calllow.wav',
127-
'assets/audio/callmedium.wav',
128-
'assets/audio/newcall.wav',
129-
'assets/audio/newchat.wav',
130-
'assets/audio/newmessage.wav',
131-
'assets/audio/newshift.wav',
132-
'assets/audio/newtraining.wav',
133-
'assets/audio/personnelstaffingupdated.wav',
134-
'assets/audio/personnelstatusupdated.wav',
135-
'assets/audio/troublealert.wav',
136-
'assets/audio/unitnotice.wav',
137-
'assets/audio/unitstatusupdated.wav',
138-
'assets/audio/upcomingshift.wav',
139-
'assets/audio/upcomingtraining.wav',
140-
'assets/audio/custom/c1.wav',
141-
'assets/audio/custom/c2.wav',
142-
'assets/audio/custom/c3.wav',
143-
'assets/audio/custom/c4.wav',
144-
'assets/audio/custom/c5.wav',
145-
'assets/audio/custom/c6.wav',
146-
'assets/audio/custom/c7.wav',
147-
'assets/audio/custom/c8.wav',
148-
'assets/audio/custom/c9.wav',
149-
'assets/audio/custom/c10.wav',
150-
'assets/audio/custom/c11.wav',
151-
'assets/audio/custom/c12.wav',
152-
'assets/audio/custom/c13.wav',
153-
'assets/audio/custom/c14.wav',
154-
'assets/audio/custom/c15.wav',
155-
'assets/audio/custom/c16.wav',
156-
'assets/audio/custom/c17.wav',
157-
'assets/audio/custom/c18.wav',
158-
'assets/audio/custom/c19.wav',
159-
'assets/audio/custom/c20.wav',
160-
'assets/audio/custom/c21.wav',
161-
'assets/audio/custom/c22.wav',
162-
'assets/audio/custom/c23.wav',
163-
'assets/audio/custom/c24.wav',
164-
'assets/audio/custom/c25.wav',
165-
],
166-
requestPermissions: true,
167-
},
168-
],
169107
[
170108
'@rnmapbox/maps',
171109
{
@@ -267,6 +205,8 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
267205
'@react-native-firebase/app',
268206
'./customGradle.plugin.js',
269207
'./customManifest.plugin.js',
208+
'./plugins/withForegroundNotifications.js',
209+
'./plugins/withNotificationSounds.js',
270210
['app-icon-badge', appIconBadgeConfig],
271211
],
272212
extra: {
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
# iOS Foreground Notifications Fix
2+
3+
## Problem
4+
5+
iOS push notifications were completely broken after removing the `expo-notifications` plugin. Issues included:
6+
1. Notifications not displaying in foreground (app responded with 0 to `willPresentNotification`)
7+
2. Notification taps not working (no `didReceive` response handler)
8+
3. **Push notifications not working at all** (missing `aps-environment` entitlement)
9+
10+
Console logs showed:
11+
- Notification received: `hasAlertContent: 1, hasSound: 1 hasBadge: 1`
12+
- App responded with 0 to `willPresentNotification`
13+
- iOS decided not to show: `shouldPresentAlert: NO`
14+
- No handler for notification taps (`didReceive` response)
15+
16+
## Root Cause
17+
18+
**Issue 1: Missing APS Entitlement (Critical)**
19+
When `expo-notifications` plugin was removed, it also removed the `aps-environment` entitlement from the iOS project. This entitlement is **required** for iOS to register the app with Apple Push Notification service (APNs). Without it, the app cannot receive any push notifications at all.
20+
21+
**Issue 2: Notifications not displaying in foreground**
22+
When a push notification arrives on iOS while the app is in the foreground, iOS sends a `willPresentNotification` delegate call asking the app how to present the notification. Without a proper delegate implementation, the default behavior is to NOT show the notification (response 0).
23+
24+
**Issue 3: Notification taps not working**
25+
When a user taps on a notification, iOS sends a `didReceive response` delegate call. Without implementing this delegate method, taps are ignored and don't trigger any action in the app.
26+
27+
The previous implementation tried to manually display notifications using Notifee, but this happened AFTER Firebase Messaging had already told iOS not to show the notification.
28+
29+
## Solution
30+
31+
### 1. Config Plugin (`plugins/withForegroundNotifications.js`)
32+
33+
Created an Expo config plugin to automatically configure push notifications during prebuild:
34+
35+
```javascript
36+
const { withAppDelegate, withEntitlementsPlist } = require('@expo/config-plugins');
37+
38+
const withForegroundNotifications = (config) => {
39+
// Add push notification entitlements
40+
config = withEntitlementsPlist(config, (config) => {
41+
const entitlements = config.modResults;
42+
43+
// Add APS environment for push notifications - REQUIRED
44+
entitlements['aps-environment'] = 'production';
45+
46+
// Add critical alerts for production/internal builds
47+
const env = process.env.APP_ENV || config.extra?.APP_ENV;
48+
if (env === 'production' || env === 'internal') {
49+
entitlements['com.apple.developer.usernotifications.critical-alerts'] = true;
50+
entitlements['com.apple.developer.usernotifications.time-sensitive'] = true;
51+
}
52+
53+
return config;
54+
});
55+
56+
// Add AppDelegate modifications for notification handling
57+
// ...
58+
};
59+
60+
module.exports = withForegroundNotifications;
61+
```
62+
63+
This plugin:
64+
1. **Adds `aps-environment` entitlement** - Required for APNs registration
65+
2. **Adds critical alerts entitlement** - For emergency call notifications
66+
3. **Adds time-sensitive entitlement** - For high-priority notifications
67+
4. Adds AppDelegate notification handlers (see below)
68+
69+
Added to `app.config.ts` plugins array:
70+
```typescript
71+
plugins: [
72+
// ...
73+
'./plugins/withForegroundNotifications.js',
74+
// ...
75+
]
76+
```
77+
78+
This ensures the native iOS code is correctly configured even after running `expo prebuild`.
79+
80+
### 2. AppDelegate.swift Changes
81+
82+
The config plugin automatically applies these changes during prebuild:
83+
84+
```swift
85+
import UserNotifications
86+
87+
public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate {
88+
89+
public override func application(...) -> Bool {
90+
// ...
91+
92+
// Set the UNUserNotificationCenter delegate to handle foreground notifications
93+
UNUserNotificationCenter.current().delegate = self
94+
95+
// ...
96+
}
97+
98+
// Handle foreground notifications - tell iOS to show them
99+
public func userNotificationCenter(
100+
_ center: UNUserNotificationCenter,
101+
willPresent notification: UNNotification,
102+
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
103+
) {
104+
// Show notification with alert, sound, and badge even when app is in foreground
105+
if #available(iOS 14.0, *) {
106+
completionHandler([.banner, .sound, .badge])
107+
} else {
108+
completionHandler([.alert, .sound, .badge])
109+
}
110+
}
111+
112+
// Handle notification tap - when user taps on a notification
113+
public func userNotificationCenter(
114+
_ center: UNUserNotificationCenter,
115+
didReceive response: UNNotificationResponse,
116+
withCompletionHandler completionHandler: @escaping () -> Void
117+
) {
118+
// Forward the notification response to React Native
119+
// This allows Firebase Messaging to handle it via onNotificationOpenedApp
120+
completionHandler()
121+
}
122+
}
123+
```
124+
125+
This tells iOS to:
126+
1. Display all foreground notifications with banner/alert, sound, and badge updates
127+
2. Forward notification taps to React Native for handling
128+
129+
### 3. push-notification.ts Changes
130+
131+
Added Notifee event listeners to handle notification taps:
132+
133+
**Added:**
134+
```typescript
135+
import { EventType } from '@notifee/react-native';
136+
137+
// In initialize():
138+
// Set up Notifee event listeners for notification taps
139+
notifee.onForegroundEvent(async ({ type, detail }) => {
140+
if (type === EventType.PRESS && detail.notification) {
141+
const eventCode = detail.notification.data?.eventCode;
142+
if (eventCode) {
143+
usePushNotificationModalStore.getState().showNotificationModal({
144+
eventCode,
145+
title: detail.notification.title,
146+
body: detail.notification.body,
147+
data: detail.notification.data,
148+
});
149+
}
150+
}
151+
});
152+
153+
notifee.onBackgroundEvent(async ({ type, detail }) => {
154+
if (type === EventType.PRESS && detail.notification) {
155+
// Handle background notification taps
156+
// ...
157+
}
158+
});
159+
```
160+
161+
This ensures that:
162+
1. When user taps a notification shown via Notifee, it's caught by `onForegroundEvent`
163+
2. When user taps a notification while app is in background, it's caught by `onBackgroundEvent`
164+
3. Both handlers extract the eventCode and show the in-app notification modal
165+
166+
Also kept the existing Firebase Messaging handlers:
167+
168+
**Before:**
169+
```typescript
170+
// On iOS, display the notification in foreground using Notifee
171+
if (Platform.OS === 'ios' && remoteMessage.notification) {
172+
await notifee.displayNotification({...});
173+
}
174+
```
175+
176+
**After:**
177+
```typescript
178+
// On iOS, the notification will be displayed automatically by the native system
179+
// via the UNUserNotificationCenterDelegate in AppDelegate.swift
180+
// We don't need to manually display it here
181+
```
182+
183+
The `handleRemoteMessage` function now only:
184+
1. Logs the received message
185+
2. Extracts eventCode and notification data
186+
3. Shows the notification modal if eventCode exists
187+
4. Lets iOS handle the notification display natively
188+
189+
The existing Firebase Messaging handlers (`onNotificationOpenedApp`, `getInitialNotification`) continue to work for notifications tapped from the system tray.
190+
191+
## Flow After Fix
192+
193+
### Notification Display Flow
194+
1. **Notification arrives** → Firebase Messaging receives it
195+
2. **iOS asks** → "How should I present this?" (willPresentNotification)
196+
3. **AppDelegate responds** → "Show with banner, sound, and badge" ([.banner, .sound, .badge])
197+
4. **iOS displays** → Native notification appears at the top of the screen
198+
5. **React Native processes** → `onMessage` handler extracts eventCode for modal
199+
200+
### Notification Tap Flow
201+
1. **User taps notification** → iOS receives the tap
202+
2. **iOS asks** → "How should I handle this?" (didReceive response)
203+
3. **AppDelegate responds** → Forwards to React Native
204+
4. **Two paths handled**:
205+
- **Path A (Notifee)**: If notification was displayed by Notifee → `onForegroundEvent` fires → Shows modal
206+
- **Path B (Firebase)**: If notification is from system tray → `onNotificationOpenedApp` fires → Shows modal
207+
208+
## Benefits
209+
210+
1. **Native behavior**: Notifications look and feel native
211+
2. **Proper sounds**: Custom notification sounds work correctly
212+
3. **Critical alerts**: Can leverage iOS critical alert features
213+
4. **Better UX**: Consistent with iOS notification standards
214+
5. **Less code**: Removed manual display logic
215+
216+
## Testing
217+
218+
Test foreground notifications with:
219+
1. App in foreground
220+
2. Send push notification with eventCode
221+
3. **Verify notification banner appears at top** ✅
222+
4. **Verify sound plays** ✅
223+
5. **Tap the notification banner** ✅
224+
6. **Verify modal shows for eventCode** ✅
225+
7. Test with different notification types (calls, messages, etc.)
226+
227+
Test background/killed state notifications:
228+
1. App in background or killed
229+
2. Send push notification with eventCode
230+
3. **Tap the notification from system tray** ✅
231+
4. **Verify app opens and modal shows** ✅
232+
233+
## Related Files
234+
235+
- `/plugins/withForegroundNotifications.js` - Expo config plugin for iOS modifications
236+
- `/app.config.ts` - Expo configuration with plugin registration
237+
- `/ios/ResgridUnit/AppDelegate.swift` - Native iOS delegate implementation (auto-generated)
238+
- `/src/services/push-notification.ts` - React Native notification service
239+
240+
## Important Notes
241+
242+
- The `AppDelegate.swift` is auto-generated during `expo prebuild`
243+
- Never manually edit `AppDelegate.swift` - changes will be lost on next prebuild
244+
- All iOS native modifications must be done through the config plugin
245+
- Run `expo prebuild --platform ios --clean` after modifying the plugin

0 commit comments

Comments
 (0)