react-native-notify-kit 9.7.0 → 10.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +140 -6
- package/RNNotifeeCore.podspec +2 -0
- package/android/src/androidTest/java/app/notifee/core/RebootRecoveryTest.java +94 -2
- package/android/src/main/AndroidManifest.xml +8 -7
- package/android/src/main/java/app/notifee/core/NotificationManager.java +2 -0
- package/android/src/main/java/app/notifee/core/model/NotificationAndroidModel.java +8 -3
- package/android/src/main/java/app/notifee/core/utility/ResourceUtils.java +28 -0
- package/android/src/test/java/app/notifee/core/model/NotificationAndroidModelSmallIconFallbackTest.java +74 -0
- package/android/src/test/java/app/notifee/core/utility/ResourceUtilsFallbackIconTest.java +116 -0
- package/cli/bin/react-native-notify-kit +2 -0
- package/cli/dist/cli.bundle.js +20834 -0
- package/cli/dist/cli.d.ts +2 -0
- package/cli/dist/cli.js +87 -0
- package/cli/dist/cli.js.map +1 -0
- package/cli/dist/commands/initNse.d.ts +12 -0
- package/cli/dist/commands/initNse.js +212 -0
- package/cli/dist/commands/initNse.js.map +1 -0
- package/cli/dist/lib/detectProject.d.ts +15 -0
- package/cli/dist/lib/detectProject.js +156 -0
- package/cli/dist/lib/detectProject.js.map +1 -0
- package/cli/dist/lib/logger.d.ts +4 -0
- package/cli/dist/lib/logger.js +24 -0
- package/cli/dist/lib/logger.js.map +1 -0
- package/cli/dist/lib/patchPodfile.d.ts +12 -0
- package/cli/dist/lib/patchPodfile.js +143 -0
- package/cli/dist/lib/patchPodfile.js.map +1 -0
- package/cli/dist/lib/patchXcodeProject.d.ts +25 -0
- package/cli/dist/lib/patchXcodeProject.js +239 -0
- package/cli/dist/lib/patchXcodeProject.js.map +1 -0
- package/cli/dist/lib/writeTemplates.d.ts +11 -0
- package/cli/dist/lib/writeTemplates.js +82 -0
- package/cli/dist/lib/writeTemplates.js.map +1 -0
- package/cli/dist/templates/Info.plist.tmpl +29 -0
- package/cli/dist/templates/NotificationService.swift.tmpl +32 -0
- package/cli/dist/templates/NotifyKitNSE.entitlements.tmpl +6 -0
- package/cli/dist/templates/templates/Info.plist.tmpl +29 -0
- package/cli/dist/templates/templates/NotificationService.swift.tmpl +73 -0
- package/cli/dist/templates/templates/NotifyKitNSE.entitlements.tmpl +6 -0
- package/dist/NotifeeApiModule.d.ts +17 -0
- package/dist/NotifeeApiModule.js +66 -1
- package/dist/NotifeeApiModule.js.map +1 -1
- package/dist/fcm/index.d.ts +4 -0
- package/dist/fcm/index.js +3 -0
- package/dist/fcm/index.js.map +1 -0
- package/dist/fcm/parseFcmPayload.d.ts +16 -0
- package/dist/fcm/parseFcmPayload.js +37 -0
- package/dist/fcm/parseFcmPayload.js.map +1 -0
- package/dist/fcm/reconstructNotification.d.ts +8 -0
- package/dist/fcm/reconstructNotification.js +167 -0
- package/dist/fcm/reconstructNotification.js.map +1 -0
- package/dist/fcm/types.d.ts +51 -0
- package/dist/fcm/types.js +6 -0
- package/dist/fcm/types.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/types/Module.d.ts +21 -0
- package/dist/validators/validateTrigger.js +16 -0
- package/dist/validators/validateTrigger.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +38 -3
- package/server/README.md +129 -0
- package/server/dist/android.d.ts +8 -0
- package/server/dist/android.d.ts.map +1 -0
- package/server/dist/android.js +20 -0
- package/server/dist/android.js.map +1 -0
- package/server/dist/buildPayload.d.ts +3 -0
- package/server/dist/buildPayload.d.ts.map +1 -0
- package/server/dist/buildPayload.js +82 -0
- package/server/dist/buildPayload.js.map +1 -0
- package/server/dist/index.d.ts +6 -0
- package/server/dist/index.d.ts.map +1 -0
- package/server/dist/index.js +12 -0
- package/server/dist/index.js.map +1 -0
- package/server/dist/ios.d.ts +11 -0
- package/server/dist/ios.d.ts.map +1 -0
- package/server/dist/ios.js +61 -0
- package/server/dist/ios.js.map +1 -0
- package/server/dist/serialize.d.ts +11 -0
- package/server/dist/serialize.d.ts.map +1 -0
- package/server/dist/serialize.js +32 -0
- package/server/dist/serialize.js.map +1 -0
- package/server/dist/types.d.ts +2 -0
- package/server/dist/types.d.ts.map +1 -0
- package/server/dist/types.js +3 -0
- package/server/dist/types.js.map +1 -0
- package/server/dist/validation.d.ts +3 -0
- package/server/dist/validation.d.ts.map +1 -0
- package/server/dist/validation.js +101 -0
- package/server/dist/validation.js.map +1 -0
- package/server/package.json +5 -0
- package/server/src/android.ts +27 -0
- package/server/src/buildPayload.ts +91 -0
- package/server/src/index.ts +12 -0
- package/server/src/ios.ts +88 -0
- package/server/src/serialize.ts +39 -0
- package/server/src/types.ts +24 -0
- package/server/src/validation.ts +134 -0
- package/server/tsconfig.json +25 -0
- package/src/NotifeeApiModule.ts +80 -1
- package/src/fcm/index.ts +4 -0
- package/src/fcm/parseFcmPayload.ts +56 -0
- package/src/fcm/reconstructNotification.ts +201 -0
- package/src/fcm/types.ts +57 -0
- package/src/index.ts +2 -0
- package/src/internal/fcmContract.d.ts +150 -0
- package/src/types/Module.ts +23 -0
- package/src/validators/validateTrigger.ts +24 -0
- package/src/version.ts +1 -1
package/README.md
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
|
+
<!-- markdownlint-disable MD033 MD041 -->
|
|
2
|
+
<p align="center">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/marcocrupi/react-native-notify-kit/main/docs/assets/logo.png" alt="react-native-notify-kit" width="160" />
|
|
4
|
+
</p>
|
|
5
|
+
|
|
1
6
|
# react-native-notify-kit
|
|
2
7
|
|
|
3
|
-
Maintained Notifee-compatible fork
|
|
8
|
+
Maintained Notifee-compatible fork: a feature-rich React Native notification library (Android & iOS).
|
|
4
9
|
|
|
5
|
-
<!-- markdownlint-disable MD033 -->
|
|
6
10
|
<p align="center">
|
|
7
11
|
<a href="https://www.npmjs.com/package/react-native-notify-kit"><img src="https://img.shields.io/npm/v/react-native-notify-kit.svg" alt="npm version"></a>
|
|
8
12
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue.svg" alt="License"></a>
|
|
9
13
|
<img src="https://img.shields.io/badge/platform-Android%20%7C%20iOS-green.svg" alt="Platform">
|
|
10
14
|
<img src="https://img.shields.io/badge/React%20Native-%3E%3D0.73-blue.svg" alt="React Native">
|
|
15
|
+
<a href="https://docs.page/marcocrupi/react-native-notify-kit/react-native/overview"><img src="https://img.shields.io/badge/docs-docs.page-blue.svg" alt="Documentation"></a>
|
|
11
16
|
</p>
|
|
12
17
|
|
|
13
18
|
<hr/>
|
|
14
19
|
|
|
20
|
+
> 📘 **Full documentation:** [docs.page/marcocrupi/react-native-notify-kit](https://docs.page/marcocrupi/react-native-notify-kit/react-native/overview) — public API reference, platform guides, FCM Mode, server SDK, and the `init-nse` CLI.
|
|
21
|
+
|
|
15
22
|
An actively maintained fork of Notifee for React Native notifications, continued and improved by Marco Crupi.
|
|
16
23
|
|
|
17
24
|
This repository preserves the original Notifee APIs and native core while continuing development for modern React Native releases.
|
|
@@ -135,10 +142,38 @@ useEffect(() => {
|
|
|
135
142
|
}, []);
|
|
136
143
|
```
|
|
137
144
|
|
|
145
|
+
> **Which handler fires when (iOS):**
|
|
146
|
+
>
|
|
147
|
+
> - Tap a notification while the app is **active in foreground** → `onForegroundEvent` receives `PRESS`.
|
|
148
|
+
> - Tap a notification while the app is **in background or killed** → `onBackgroundEvent` receives `PRESS`, even though iOS immediately brings the app to the foreground right after. At the moment iOS delivers the tap to the delegate, `UIApplication.applicationState` is `Inactive`, not `Active`, so the event is routed to the background handler.
|
|
149
|
+
> - Foreground delivery of a Notifee-owned notification → `onForegroundEvent` receives `DELIVERED`.
|
|
150
|
+
>
|
|
151
|
+
> Register **both** handlers if you need to react to taps in every app state. Resolves the confusion reported in upstream [invertase/notifee#1155](https://github.com/invertase/notifee/issues/1155).
|
|
152
|
+
|
|
153
|
+
## Notifee FCM Mode (NEW in 10.0.0)
|
|
154
|
+
|
|
155
|
+
**Use `react-native-notify-kit` as the sole FCM display layer on both Android and iOS** — one developer API, no duplicate notifications on Android, no silent-push drops on iOS. Ship a server SDK payload, let the client handle it in one line, and scaffold the iOS Notification Service Extension with a single CLI command:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# server: build the payload
|
|
159
|
+
import { buildNotifyKitPayload } from 'react-native-notify-kit/server';
|
|
160
|
+
await admin.messaging().send(buildNotifyKitPayload({ token, notification: { title, body, android, ios } }));
|
|
161
|
+
|
|
162
|
+
# client (Android + iOS): one line in setBackgroundMessageHandler / onMessage
|
|
163
|
+
await notifee.handleFcmMessage(remoteMessage);
|
|
164
|
+
|
|
165
|
+
# iOS NSE scaffold
|
|
166
|
+
npx react-native-notify-kit init-nse && cd ios && pod install
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
See the full guide: **[docs/fcm-mode.mdx](docs/fcm-mode.mdx)** — covers architecture, server SDK reference, client API, NSE setup, payload schema, migration, troubleshooting, and known limitations.
|
|
170
|
+
|
|
138
171
|
## Push Notifications (Firebase)
|
|
139
172
|
|
|
140
173
|
This library handles notification **display and management**. For receiving push notifications, pair it with [`@react-native-firebase/messaging`](https://rnfirebase.io/messaging/usage):
|
|
141
174
|
|
|
175
|
+
> **New in 10.0.0:** for a turnkey FCM integration that handles both platforms (no duplicate on Android, APNs-reliable on iOS), use **[Notifee FCM Mode](docs/fcm-mode.mdx)** instead of the manual pattern below. The sections that follow still apply for basic Firebase setup (google-services plugin, permissions, APNs capability).
|
|
176
|
+
|
|
142
177
|
### Android setup
|
|
143
178
|
|
|
144
179
|
1. Add Firebase dependencies to your app:
|
|
@@ -196,7 +231,22 @@ messaging().onMessage(async remoteMessage => {
|
|
|
196
231
|
|
|
197
232
|
## iOS Notification Service Extension
|
|
198
233
|
|
|
199
|
-
To modify push notification content before display (e.g., attach images), create a Notification Service Extension
|
|
234
|
+
To modify push notification content before display (e.g., attach images), create a Notification Service Extension.
|
|
235
|
+
|
|
236
|
+
### Automated setup (recommended)
|
|
237
|
+
|
|
238
|
+
From your project root, with `react-native-notify-kit` installed:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
npx react-native-notify-kit init-nse
|
|
242
|
+
cd ios && pod install
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
The CLI scaffolds a Swift NSE target (default name: `NotifyKitNSE`), patches your Podfile, and wires `.pbxproj`. Open your `.xcworkspace` in Xcode, verify the NSE target's signing, and build. See [docs/fcm-mode.mdx#ios-nse-setup](docs/fcm-mode.mdx#ios-nse-setup) for the full CLI reference (target name, bundle suffix, `--dry-run`, `--force`).
|
|
246
|
+
|
|
247
|
+
### Manual setup
|
|
248
|
+
|
|
249
|
+
For Expo, monorepos with non-standard iOS paths, or heavily-customized Xcode configurations where the CLI can't patch the project cleanly:
|
|
200
250
|
|
|
201
251
|
1. In Xcode: **File > New > Target > Notification Service Extension**
|
|
202
252
|
2. Add to your Podfile:
|
|
@@ -234,6 +284,40 @@ To modify push notification content before display (e.g., attach images), create
|
|
|
234
284
|
|
|
235
285
|
5. Run `cd ios && pod install`
|
|
236
286
|
|
|
287
|
+
## Server SDK
|
|
288
|
+
|
|
289
|
+
`react-native-notify-kit` ships a **zero-dependency server SDK** under the `/server` subpath, for building FCM HTTP v1 payloads that the client `handleFcmMessage` handler consumes. Runs in Node.js 22+ and Firebase Cloud Functions.
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
import { buildNotifyKitPayload } from 'react-native-notify-kit/server';
|
|
293
|
+
import * as admin from 'firebase-admin';
|
|
294
|
+
|
|
295
|
+
const message = buildNotifyKitPayload({
|
|
296
|
+
token: '<device FCM token>',
|
|
297
|
+
notification: {
|
|
298
|
+
id: 'order-42',
|
|
299
|
+
title: 'Your order is on the way',
|
|
300
|
+
body: 'Tap to see live tracking',
|
|
301
|
+
data: { orderId: '42' },
|
|
302
|
+
android: { channelId: 'orders', smallIcon: 'ic_notification' },
|
|
303
|
+
ios: { sound: 'default', interruptionLevel: 'timeSensitive' },
|
|
304
|
+
},
|
|
305
|
+
options: { androidPriority: 'high', ttl: 3600 },
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
await admin.messaging().send(message);
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Full reference: [docs/fcm-mode.mdx#server-sdk-reference](docs/fcm-mode.mdx#server-sdk-reference) (types, validation rules, payload shape, FCM 4 KB limit).
|
|
312
|
+
|
|
313
|
+
## CLI Tools
|
|
314
|
+
|
|
315
|
+
The library ships a small CLI at `npx react-native-notify-kit`. Currently one command is available:
|
|
316
|
+
|
|
317
|
+
- `npx react-native-notify-kit init-nse` — scaffolds an iOS Notification Service Extension (Swift), patches the Podfile, and wires `.pbxproj`. See [docs/fcm-mode.mdx#ios-nse-setup](docs/fcm-mode.mdx#ios-nse-setup) for options.
|
|
318
|
+
|
|
319
|
+
The CLI is prepacked into the main package at publish time, so `npx react-native-notify-kit` works immediately after `yarn add react-native-notify-kit` — no separate install.
|
|
320
|
+
|
|
237
321
|
## Jest Testing
|
|
238
322
|
|
|
239
323
|
Mock the native module in your Jest setup file:
|
|
@@ -263,7 +347,7 @@ This fork is a complete migration to React Native's **New Architecture**:
|
|
|
263
347
|
- **Toolchain**: Yarn 4, Node 22+, Java 17, compileSdk/targetSdk 35
|
|
264
348
|
- **Single Android module** — the original Notifee shipped a pre-compiled AAR bundled inside the npm tarball under a frozen Maven coordinate; this fork compiles the core from source as part of the React Native bridge module on every consumer build. Eliminates the `FAIL_ON_PROJECT_REPOS` issue on RN 0.74+ and the Gradle cache staleness bug that could serve outdated bytecode after `yarn upgrade`.
|
|
265
349
|
- **Core notification logic (NotifeeCore) is unchanged** — the public API is fully compatible with the original Notifee
|
|
266
|
-
- **
|
|
350
|
+
- **35 upstream bugs fixed** — see [Bugs Fixed from Upstream Notifee](#bugs-fixed-from-upstream-notifee) below
|
|
267
351
|
- **Reliable trigger notifications** — AlarmManager is the default backend instead of WorkManager, with automatic fallback when exact alarm permission is not granted
|
|
268
352
|
- **New API: `setNotificationConfig()`** — opt-out flag to prevent Notifee from intercepting iOS remote notification handlers (see [New APIs](#new-apis) below)
|
|
269
353
|
- **Baseline Profile** — the library AAR ships a Baseline Profile that instructs ART to AOT-compile the foreground service notification hot path at install time, eliminating JIT penalty on first invocation
|
|
@@ -293,7 +377,7 @@ This fork fixes the following bugs that were never resolved in the original Noti
|
|
|
293
377
|
| `!=` reference equality on String comparison in `NotificationPendingIntent` (latent — would activate when `getLaunchActivity()` returns a non-null value for `id=default`) | Android | Pre-existing (latent) | 9.1.19 |
|
|
294
378
|
| `pressAction.launchActivity` not defaulted at native layer when `pressAction.id === 'default'` | Android | N/A (defense-in-depth) | 9.1.19 |
|
|
295
379
|
| Duplicate symbols linker error when using NSE (`$NotifeeExtension = true`) with static frameworks — `NotifeeExtensionHelper` compiled by both `RNNotifee` and `RNNotifeeCore` pods | iOS | Pre-existing | 9.1.22 |
|
|
296
|
-
| `FAIL_ON_PROJECT_REPOS` rejection
|
|
380
|
+
| `Could not resolve app.notifee:core:+` / `FAIL_ON_PROJECT_REPOS` rejection — library injected a Maven repository into the consumer's `rootProject.allprojects` block, which broke on (a) RN 0.74+ with `dependencyResolutionManagement`, (b) Expo SDK 53/54 where `extraMavenRepos` is not propagated to subprojects, and (c) Gradle 8 dependency locking with legacy XML parsers | Android | [#1079](https://github.com/invertase/notifee/issues/1079), [#1226](https://github.com/invertase/notifee/issues/1226), [#1262](https://github.com/invertase/notifee/issues/1262) | 9.2.0 |
|
|
297
381
|
| Stale Gradle cache could serve outdated AAR bytecode after `yarn upgrade` — same Maven coordinate reused across releases violated Gradle's coordinate-immutability assumption | Android | N/A (architectural) | 9.2.0 |
|
|
298
382
|
| `EventType.DELIVERED` not emitted for `displayNotification()` in foreground (only for trigger notifications) — `notifeeTrigger != nil` guard in `willPresentNotification:` suppressed the event, breaking iOS/Android symmetry | iOS | Pre-existing | 9.3.0 |
|
|
299
383
|
| Tapping a notification without explicit `pressAction` does nothing (app doesn't open) — `NotificationPendingIntent.createIntent()` creates a tap-less PendingIntent when `pressActionModelBundle` is null, especially visible on trigger notifications after app kill | Android | Pre-existing (latent) | 9.3.0 |
|
|
@@ -305,8 +389,11 @@ This fork fixes the following bugs that were never resolved in the original Noti
|
|
|
305
389
|
| Notification Service Extension attachment downloads had no timeout cap (default 60-second `NSURLSession` timeout exceeds iOS's ~30-second NSE budget), causing extension process kill and notification loss on slow networks | iOS | Pre-existing | 9.4.0 |
|
|
306
390
|
| `cancelTriggerNotifications()` / `createTriggerNotification()` promises resolve before Room DB write completes, causing ~3% race on cancel-then-create patterns. Also fixes a previously-undocumented reboot-recovery data-loss bug in `NotifeeAlarmManager.rescheduleNotification` and an ordering bug in `NotificationManager.doScheduledWork` | Android | [#549](https://github.com/invertase/notifee/issues/549) | 9.5.0 |
|
|
307
391
|
| Scheduled trigger notifications silently lost across device reboot on OEM devices (Xiaomi MIUI, OnePlus, Huawei EMUI, Oppo ColorOS, Vivo FuntouchOS) whose vendor OS suppresses `BOOT_COMPLETED` until the user manually enables autostart. Also handles zombie non-repeating triggers whose fire time already passed (fire-once within a 24-hour grace period, then delete the Room row; delete silently beyond the grace period) and adds try/catch/finally guards to all notifee `BroadcastReceiver` async paths. | Android | [#734](https://github.com/invertase/notifee/issues/734) | 9.6.0 |
|
|
392
|
+
| `RepeatFrequency.DAILY` / `WEEKLY` triggers fire on day 1 but never on day 2+ (also reproduces with arrays of 24 daily reminders) — pre-fix: stale Room anchors after the post-fire repeat recalculation, plus DST ±1h shift on repeating triggers, plus reboot recovery silently dropping the rearmed PendingIntent on OEM devices that suppress `BOOT_COMPLETED` | Android | [#601](https://github.com/invertase/notifee/issues/601), [#1063](https://github.com/invertase/notifee/issues/1063) | 9.1.14 (DST + persist recalc) + 9.5.0 (await Room) + 9.6.0 (BOOT_COUNT cold-start, race guard) |
|
|
393
|
+
| Scheduled `TIMESTAMP` trigger notifications lost after device reboot on Android 14 emulator with battery optimizations off — pre-fix: `RebootBroadcastReceiver` dropped the goAsync handoff before Room writes completed; OEM devices that suppress `BOOT_COMPLETED` never re-armed the alarm at all | Android | [#991](https://github.com/invertase/notifee/issues/991) | 9.1.12 (goAsync in `RebootBroadcastReceiver`) + 9.5.0 (await Room in `rescheduleNotification`) + 9.6.0 (BOOT_COUNT cold-start for OEM suppressors) |
|
|
308
394
|
| `ObjectAlreadyConsumedException` in headless task when the same `WritableMap` is reused or the `taskConfig` accessor is read twice — `TaskConfig.init` mutated the caller's map instead of copying it first. Latent in most apps but observed in production by upstream users with high-frequency headless events | Android | [#266](https://github.com/invertase/notifee/issues/266) | 9.6.0 |
|
|
309
395
|
| `getDisplayedNotifications()` returned no `data` field on Android, breaking iOS/Android API symmetry where iOS exposes custom keys via `parseDataFromUserInfo:` (see platform limitation note below — the fix is API parity for app-posted notifications, not a workaround for FCM background auto-display) | Android | [#393](https://github.com/invertase/notifee/issues/393) | 9.7.0 |
|
|
396
|
+
| Small icon resolution failure in release builds causes `IllegalArgumentException` at `NotificationCompat.Builder.build()` — library now falls back to the app launcher icon and logs a warning instead of failing the notification display | Android | [#733](https://github.com/invertase/notifee/issues/733) | 10.1.0 |
|
|
310
397
|
|
|
311
398
|
> **Important note on `getDisplayedNotifications()` and FCM custom data on Android.**
|
|
312
399
|
>
|
|
@@ -363,6 +450,8 @@ In addition to bug fixes, the fork makes a few opinionated default changes vs `@
|
|
|
363
450
|
|
|
364
451
|
- **iOS `EventType.DELIVERED` now emitted for all foreground notifications** (since 9.3.0 — **BREAKING vs upstream**). Upstream Notifee had a guard in `willPresentNotification:` that suppressed DELIVERED for notifications created via `displayNotification()` (immediate display), emitting it only for trigger notifications. Android always emitted DELIVERED in both cases. The fork removes the guard so iOS matches Android. If you registered `onForegroundEvent` listeners that did heavy work on DELIVERED assuming the event would only fire for trigger notifications, audit them — you may now receive an event per `displayNotification()` call while in foreground. **Known limitation**: trigger notifications that fire while the app is in background or killed still do not emit DELIVERED on iOS — this is a platform limitation (`willPresentNotification:` is only invoked in foreground, and iOS provides no delegate callback for background-delivered triggers). If you need delivery confirmation for background trigger notifications on iOS, check the notification's presence via `getDisplayedNotifications()` after the app returns to foreground.
|
|
365
452
|
|
|
453
|
+
- **Failed `smallIcon` resolution falls back to the app launcher icon instead of failing the notification** (since 10.1.0). Previously, when the string in `android.smallIcon` did not resolve to a valid resource ID at runtime (asset only in `src/debug/res/`, R8 resource shrinking, naming mismatch), the library logged the failure at DEBUG level — invisible in release logcat — and skipped `setSmallIcon()`, causing `NotificationCompat.Builder.build()` to throw `IllegalArgumentException`. The library now resolves to the app's launcher icon as a fallback and logs a warning with the original icon name and likely causes. See the [Troubleshooting section](#small-icon-not-showing-in-android-release-builds-falls-back-to-launcher-icon) for diagnosis tips.
|
|
454
|
+
|
|
366
455
|
These changes are documented in the [CHANGELOG](CHANGELOG.md) under the release that introduced them. If you rely on any of the upstream defaults, you can either pin to the specific behavior via the opt-out flags listed above, or open an issue to discuss.
|
|
367
456
|
|
|
368
457
|
## Foreground Service Setup (Android 14+)
|
|
@@ -627,6 +716,26 @@ Default is `true` (backward compatible — Notifee handles everything, same as o
|
|
|
627
716
|
|
|
628
717
|
### Troubleshooting
|
|
629
718
|
|
|
719
|
+
#### Small icon not showing in Android release builds (falls back to launcher icon)
|
|
720
|
+
|
|
721
|
+
From 10.1.0, when the resource ID for `android.smallIcon` cannot be resolved at runtime, the library logs a warning and falls back to your app's launcher icon instead of failing the notification display. If you see your launcher icon in a notification where you expected a custom small icon, filter logcat for the `NOTIFEE` tag to find the resolution failure.
|
|
722
|
+
|
|
723
|
+
Three causes account for the vast majority of reports:
|
|
724
|
+
|
|
725
|
+
**Asset only in `src/debug/res/`.** When you add a drawable from Android Studio with the "Image Asset" wizard, the IDE sometimes places it under the `debug` variant only. The resource exists in debug builds but disappears in release. Copy or move the asset into `android/app/src/main/res/drawable-*/` (and the density buckets) so it participates in the release build.
|
|
726
|
+
|
|
727
|
+
**R8 / ProGuard resource shrinking.** Release builds with `shrinkResources true` in the app's `build.gradle` can strip drawables that R8 judges unreferenced from code. Declare the asset as keep in `android/app/src/main/res/raw/keep.xml`:
|
|
728
|
+
|
|
729
|
+
```xml
|
|
730
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
731
|
+
<resources xmlns:tools="http://schemas.android.com/tools"
|
|
732
|
+
tools:keep="@drawable/ic_notification" />
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
**Naming mismatch.** Android resource names are case-sensitive and only accept `[a-z0-9_]`. A string like `smallIcon: 'icNotification'` will not resolve `ic_notification.png`. Rename the `smallIcon` value to match the file on disk exactly.
|
|
736
|
+
|
|
737
|
+
For the full procedure on creating a small-icon asset via Android Studio, see [docs/react-native/android/appearance.mdx](docs/react-native/android/appearance.mdx).
|
|
738
|
+
|
|
630
739
|
#### Custom sounds for push notifications in background or killed state
|
|
631
740
|
|
|
632
741
|
If you've set `android.sound` and `ios.sound` in `displayNotification(...)` and the custom sound plays only when the app is in foreground, this is expected platform behavior — not a library bug. When a **remote push** (FCM/APNs) arrives while the app is killed, your JavaScript code never runs, so anything you configured client-side is ignored.
|
|
@@ -642,6 +751,31 @@ An advanced alternative on Android is to switch the backend to an FCM **data-onl
|
|
|
642
751
|
|
|
643
752
|
Reference: [invertase/notifee#927](https://github.com/invertase/notifee/issues/927).
|
|
644
753
|
|
|
754
|
+
#### Silent pushes and background fetch — handled by Firebase, not by this library
|
|
755
|
+
|
|
756
|
+
`react-native-notify-kit` hooks into `UNUserNotificationCenterDelegate` on iOS to display notifications and dispatch tap/delivery events to JS. It does **not** hook into `application:didReceiveRemoteNotification:fetchCompletionHandler:` — the iOS entry point for silent pushes (`content-available: 1` with no visible alert) and background fetch. Those paths belong to [`@react-native-firebase/messaging`](https://rnfirebase.io/messaging/usage) via `setBackgroundMessageHandler`.
|
|
757
|
+
|
|
758
|
+
If a silent push arrives while the app is killed and its only job is to trigger JS code (no notification displayed), subject to iOS's background budget it is handled entirely by Firebase's path — `notifee.onBackgroundEvent` will not fire, by design. If the silent push's JS handler then calls `notifee.displayNotification()`, the subsequent user tap on the displayed notification does flow through Notifee and fires `onBackgroundEvent` normally.
|
|
759
|
+
|
|
760
|
+
Relates to upstream [invertase/notifee#597](https://github.com/invertase/notifee/issues/597) for apps with slow startup. For remote notifications that should fire `onBackgroundEvent` reliably on iOS, use [FCM Mode](docs/fcm-mode.mdx) — see also the note on [#1133](https://github.com/invertase/notifee/issues/1133) in the FCM Mode guide.
|
|
761
|
+
|
|
762
|
+
#### `Could not resolve app.notifee:core:+` — does not apply to this fork
|
|
763
|
+
|
|
764
|
+
If you arrived here from a Google search for this error, note: the error is specific
|
|
765
|
+
to `@notifee/react-native` (the archived upstream package), where the native core was
|
|
766
|
+
distributed as a pre-compiled AAR inside a bundled Maven repo at
|
|
767
|
+
`node_modules/@notifee/react-native/android/libs/`. The fork eliminated that
|
|
768
|
+
distribution model in 9.2.0 — the core compiles from source as part of the bridge
|
|
769
|
+
module, so there is no Maven coordinate to resolve, no `extraMavenRepos` to configure
|
|
770
|
+
on Expo, and no `FAIL_ON_PROJECT_REPOS` conflict on RN 0.74+. Migrating from
|
|
771
|
+
`@notifee/react-native` to `react-native-notify-kit` removes the error with no
|
|
772
|
+
additional `build.gradle` patching, no config plugin, and no Expo `extraMavenRepos`
|
|
773
|
+
entry.
|
|
774
|
+
|
|
775
|
+
References: upstream issues [#1079](https://github.com/invertase/notifee/issues/1079),
|
|
776
|
+
[#1226](https://github.com/invertase/notifee/issues/1226),
|
|
777
|
+
[#1262](https://github.com/invertase/notifee/issues/1262).
|
|
778
|
+
|
|
645
779
|
### Manual warmup control
|
|
646
780
|
|
|
647
781
|
The library automatically pre-warms the foreground service notification path during app startup via `InitProvider`. **Most apps do not need to do anything extra.** However, in certain edge cases the automatic warmup may not be sufficient:
|
|
@@ -688,7 +822,7 @@ The script runs the macrobenchmark test in `apps/smoke/android/baselineprofile/`
|
|
|
688
822
|
|
|
689
823
|
## Documentation
|
|
690
824
|
|
|
691
|
-
The
|
|
825
|
+
The full `react-native-notify-kit` documentation is hosted on docs.page and is kept in sync with this repo. It covers the public API, platform guides, FCM Mode, the server SDK, and the `init-nse` CLI.
|
|
692
826
|
|
|
693
827
|
- [Overview](https://docs.page/marcocrupi/react-native-notify-kit/react-native/overview)
|
|
694
828
|
- [Reference](https://docs.page/marcocrupi/react-native-notify-kit/react-native/reference)
|
package/RNNotifeeCore.podspec
CHANGED
|
@@ -16,6 +16,8 @@ Pod::Spec.new do |s|
|
|
|
16
16
|
|
|
17
17
|
s.cocoapods_version = '>= 1.10.0'
|
|
18
18
|
s.ios.deployment_target = '15.1'
|
|
19
|
+
s.module_name = 'RNNotifeeCore'
|
|
20
|
+
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
|
|
19
21
|
|
|
20
22
|
if defined?($NotifeeCoreFromSources) && $NotifeeCoreFromSources == true
|
|
21
23
|
# internal dev flag used by Notifee devs, ignore
|
|
@@ -14,8 +14,8 @@ package app.notifee.core;
|
|
|
14
14
|
* NOT RUN IN CI.
|
|
15
15
|
*
|
|
16
16
|
* Instrumented regression suite for the reboot-recovery path in
|
|
17
|
-
* NotifeeAlarmManager.rescheduleNotifications, covering
|
|
18
|
-
* issues across
|
|
17
|
+
* NotifeeAlarmManager.rescheduleNotifications, covering four upstream
|
|
18
|
+
* issues across six test cases:
|
|
19
19
|
*
|
|
20
20
|
* 1. rescheduleNotifications_advancesEveryAnchorBeforeCompleting
|
|
21
21
|
* (upstream invertase/notifee#549 — reboot-recovery anchor persistence
|
|
@@ -54,6 +54,15 @@ package app.notifee.core;
|
|
|
54
54
|
* handleStaleNonRepeatingTrigger routes the failure through the
|
|
55
55
|
* resilient branch that still runs deleteById.
|
|
56
56
|
*
|
|
57
|
+
* 6. rescheduleNotifications_nonRepeatingFuture_preservesAnchorAndRearmsAlarm
|
|
58
|
+
* (upstream invertase/notifee#991 — non-repeating TIMESTAMP triggers lost
|
|
59
|
+
* after device reboot on Android 14). Seeds a non-repeating row 5 minutes
|
|
60
|
+
* in the future and asserts the row survives reboot recovery with its
|
|
61
|
+
* anchor preserved AND a fresh AlarmManager PendingIntent registered.
|
|
62
|
+
* Complements the past-stale paths above by covering the happy-path
|
|
63
|
+
* branch where setNextTimestamp early-returns at TimestampTriggerModel:136
|
|
64
|
+
* and rescheduleNotification re-arms the original anchor.
|
|
65
|
+
*
|
|
57
66
|
* Run manually via the smoke harness (recommended — wraps gradle + copies
|
|
58
67
|
* reports + fails loud if no XML reports are found):
|
|
59
68
|
*
|
|
@@ -133,6 +142,9 @@ public class RebootRecoveryTest {
|
|
|
133
142
|
/** 48 hours before "now" — well beyond the 24h grace period. */
|
|
134
143
|
private static final long STALE_BEYOND_GRACE_OFFSET_MS = 48 * HOUR_IN_MS;
|
|
135
144
|
|
|
145
|
+
/** 5 minutes after "now" — the future-non-repeating happy path for upstream #991. */
|
|
146
|
+
private static final long FUTURE_NON_REPEATING_OFFSET_MS = 5L * 60 * 1000;
|
|
147
|
+
|
|
136
148
|
/**
|
|
137
149
|
* Channel id used by the within-grace stale-trigger test fixture. Must match a real Android
|
|
138
150
|
* notification channel created in {@link #setUp()} so {@code
|
|
@@ -181,6 +193,7 @@ public class RebootRecoveryTest {
|
|
|
181
193
|
NotifeeAlarmManager.cancelNotification(staleWithinGraceTestId());
|
|
182
194
|
NotifeeAlarmManager.cancelNotification(staleBeyondGraceTestId());
|
|
183
195
|
NotifeeAlarmManager.cancelNotification(staleDisplayFailsTestId());
|
|
196
|
+
NotifeeAlarmManager.cancelNotification(nonRepeatingFutureTestId());
|
|
184
197
|
// Dismiss any notifications the within-grace tests may have posted so the device
|
|
185
198
|
// notification shade is left clean — the Room row is the test's contract, but the
|
|
186
199
|
// visible notification is a side effect we should not leave behind.
|
|
@@ -387,6 +400,81 @@ public class RebootRecoveryTest {
|
|
|
387
400
|
repo.getWorkDataById(id).get(5, TimeUnit.SECONDS) == null);
|
|
388
401
|
}
|
|
389
402
|
|
|
403
|
+
/**
|
|
404
|
+
* Regression test for upstream invertase/notifee#991 — non-repeating TIMESTAMP triggers whose
|
|
405
|
+
* fire time is still in the future at boot must be re-armed with their original anchor preserved.
|
|
406
|
+
*
|
|
407
|
+
* <p>The existing #734 tests cover the past-stale branches of {@code
|
|
408
|
+
* handleStaleNonRepeatingTrigger}; this fills the happy-path gap. Seeds a non-repeating trigger 5
|
|
409
|
+
* minutes in the future, invokes reboot recovery, and asserts that:
|
|
410
|
+
*
|
|
411
|
+
* <ul>
|
|
412
|
+
* <li>the Room row survives (not stale → not deleted),
|
|
413
|
+
* <li>the timestamp is unchanged (non-repeating → {@code setNextTimestamp} early-returns at
|
|
414
|
+
* {@code TimestampTriggerModel.java:136-139}),
|
|
415
|
+
* <li>an {@code AlarmManager} {@code PendingIntent} has been re-registered for the row, proving
|
|
416
|
+
* {@code rescheduleNotification} → {@code scheduleTimestampTriggerNotification} reached the
|
|
417
|
+
* alarm-set call.
|
|
418
|
+
* </ul>
|
|
419
|
+
*/
|
|
420
|
+
@Test
|
|
421
|
+
public void rescheduleNotifications_nonRepeatingFuture_preservesAnchorAndRearmsAlarm()
|
|
422
|
+
throws Exception {
|
|
423
|
+
long now = System.currentTimeMillis();
|
|
424
|
+
long futureAnchor = now + FUTURE_NON_REPEATING_OFFSET_MS;
|
|
425
|
+
String id = nonRepeatingFutureTestId();
|
|
426
|
+
|
|
427
|
+
repo.insert(buildNonRepeatingEntity(id, futureAnchor)).get(5, TimeUnit.SECONDS);
|
|
428
|
+
assertEquals(1, repo.getAll().get(5, TimeUnit.SECONDS).size());
|
|
429
|
+
|
|
430
|
+
new NotifeeAlarmManager().rescheduleNotifications(null);
|
|
431
|
+
|
|
432
|
+
PendingIntent pi = awaitPendingIntentRegistered(id);
|
|
433
|
+
assertNotNull(
|
|
434
|
+
"AlarmManager PendingIntent must be re-registered for the non-repeating future row " + id,
|
|
435
|
+
pi);
|
|
436
|
+
|
|
437
|
+
WorkDataEntity row = repo.getWorkDataById(id).get(5, TimeUnit.SECONDS);
|
|
438
|
+
assertNotNull("non-repeating future row must survive reboot recovery", row);
|
|
439
|
+
Bundle triggerBundle = ObjectUtils.bytesToBundle(row.getTrigger());
|
|
440
|
+
long preservedAnchor = ObjectUtils.getLong(triggerBundle.get("timestamp"));
|
|
441
|
+
assertEquals(
|
|
442
|
+
"non-repeating future trigger anchor must NOT be advanced — setNextTimestamp early-returns"
|
|
443
|
+
+ " when repeatFrequency is -1 and timestamp is in the future",
|
|
444
|
+
futureAnchor,
|
|
445
|
+
preservedAnchor);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Polls until an {@code AlarmManager} {@code PendingIntent} keyed off {@code id.hashCode()} is
|
|
450
|
+
* registered (FLAG_NO_CREATE returns non-null). Times out via {@link #fail(String)} if the
|
|
451
|
+
* reschedule pass never reaches {@code alarmManager.set*}.
|
|
452
|
+
*/
|
|
453
|
+
private PendingIntent awaitPendingIntentRegistered(String id) throws Exception {
|
|
454
|
+
Intent intent = new Intent(context, NotificationAlarmReceiver.class);
|
|
455
|
+
intent.putExtra("notificationId", id);
|
|
456
|
+
long deadline = System.currentTimeMillis() + POLL_DEADLINE_MS;
|
|
457
|
+
while (System.currentTimeMillis() < deadline) {
|
|
458
|
+
PendingIntent pi =
|
|
459
|
+
PendingIntent.getBroadcast(
|
|
460
|
+
context,
|
|
461
|
+
id.hashCode(),
|
|
462
|
+
intent,
|
|
463
|
+
PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_MUTABLE);
|
|
464
|
+
if (pi != null) {
|
|
465
|
+
return pi;
|
|
466
|
+
}
|
|
467
|
+
Thread.sleep(POLL_INTERVAL_MS);
|
|
468
|
+
}
|
|
469
|
+
fail(
|
|
470
|
+
"AlarmManager PendingIntent for "
|
|
471
|
+
+ id
|
|
472
|
+
+ " was not registered within "
|
|
473
|
+
+ POLL_DEADLINE_MS
|
|
474
|
+
+ "ms — the #991 fix in rescheduleNotification may be broken");
|
|
475
|
+
return null; // unreachable
|
|
476
|
+
}
|
|
477
|
+
|
|
390
478
|
/** Polls {@link WorkDataRepository#getWorkDataById(String)} until it returns {@code null}. */
|
|
391
479
|
private void awaitRowDeleted(String id) throws Exception {
|
|
392
480
|
long deadline = System.currentTimeMillis() + POLL_DEADLINE_MS;
|
|
@@ -566,4 +654,8 @@ public class RebootRecoveryTest {
|
|
|
566
654
|
private static String staleDisplayFailsTestId() {
|
|
567
655
|
return "reboot-recovery-stale-display-fails-test";
|
|
568
656
|
}
|
|
657
|
+
|
|
658
|
+
private static String nonRepeatingFutureTestId() {
|
|
659
|
+
return "reboot-recovery-non-repeating-future-test";
|
|
660
|
+
}
|
|
569
661
|
}
|
|
@@ -49,15 +49,16 @@
|
|
|
49
49
|
</intent-filter>
|
|
50
50
|
</receiver>
|
|
51
51
|
|
|
52
|
+
<!--
|
|
53
|
+
NotificationAlarmReceiver is invoked exclusively via PendingIntent broadcasts from
|
|
54
|
+
AlarmManager (NotifeeAlarmManager.scheduleTimestampTriggerNotification). It does not
|
|
55
|
+
handle any system-broadcast Intent action — onReceive dispatches intent.getExtras()
|
|
56
|
+
to NotifeeAlarmManager.displayScheduledNotification, which early-returns on null
|
|
57
|
+
extras. Reboot recovery is owned by RebootBroadcastReceiver above.
|
|
58
|
+
-->
|
|
52
59
|
<receiver
|
|
53
60
|
android:name="app.notifee.core.NotificationAlarmReceiver"
|
|
54
|
-
android:exported="false"
|
|
55
|
-
<intent-filter>
|
|
56
|
-
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
|
57
|
-
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
|
58
|
-
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
|
|
59
|
-
</intent-filter>
|
|
60
|
-
</receiver>
|
|
61
|
+
android:exported="false" />
|
|
61
62
|
|
|
62
63
|
<!-- Broadcast Receiver -->
|
|
63
64
|
<receiver
|
|
@@ -533,9 +533,14 @@ public class NotificationAndroidModel {
|
|
|
533
533
|
int smallIconId = ResourceUtils.getImageResourceId(rawIcon);
|
|
534
534
|
|
|
535
535
|
if (smallIconId == 0) {
|
|
536
|
-
Logger.
|
|
537
|
-
|
|
538
|
-
String.format(
|
|
536
|
+
Logger.w(
|
|
537
|
+
TAG,
|
|
538
|
+
String.format(
|
|
539
|
+
"Notification small icon '%s' could not be resolved; falling back to the app"
|
|
540
|
+
+ " launcher icon. Common causes: asset only in src/debug/res, R8 resource"
|
|
541
|
+
+ " shrinking, or name mismatch (names must be lowercase with underscores)."
|
|
542
|
+
+ " See README Troubleshooting.",
|
|
543
|
+
rawIcon));
|
|
539
544
|
return null;
|
|
540
545
|
}
|
|
541
546
|
|
|
@@ -18,6 +18,7 @@ package app.notifee.core.utility;
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import android.content.Context;
|
|
21
|
+
import android.content.pm.ApplicationInfo;
|
|
21
22
|
import android.graphics.Bitmap;
|
|
22
23
|
import android.graphics.Canvas;
|
|
23
24
|
import android.graphics.Color;
|
|
@@ -223,6 +224,33 @@ public class ResourceUtils {
|
|
|
223
224
|
return resourceId;
|
|
224
225
|
}
|
|
225
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Returns a resource id guaranteed to be usable as a smallIcon. Three-layer fallback: (1) {@code
|
|
229
|
+
* applicationInfo.icon}, (2) {@code applicationInfo.logo}, (3) the system {@code
|
|
230
|
+
* android.R.drawable.ic_dialog_info} drawable. Never returns 0, never throws.
|
|
231
|
+
*
|
|
232
|
+
* <p>Used by {@link app.notifee.core.NotificationManager} to keep a notification valid when the
|
|
233
|
+
* user-supplied {@code smallIcon} string cannot be resolved to a drawable at runtime.
|
|
234
|
+
*/
|
|
235
|
+
public static int getFallbackSmallIconId(Context context) {
|
|
236
|
+
try {
|
|
237
|
+
if (context != null) {
|
|
238
|
+
ApplicationInfo info = context.getApplicationInfo();
|
|
239
|
+
if (info != null) {
|
|
240
|
+
if (info.icon != 0) {
|
|
241
|
+
return info.icon;
|
|
242
|
+
}
|
|
243
|
+
if (info.logo != 0) {
|
|
244
|
+
return info.logo;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
} catch (Exception e) {
|
|
249
|
+
Logger.w(TAG, "getFallbackSmallIconId -> falling back to system default icon", e);
|
|
250
|
+
}
|
|
251
|
+
return android.R.drawable.ic_dialog_info;
|
|
252
|
+
}
|
|
253
|
+
|
|
226
254
|
/** Attempts to find a resource id by name and type */
|
|
227
255
|
private static int getResourceIdByName(String name, String type) {
|
|
228
256
|
if (name == null || name.isEmpty()) {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
package app.notifee.core.model;
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Copyright (c) 2016-present Invertase Limited & Contributors
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this library except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import static org.junit.Assert.assertNull;
|
|
20
|
+
|
|
21
|
+
import android.os.Bundle;
|
|
22
|
+
import app.notifee.core.ContextHolder;
|
|
23
|
+
import org.junit.Before;
|
|
24
|
+
import org.junit.Test;
|
|
25
|
+
import org.junit.runner.RunWith;
|
|
26
|
+
import org.robolectric.RobolectricTestRunner;
|
|
27
|
+
import org.robolectric.RuntimeEnvironment;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Unit coverage for {@link NotificationAndroidModel#getSmallIcon()}.
|
|
31
|
+
*
|
|
32
|
+
* <p>The method returns {@code null} when the smallIcon string cannot be resolved to a valid
|
|
33
|
+
* resource id. This is the trigger condition that {@link app.notifee.core.NotificationManager}
|
|
34
|
+
* translates into the launcher-icon fallback (upstream invertase/notifee#733).
|
|
35
|
+
*
|
|
36
|
+
* <p>The happy path (valid resource id returned) is covered end-to-end by smoke tests on real
|
|
37
|
+
* devices — unit-testing it here would require a real drawable registered against the test
|
|
38
|
+
* application package, which Robolectric does not stage by default.
|
|
39
|
+
*/
|
|
40
|
+
@RunWith(RobolectricTestRunner.class)
|
|
41
|
+
public class NotificationAndroidModelSmallIconFallbackTest {
|
|
42
|
+
|
|
43
|
+
@Before
|
|
44
|
+
public void setUp() {
|
|
45
|
+
// getSmallIcon() -> ResourceUtils.getImageResourceId() -> getResourceIdByName() reads
|
|
46
|
+
// ContextHolder.getApplicationContext() to call context.getResources().getIdentifier(...).
|
|
47
|
+
// Robolectric does not auto-populate ContextHolder, so we wire it up here. Same pattern
|
|
48
|
+
// used by ForegroundServiceTest and NotifeeAlarmManagerHandleStaleTest.
|
|
49
|
+
ContextHolder.setApplicationContext(RuntimeEnvironment.getApplication());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@Test
|
|
53
|
+
public void returnsNull_whenSmallIconKeyMissing() {
|
|
54
|
+
Bundle bundle = new Bundle();
|
|
55
|
+
NotificationAndroidModel model = NotificationAndroidModel.fromBundle(bundle);
|
|
56
|
+
assertNull(model.getSmallIcon());
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@Test
|
|
60
|
+
public void returnsNull_whenSmallIconCannotBeResolved() {
|
|
61
|
+
Bundle bundle = new Bundle();
|
|
62
|
+
bundle.putString("smallIcon", "nonexistent_icon_name_for_test");
|
|
63
|
+
NotificationAndroidModel model = NotificationAndroidModel.fromBundle(bundle);
|
|
64
|
+
assertNull(model.getSmallIcon());
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@Test
|
|
68
|
+
public void returnsNull_whenSmallIconEmptyString() {
|
|
69
|
+
Bundle bundle = new Bundle();
|
|
70
|
+
bundle.putString("smallIcon", "");
|
|
71
|
+
NotificationAndroidModel model = NotificationAndroidModel.fromBundle(bundle);
|
|
72
|
+
assertNull(model.getSmallIcon());
|
|
73
|
+
}
|
|
74
|
+
}
|