<!-- markdownlint-disable MD033 MD041 -->
<p align="center">
  <img src="https://raw.githubusercontent.com/marcocrupi/react-native-notify-kit/main/docs/assets/logo.png" alt="react-native-notify-kit" width="160" />
</p>

# react-native-notify-kit

Maintained Notifee-compatible fork: a feature-rich React Native notification library for Android, iOS, FCM Mode, and Expo CNG development builds.

<p align="center">
  <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>
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue.svg" alt="License"></a>
  <img src="https://img.shields.io/badge/platform-Android%20%7C%20iOS-green.svg" alt="Platform">
  <img src="https://img.shields.io/badge/React%20Native-%3E%3D0.73-blue.svg" alt="React Native">
  <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>
</p>

<hr/>

> 📘 **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, Expo CNG setup, server SDK, config plugin, and the `init-nse` CLI.

An actively maintained fork of Notifee for React Native notifications, continued and improved by Marco Crupi.

This repository preserves the original Notifee APIs and native core while continuing development for modern React Native releases.

## Why this fork

The original [Notifee](https://github.com/invertase/notifee) repository was **officially archived** by Invertase on April 7, 2026 (last release: v9.1.8, December 2024). The archived README recommends this fork (`react-native-notify-kit`) as a community-maintained drop-in replacement, alongside `expo-notifications`. Previously, in [issue #1254](https://github.com/invertase/notifee/issues/1254), the Invertase maintainer had already suggested migrating to `expo-notifications`.

However, `expo-notifications` does not cover several advanced capabilities that many production apps rely on:

- **Android foreground services** (ongoing notifications for background tasks)
- **Rich notification styles** (BigPicture, Messaging, Inbox)
- **Progress bar notifications**
- **Full-screen intent notifications** (alarm/call screens)
- **Ongoing / persistent notifications**

This fork fills the gap: it preserves all of Notifee's advanced features, migrates the bridge to React Native's **New Architecture** (TurboModules), and actively fixes the critical bugs left unresolved upstream. See the [bug fix table](#bugs-fixed-from-upstream-notifee) below. It also supports Expo CNG / prebuild development builds through an official config plugin. Expo Go is not supported.

## Project Status

<a href="https://github.com/marcocrupi/react-native-notify-kit/commits"><img src="https://img.shields.io/github/last-commit/marcocrupi/react-native-notify-kit.svg" alt="Last commit"></a>

- Officially recommended by Invertase as the community-maintained fork (April 2026)
- Maintained fork of Notifee — actively developed and published as `react-native-notify-kit`
- New Architecture only (TurboModules)
- Expo CNG / prebuild support for development builds. Expo Go is not supported.
- iOS FCM Mode Notification Service Extension automation via config plugin.
- Android foreground service manifest configuration via config plugin, with explicit opt-in for foreground service types.
- Android Expo FCM smoke validation using RNFirebase data-only messages and `notifee.handleFcmMessage`.
- Minimum supported React Native: `>=0.73`
- Development target: React Native `0.85.3`
- License: `Apache-2.0`
- Full changelog: [CHANGELOG.md](CHANGELOG.md)

The native core (NotifeeCore) is compiled from source as part of the bridge module (since 9.2.0) and the public API is **100% compatible** with the original `@notifee/react-native` — migration is a safe, drop-in replacement.

## Installation

```bash
yarn add react-native-notify-kit
# or
npm install react-native-notify-kit
```

For iOS, run `cd ios && pod install` after installing.

## Migration from @notifee/react-native

If you're coming from the original Notifee package, migrating takes just a few steps:

1. **Swap the package:**

   ```bash
   yarn remove @notifee/react-native
   yarn add react-native-notify-kit
   ```

2. **Update imports** — find and replace across your codebase:

   ```diff
   - import notifee from '@notifee/react-native';
   + import notifee from 'react-native-notify-kit';
   ```

   The default export is still called `notifee`, so your application code stays the same — only the import path changes.

3. **Reinstall pods** (iOS):

   ```bash
   cd ios && pod install
   ```

No native code changes are required. The public API is fully compatible with `@notifee/react-native`.

## Quick Start

```ts
import notifee, { AndroidImportance } from 'react-native-notify-kit';

// 1. Request permission (required on Android 13+ and iOS)
await notifee.requestPermission();

// 2. Create a channel (Android only, required for Android 8+)
await notifee.createChannel({
  id: 'default',
  name: 'Default Channel',
  importance: AndroidImportance.HIGH,
});

// 3. Display a notification
await notifee.displayNotification({
  title: 'Hello',
  body: 'This is a local notification',
  android: { channelId: 'default' },
});
```

> **Note:** The default export name `notifee` is kept intentionally for backward compatibility. If you're migrating from `@notifee/react-native`, a simple find-and-replace of the import path is all you need.

### 4. Handle events

In your `index.js` (before `AppRegistry.registerComponent`):

```ts
import notifee from 'react-native-notify-kit';

// Background/killed state events
notifee.onBackgroundEvent(async ({ type, detail }) => {
  console.log('Background event:', type, detail.notification?.id);
});
```

In your React component:

```ts
import { useEffect } from 'react';
import notifee, { EventType } from 'react-native-notify-kit';

useEffect(() => {
  return notifee.onForegroundEvent(({ type, detail }) => {
    if (type === EventType.PRESS) {
      console.log('Notification pressed:', detail.notification?.id);
    }
  });
}, []);
```

> **Android event reliability:** Registering `onForegroundEvent()` initializes the native event relay. Pending events received while the `ReactContext` is unavailable are buffered. If the context becomes unavailable after a flush takes its buffer snapshot, undelivered events are requeued so a later native event relay flush can deliver them without duplication in the validated recovery path, while preserving FIFO, the 10-event capacity, and drop-oldest behavior. No application code changes or warm-up calls such as `getDisplayedNotifications()` are required; continue registering foreground and background handlers at the recommended points shown above. This hardens the Android event path investigated in [react-native-notify-kit#47](https://github.com/marcocrupi/react-native-notify-kit/issues/47) and relates to the event reliability and buffering problem tracked in [invertase/notifee#1279](https://github.com/invertase/notifee/issues/1279); the scenarios are not necessarily identical.

<!-- Keep these callouts separate. -->

> **Which handler fires when (iOS):**
>
> - Tap a notification while the app is **active in foreground** → `onForegroundEvent` receives `PRESS`.
> - 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.
> - Foreground delivery of a Notifee-owned notification → `onForegroundEvent` receives `DELIVERED`.
>
> 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).

### Android notification numbers and launcher badges

For notifications displayed through `notifee.displayNotification()`, use `android.badgeCount` to associate a number with
an individual Android notification:

```ts
await notifee.createChannel({
  id: 'messages',
  name: 'Messages',
  badge: true,
});

await notifee.displayNotification({
  id: 'messages',
  title: 'New messages',
  body: 'You have 5 unread messages',
  android: {
    channelId: 'messages',
    badgeCount: 5,
  },
});
```

`android.badgeCount` belongs to that notification and maps conceptually to AndroidX
`NotificationCompat.Builder.setNumber(...)`. Display the notification again with the same `id` and a new `badgeCount`
to update or replace it. The notification number itself is not limited to API level 26.

On Android 8.0 (API level 26) and above, `badge: true` allows notifications in the channel to contribute to launcher
badging; it does not set a count. Launcher and OEM behavior determines whether a number, only a notification dot, or a
different aggregation is shown, so a numeric badge on the app icon is not guaranteed.

Unlike the global iOS `notifee.setBadgeCount()` API, Android has no equivalent standard public API independent of posted
notifications. `badgeCount: 0` neither cancels the posted notification nor provides a portable way to clear a global
Android badge. See [Android Appearance: Badges](https://docs.page/marcocrupi/react-native-notify-kit/react-native/android/appearance#badges)
for complete details.

## Notifee FCM Mode (NEW in 10.0.0, Expo CNG in 10.4.0)

**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 set up the iOS Notification Service Extension with the Expo config plugin or, for bare React Native, the `init-nse` CLI. On Android, FCM Mode remains data-only: RNFirebase receives the message and calls `notifee.handleFcmMessage`.

```bash
# server: build the payload
import { buildNotifyKitPayload } from 'react-native-notify-kit/server';
await admin.messaging().send(buildNotifyKitPayload({ token, notification: { title, body, android, ios } }));

# client (Android + iOS): one line in setBackgroundMessageHandler / onMessage
await notifee.handleFcmMessage(remoteMessage);

# iOS NSE setup for bare React Native
npx react-native-notify-kit init-nse && cd ios && pod install
```

Need to inspect or patch a reconstructed notification before display—for example, to apply your own Android BigPicture image? Use `notifee.buildFcmNotification(remoteMessage)`, modify the returned notification, then call `displayNotification()` yourself. Do not also call `handleFcmMessage` for that message. See the [FCM Mode guide](docs/fcm-mode.mdx) for the complete build-only flow and iOS display-policy considerations.

### Expo CNG / development builds

Expo CNG / development builds are supported. Expo Go is not supported because this library requires native modules and native notification targets/capabilities.

On iOS, add the config plugin to your Expo config and run prebuild; the plugin generates and wires the Notification Service Extension used by NotifyKit FCM Mode.

```ts
export default {
  expo: {
    name: 'MyApp',
    slug: 'my-app',
    ios: {
      bundleIdentifier: 'com.example.myapp',
    },
    plugins: [
      [
        'react-native-notify-kit',
        {
          ios: {
            notificationServiceExtension: true,
          },
        },
      ],
    ],
  },
};
```

On Android, FCM Mode stays data-only. Configure Firebase and RNFirebase in the app, create the Android channel used by your payloads, then call `notifee.handleFcmMessage(remoteMessage)` from RNFirebase `onMessage` and `setBackgroundMessageHandler`. The Expo smoke app validates foreground and background delivery with the `android-expo-smoke` scenario on a real device.

Android foreground service manifest configuration is also available through the NotifyKit config plugin, but it is explicit opt-in:

```ts
export default {
  expo: {
    plugins: [
      [
        'react-native-notify-kit',
        {
          android: {
            foregroundService: {
              types: ['shortService'],
            },
          },
        },
      ],
    ],
  },
};
```

The plugin does not configure Firebase, does not add `USE_EXACT_ALARM`, does not add `USE_FULL_SCREEN_INTENT`, and does not choose a foreground service type by default.

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.

## Push Notifications (Firebase)

This library handles notification **display and management**. For receiving push notifications, pair it with [`@react-native-firebase/messaging`](https://rnfirebase.io/messaging/usage):

Firebase setup remains the consumer app's responsibility. The NotifyKit Expo config plugin does not install or configure Firebase, does not copy `google-services.json`, and does not patch Gradle for Firebase.

> **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).

### Android setup

1. Add Firebase dependencies to your app:

   ```bash
   yarn add @react-native-firebase/app @react-native-firebase/messaging
   ```

2. Add the google-services plugin to `android/build.gradle`:

   ```gradle
   classpath("com.google.gms:google-services:4.4.2")
   ```

3. Apply the plugin in `android/app/build.gradle`:

   ```gradle
   apply plugin: "com.google.gms.google-services"
   ```

4. Download `google-services.json` from [Firebase Console](https://console.firebase.google.com/) and place it in `android/app/`.

5. Add `POST_NOTIFICATIONS` permission to `AndroidManifest.xml` (required for Android 13+):

   ```xml
   <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
   ```

### iOS setup

1. Download `GoogleService-Info.plist` from Firebase Console and add it to your Xcode project.

2. Enable **Push Notifications** capability in Xcode:
   - Select your target > **Signing & Capabilities** > **+ Capability** > **Push Notifications**

3. Enable **Background Modes** > **Remote notifications**:
   - Select your target > **Signing & Capabilities** > **+ Capability** > **Background Modes** > check **Remote notifications**

4. Configure APNs certificates or keys in Firebase Console > Project Settings > Cloud Messaging.

### Display a push notification

```ts
import messaging from '@react-native-firebase/messaging';
import notifee from 'react-native-notify-kit';

messaging().onMessage(async remoteMessage => {
  await notifee.displayNotification({
    title: remoteMessage.notification?.title,
    body: remoteMessage.notification?.body,
    android: { channelId: 'default' },
  });
});
```

## iOS Notification Service Extension

To modify push notification content before display (e.g., attach images), create a Notification Service Extension. FCM Mode supports two automated setup paths: Expo CNG uses the config plugin, while bare React Native uses the `init-nse` CLI.

### Expo CNG automated setup

Use this for Expo prebuild / development builds:

```ts
export default {
  expo: {
    name: 'MyApp',
    slug: 'my-app',
    ios: {
      bundleIdentifier: 'com.example.myapp',
    },
    plugins: [
      [
        'react-native-notify-kit',
        {
          ios: {
            notificationServiceExtension: true,
          },
        },
      ],
    ],
  },
};
```

Run Expo prebuild or an EAS development build after adding the plugin. The plugin adds the EAS `appExtensions` entry and generates the Swift service, `Info.plist`, entitlements, Xcode target, `.appex` embed phase, and Podfile target. Expo Go is not supported.

### Bare React Native automated setup

From your project root, with `react-native-notify-kit` installed:

```bash
npx react-native-notify-kit init-nse
cd ios && pod install
```

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. Expo users should use the config plugin instead of running `init-nse` against generated native folders. 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`).

### Manual setup

For bare React Native projects with non-standard iOS paths or heavily-customized Xcode configurations where automation cannot patch the project cleanly:

1. In Xcode: **File > New > Target > Notification Service Extension**
2. Add to your Podfile:

   ```ruby
   target 'YourNSETarget' do
     pod 'RNNotifeeCore', :path => '../node_modules/react-native-notify-kit'
   end
   ```

3. Use `NotifeeExtensionHelper` in your `NotificationService.m`:

   ```objc
   #import "NotifeeExtensionHelper.h"

   - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request
                      withContentHandler:(void (^)(UNNotificationContent *))contentHandler {
       self.contentHandler = contentHandler;
       self.bestAttemptContent = [request.content mutableCopy];
       [NotifeeExtensionHelper populateNotificationContent:request
                                               withContent:self.bestAttemptContent
                                        withContentHandler:contentHandler];
   }
   ```

4. Implement `serviceExtensionTimeWillExpire` as a safety net. Notification Service Extensions have a ~30-second time budget; if your notification includes a large image attachment and the download is slow, the extension may be terminated before the content handler is called. Deliver a best-effort notification in the expiration handler:

   ```objc
   - (void)serviceExtensionTimeWillExpire {
       // Deliver the notification with whatever content we have so far
       // (e.g., without the image attachment if the download didn't finish).
       self.contentHandler(self.bestAttemptContent);
   }
   ```

5. Run `cd ios && pod install`

## Server SDK

`react-native-notify-kit` ships a **zero-dependency server SDK** under the `/server` subpath for building Firebase Admin-compatible FCM messages that the client `handleFcmMessage` handler consumes. Firebase Admin serializes each message to the FCM HTTP v1 request format. The SDK runs in Node.js 22+ and Firebase Cloud Functions.

```ts
import { buildNotifyKitPayload } from 'react-native-notify-kit/server';
import * as admin from 'firebase-admin';

const message = buildNotifyKitPayload({
  token: '<device FCM token>',
  notification: {
    id: 'order-42',
    title: 'Your order is on the way',
    body: 'Tap to see live tracking',
    data: { orderId: '42' },
    android: { channelId: 'orders', smallIcon: 'ic_notification' },
    ios: { sound: 'default', interruptionLevel: 'timeSensitive' },
  },
  options: { androidPriority: 'high', ttl: 3600 },
});

await admin.messaging().send(message);
```

`options.ttl` is expressed in seconds. Pass the message directly to Firebase Admin without an adapter or a manual unit conversion.

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).

## CLI Tools

The library ships a small CLI at `npx react-native-notify-kit`. Currently one command is available:

- `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.

Expo CNG users should use the config plugin for NSE setup instead of the CLI.

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.

## Jest Testing

Mock the native module in your Jest setup file:

```js
// jest.setup.js
jest.mock('react-native-notify-kit', () => require('react-native-notify-kit/jest-mock'));
```

Add to your Jest config:

```js
setupFiles: ['<rootDir>/jest.setup.js'],
transformIgnorePatterns: [
  'node_modules/(?!(jest-)?react-native|@react-native|react-native-notify-kit)'
],
```

## What's Different from Notifee

This fork is a complete migration to React Native's **New Architecture**:

- **TurboModules only** — no legacy Bridge support (`NativeModules` replaced with `TurboModuleRegistry`)
- **Android bridge rewritten in Kotlin** (original was Java)
- **iOS bridge uses Objective-C++** with `NativeNotifeeModuleSpecJSI` TurboModule conformance
- **Minimum React Native >=0.73**, development target **0.85.3**
- **Toolchain**: Yarn 4, Node 22+, JDK 17+ for Android builds, library Android module compileSdk/targetSdk 35
- **Android JDK baseline**: JDK 17 and JDK 21 are the validated Android build baselines; newer JDKs are not blocked by NotifyKit but depend on the consumer Android toolchain
- **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`.
- **Expo CNG development builds** — iOS FCM Mode NSE automation and Android foreground service manifest configuration are available through the config plugin. Android Expo FCM smoke validation uses RNFirebase data-only messages plus `notifee.handleFcmMessage`.
- **Core notification logic (NotifeeCore) is unchanged** — the public API is fully compatible with the original Notifee
- **35 upstream bugs fixed** — see [Bugs Fixed from Upstream Notifee](#bugs-fixed-from-upstream-notifee) below
- **Reliable trigger notifications** — AlarmManager is the default backend instead of WorkManager, with automatic fallback when exact alarm permission is not granted
- **Custom repeat intervals for timestamp triggers** — `TimestampTrigger.repeatInterval` supports calendar-based recurrences such as every 2 days, every 2 weeks, or every 3 months from a selected start timestamp. On iOS, repeating timestamp triggers now use a bounded rolling schedule of one-shot local notifications instead of native repeating calendar triggers; this enables custom repeat intervals and start-date-respecting recurrence, but apps that relied on native iOS repeating triggers being scheduled indefinitely should review the iOS notes in the [Triggers guide](docs/react-native/triggers.mdx#custom-repeat-intervals).
- **New API: `setNotificationConfig()`** — opt-out flag to prevent Notifee from intercepting iOS remote notification handlers (see [New APIs](#new-apis) below)
- **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

## Bugs Fixed from Upstream Notifee

This fork fixes the following bugs that were never resolved in the original Notifee repository:

| Bug                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | Platform | Upstream Issue                                                                                                                                                                  | Fixed in                                                                                                                                           |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Notifee intercepts iOS remote notification tap handlers, breaking RNFB `onNotificationOpenedApp` / `getInitialNotification`                                                                                                                                                                                                                                                                                                                                                                                          | iOS      | [#912](https://github.com/invertase/notifee/issues/912)                                                                                                                         | 9.1.12                                                                                                                                             |
| `completionHandler` not called on notification dismiss                                                                                                                                                                                                                                                                                                                                                                                                                                                               | iOS      | Pre-existing                                                                                                                                                                    | 9.1.12                                                                                                                                             |
| `completionHandler` not called in `willPresentNotification` fallback                                                                                                                                                                                                                                                                                                                                                                                                                                                 | iOS      | Pre-existing                                                                                                                                                                    | 9.1.12                                                                                                                                             |
| `getInitialNotification()` returns `null` on cold start (deprecated `UIApplicationLaunchOptionsLocalNotificationKey` check)                                                                                                                                                                                                                                                                                                                                                                                          | iOS      | [#1128](https://github.com/invertase/notifee/issues/1128)                                                                                                                       | 9.1.12                                                                                                                                             |
| `willPresentNotification:` fallback silently drops foreground notifications when no original delegate is captured (returns `None` instead of platform defaults)                                                                                                                                                                                                                                                                                                                                                      | iOS      | Pre-existing (introduced by partial fix in v9.1.12)                                                                                                                             | 9.1.20                                                                                                                                             |
| All delivered notifications dismissed from Notification Center when the app is opened                                                                                                                                                                                                                                                                                                                                                                                                                                | iOS      | [#828](https://github.com/invertase/notifee/issues/828)                                                                                                                         | 9.1.20                                                                                                                                             |
| `getInitialNotification()` returns `null` without `pressAction` configured                                                                                                                                                                                                                                                                                                                                                                                                                                           | Android  | [#1128](https://github.com/invertase/notifee/issues/1128)                                                                                                                       | 9.1.12                                                                                                                                             |
| Foreground press events silently dropped when React instance not ready                                                                                                                                                                                                                                                                                                                                                                                                                                               | Android  | [#1279](https://github.com/invertase/notifee/issues/1279)                                                                                                                       | 9.1.12                                                                                                                                             |
| Trigger notifications not firing on Android 14-15 when app is killed (missing `goAsync()` in `BroadcastReceiver`)                                                                                                                                                                                                                                                                                                                                                                                                    | Android  | [#1100](https://github.com/invertase/notifee/issues/1100)                                                                                                                       | 9.1.12                                                                                                                                             |
| `SCHEDULE_EXACT_ALARM` denial silently drops scheduled alarms (no fallback)                                                                                                                                                                                                                                                                                                                                                                                                                                          | Android  | [#1100](https://github.com/invertase/notifee/issues/1100)                                                                                                                       | 9.1.12                                                                                                                                             |
| `getNotificationSettings()` returns `DENIED` instead of `NOT_DETERMINED` on Android 13+ before permission requested                                                                                                                                                                                                                                                                                                                                                                                                  | Android  | [#1237](https://github.com/invertase/notifee/issues/1237)                                                                                                                       | 9.1.12                                                                                                                                             |
| Default `AlarmType.SET_EXACT` doesn't work in Doze mode; `AlarmType.SET` uses `RTC` instead of `RTC_WAKEUP`                                                                                                                                                                                                                                                                                                                                                                                                          | Android  | [#961](https://github.com/invertase/notifee/issues/961)                                                                                                                         | 9.1.12                                                                                                                                             |
| Foreground service crashes with ANR after ~3 min on Android 14+ (`shortService` timeout, missing `onTimeout()`)                                                                                                                                                                                                                                                                                                                                                                                                      | Android  | [#703](https://github.com/invertase/notifee/issues/703), [#1107](https://github.com/invertase/notifee/issues/1107)                                                              | 9.1.13                                                                                                                                             |
| Manifest merger failure when overriding `foregroundServiceType` on `ForegroundService`                                                                                                                                                                                                                                                                                                                                                                                                                               | Android  | [#1108](https://github.com/invertase/notifee/issues/1108)                                                                                                                       | 9.1.13                                                                                                                                             |
| Foreground service notifications dismissible on Android 13+ even with `ongoing: true` (library doesn't auto-set `ongoing` for foreground services)                                                                                                                                                                                                                                                                                                                                                                   | Android  | [#1248](https://github.com/invertase/notifee/issues/1248)                                                                                                                       | 9.1.14                                                                                                                                             |
| DST (daylight saving time) shifts repeating scheduled notifications by ±1 hour                                                                                                                                                                                                                                                                                                                                                                                                                                       | Android  | [#875](https://github.com/invertase/notifee/issues/875)                                                                                                                         | 9.1.14                                                                                                                                             |
| `!=` 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                                                                                                                                             |
| `pressAction.launchActivity` not defaulted at native layer when `pressAction.id === 'default'`                                                                                                                                                                                                                                                                                                                                                                                                                       | Android  | N/A (defense-in-depth)                                                                                                                                                          | 9.1.19                                                                                                                                             |
| 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                                                                                                                                             |
| `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                                                                                                                                              |
| 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                                                                                                                                              |
| `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                                                                                                                                              |
| 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), [#291](https://github.com/invertase/notifee/issues/291)                                                                                                  | 9.3.0                                                                                                                                              |
| Foreground service notifications delayed up to 10 seconds on Android 12+ — library never calls `setForegroundServiceBehavior(FOREGROUND_SERVICE_IMMEDIATE)`                                                                                                                                                                                                                                                                                                                                                          | Android  | [#272](https://github.com/invertase/notifee/issues/272), [#1242](https://github.com/invertase/notifee/issues/1242)                                                              | 9.4.0                                                                                                                                              |
| `didReceiveNotificationResponse:` completionHandler delayed by 15 seconds via `dispatch_after`, blocking subsequent notification taps and risking handler leaks if the app is suspended during the wait                                                                                                                                                                                                                                                                                                              | iOS      | Pre-existing (TODO since 2020)                                                                                                                                                  | 9.4.0                                                                                                                                              |
| `requestPermission:` silently swallows `NSError` from `requestAuthorizationWithOptions`, making MDM and parental-control authorization failures invisible to JS consumers                                                                                                                                                                                                                                                                                                                                            | iOS      | Pre-existing (TODO since day 1)                                                                                                                                                 | 9.4.0                                                                                                                                              |
| `contentByUpdatingWithProvider:` errors suppressed via `nil` error pointer in `displayNotification:` and `createTriggerNotification:` — communication notifications with malformed SiriKit intents silently fail with nil content                                                                                                                                                                                                                                                                                    | iOS      | Pre-existing                                                                                                                                                                    | 9.4.0                                                                                                                                              |
| `getBadgeCount:` completion block never called when running in an app extension, causing JS promises to hang forever in NSE handlers                                                                                                                                                                                                                                                                                                                                                                                 | iOS      | Pre-existing                                                                                                                                                                    | 9.4.0                                                                                                                                              |
| 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                                                                                                                                              |
| `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                                                                                                                                              |
| 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                                                                                                                                              |
| `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)                                                     |
| 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) |
| `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                                                                                                                                              |
| `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                                                                                                                                              |
| 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                                                                                                                                             |

> **Important note on `getDisplayedNotifications()` and FCM custom data on Android.**
>
> The 9.7.0 fix for [#393](https://github.com/invertase/notifee/issues/393) makes `getDisplayedNotifications()` expose a `data` field for notifications stored in `Notification.extras` (matching iOS shape). However, when the FCM Android SDK auto-displays a `notification`+`data` push while the app is in background or killed, custom `data` fields are placed only on the tap-action `PendingIntent` — never on `Notification.extras`. This is the FCM SDK's original design (see `CommonNotificationBuilder.createContentIntent` in [firebase-android-sdk](https://github.com/firebase/firebase-android-sdk/blob/master/firebase-messaging/src/main/java/com/google/firebase/messaging/CommonNotificationBuilder.java)) and is not workaroundable from any library — the PendingIntent's extras are sealed by Android's security model. Firebase tracks this gap in [firebase-android-sdk#2639](https://github.com/firebase/firebase-android-sdk/issues/2639) (open since 2021).
>
> **The fix does work** for these scenarios on Android: notifications created via `notifee.displayNotification({ data: {...} })`, FCM data-only messages handled in `onMessageReceived` followed by an explicit `displayNotification()` call, custom `FirebaseMessagingService.handleIntent` overrides that inject extras before display, and notifications from other libraries that call `NotificationCompat.Builder.addExtras(bundle)`.
>
> **Recommended pattern for full control over FCM push notifications on Android**: send FCM data-only messages (omit the `notification` field server-side), handle them in `onMessageReceived` (or a headless task in killed state), and render the notification yourself via `notifee.displayNotification()`. This gives full control over `data`, channel, styling, tap behavior, and is also Firebase's [official recommendation](https://firebase.blog/posts/2018/09/handle-fcm-messages-on-android/).
>
> **Reserved keys filtered from `data`** (custom payload keys matching these are dropped): prefixes `android.`, `google.`, `gcm.`, `fcm.` (with trailing dot — `fcmRegion`, `googleish` survive), `notifee` (no trailing dot — library's reserved namespace, `notifeeFoo` is also filtered), plus exact keys `from`, `collapse_key`, `message_type`, `message_id`, `aps`, `fcm_options`. The `fcm_options` exact-match matches iOS behavior on the Firebase analytics-label key. **Cross-platform note**: bare-`fcm` keys other than `fcm_options` (e.g. `fcmRegion`, `fcmToken`) are preserved on Android but filtered on iOS — rename server-side if you need strict parity.

<!-- Keep these callouts separate. -->

> **Note for policy-eligible apps requiring exact alarm APIs (alarm clocks, timers, calendars):**
> Add `<uses-permission android:name="android.permission.USE_EXACT_ALARM" />` to your app's
> `AndroidManifest.xml`. This permission is auto-granted and not revocable, but Google Play
> restricts its use to apps whose core function requires exact timing.
> For all other apps, the library uses `SCHEDULE_EXACT_ALARM` with automatic fallback
> to inexact alarms when the permission is not granted.

As bugs are fixed, this table is updated. See [CHANGELOG.md](CHANGELOG.md) for full details.

## Documented Workarounds for Platform Limitations

Some upstream Notifee issues are not bugs in the library itself but platform-level limitations imposed by Android's Doze mode and vendor power management — no library code can make `AlarmManager` deliver an alarm to, or a foreground service survive inside, an app the OEM has explicitly paused. For these, the fork provides **documented mitigations**: user-facing helper APIs, code-level self-healing where possible, and decision guides that steer consumers toward the Android primitive most resilient to the specific vendor policy.

| Upstream issue                                                           | Symptom                                                                                                                                                                                                                                                                                                                                                                                | Platform root cause                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | Fork mitigation                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [invertase/notifee#410](https://github.com/invertase/notifee/issues/410) | Foreground service paused on screen lock (Samsung OneUI, ~6 seconds after screen off on battery) and killed immediately when the app is backgrounded (Xiaomi MIUI)                                                                                                                                                                                                                     | Vendor aggressive battery-saver and autostart policies suspend or terminate foreground services of apps not whitelisted in the OEM's protected-apps / autostart settings. Partially Doze-related on non-exempt `foregroundServiceType` values; mostly OEM-specific behavior catalogued at [dontkillmyapp.com](https://dontkillmyapp.com/).                                                                                                                                                                                                                                                                                                            | **(1) Decision guide** — the [Timers: foreground service or `SET_ALARM_CLOCK`?](#timers-foreground-service-or-set_alarm_clock) section recommends the `SET_ALARM_CLOCK` trigger over a silent foreground service for rest, cooking, and recovery timer use cases. `setAlarmClock` is the same primitive the stock Clock app uses and is generally respected by vendor aggressive-kill policies. **(2) Foreground service use case matrix** — the [Foreground service use case guide](#foreground-service-use-case-guide) documents which `foregroundServiceType` values are Doze-CPU-exempt, which have type-specific timeouts, and the Google Play policy constraints that rule out misusing `mediaPlayback` for silent timers. **(3) `openPowerManagerSettings()` helper API** — points users toward known vendor autostart / protected-apps screen candidates when available; these links are best-effort because firmware variants may move, block, or remove those settings activities. |
| [invertase/notifee#734](https://github.com/invertase/notifee/issues/734) | Scheduled trigger notifications silently lost across a device reboot on OEM devices (Xiaomi MIUI, OnePlus, Huawei EMUI, Oppo ColorOS, Vivo FuntouchOS)                                                                                                                                                                                                                                 | The vendor OS suppresses the `BOOT_COMPLETED` broadcast to apps the user has not manually whitelisted, so the library's `RebootBroadcastReceiver` never runs and persisted `AlarmManager` triggers are never re-armed after reboot.                                                                                                                                                                                                                                                                                                                                                                                                                   | **(1) `BOOT_COUNT` cold-start self-heal (code)** — on every app init, `InitProvider` compares `Settings.Global.BOOT_COUNT` against the last-known value in `SharedPreferences` and re-arms every persisted trigger on a background thread if a boot delta is detected, even when `BOOT_COMPLETED` was never delivered. Paired with a process-wide `AtomicBoolean` race guard in `NotifeeAlarmManager.rescheduleNotifications` that prevents double-advancement when the reboot receiver and the cold-start path race. **(2) `openPowerManagerSettings()` helper API** — the same vendor-settings deep-link used by #410, pointing the user at the autostart whitelist for defense in depth.                                                                                                                                                                                                                                                                                                  |
| [invertase/notifee#927](https://github.com/invertase/notifee/issues/927) | Custom sound passed via `displayNotification({ android: { sound, channelId }, ios: { sound } })` is ignored for **remote push notifications** (FCM/APNs) delivered while the app is in background or killed — the system default sound plays instead. Foreground delivery and **locally-scheduled notifications** (`displayNotification`, `createTriggerNotification`) are unaffected. | When a remote push arrives while the app is killed, the JavaScript layer never runs — the system tray item is drawn by the OS (Android system + Firebase SDK; iOS + APNs) before any Notifee code executes. On Android API 26+, the `NotificationChannel` sound is set once at channel creation and is immutable thereafter — `NotificationCompat.Builder.setSound()` is silently ignored when the builder has a `channelId`. On iOS, the Notification Service Extension only rewrites incoming push content when the payload contains a `notifee_options` key (see `NotifeeCoreExtensionHelper.m:43`); a plain APNs payload is delivered unmodified. | **Documentation only — the platform contract cannot be worked around at the library layer.** Recipes by platform: **(Android)** create the `NotificationChannel` with the desired sound at first-run (the channel sound is immutable; to change it the channel must be deleted and recreated under a new `channelId`), and configure `AndroidNotification.sound` in the FCM payload server-side so the system tray honors it for background pushes. As a heavier alternative, switch the backend to an FCM data-only payload and call `displayNotification()` from a headless task — the JS-side `android.sound` is then honored, but this trades simplicity for the cost of running JS on every push. **(iOS)** either set `aps.sound` directly in the APNs payload, or install the Notification Service Extension (see `docs/react-native/ios/remote-notification-support.mdx`) and ship the sound under `notifee_options.ios.sound` in the push payload.                                  |

Both mitigations are intentionally additive to the existing reboot-recovery and foreground-service code paths and do not replace the consumer's responsibility to prompt the user for battery-optimization exemption when the use case warrants it. For a complete vendor-by-vendor reference of autostart, battery-saver, and background-restriction behavior, see [dontkillmyapp.com](https://dontkillmyapp.com/).

## Behavior changes from upstream

In addition to bug fixes, the fork makes a few opinionated default changes vs `@notifee/react-native` to improve reliability and reduce footguns. These are intentional behavioral differences that you should be aware of when migrating:

- **Trigger notifications use AlarmManager by default** instead of WorkManager (since 9.1.12). WorkManager is battery-friendly but unreliable for time-sensitive notifications — Android may defer or drop WorkManager tasks based on Doze mode and OEM power management. Opt out per-trigger with `alarmManager: false` in the trigger config if you need battery-friendly scheduling where exact timing is not critical.

- **`AlarmType` defaults to `SET_EXACT_AND_ALLOW_WHILE_IDLE`** (since 9.1.12) instead of upstream's `SET_EXACT`, for better Doze mode compatibility.

- **`ongoing` defaults to `true` when `asForegroundService: true`** (since 9.1.14), preventing foreground service notifications from being dismissed by the user on Android 13+. This matches pre-Android 13 platform behavior. Override by setting `ongoing: false` explicitly.

- **Foreground service notifications dismissed on Android 14+ are auto re-posted** (since 9.1.14) while the service is still running. Android 14 ignores `FLAG_ONGOING_EVENT` for most foreground service types (except `mediaPlayback`, `phoneCall`, and enterprise DPC); the library detects the dismissal and immediately re-displays the notification.

- **`pressAction.launchActivity` defaults to `'default'` at the native layer when `pressAction.id === 'default'`** (since 9.1.19). The TypeScript validator already applied this default since upstream PR #141 (Sept 2020), but native code paths bypassing the validator (e.g., trigger notifications restored from the Room database after reboot, headless tasks) could miss it. The fork closes the gap at the native layer as defense-in-depth — eliminates an entire class of "tap doesn't open app" bugs in Android task management edge cases.

- **`pressAction` defaults to `{ id: 'default', launchActivity: 'default' }` when omitted from the notification payload** (since 9.3.0). Upstream Notifee required an explicit `pressAction` for tap-to-open behavior — without it, the notification displayed but tapping did nothing (only the internal transparent `NotificationReceiverActivity` would launch and finish). The fork injects the default at both the TypeScript validator layer and the native `NotificationManager` layer (defense-in-depth for code paths bypassing the validator, such as trigger notifications rehydrated from Room DB after app kill). Opt out with `pressAction: null` for intentionally non-tappable notifications.

- **Library no longer hardcodes `foregroundServiceType` in its manifest** (since 9.1.13 — **BREAKING vs upstream**). Apps using `asForegroundService: true` on Android 14+ must declare their own `foregroundServiceType` on `app.notifee.core.ForegroundService` in their app manifest. See [Foreground Service Setup](#foreground-service-setup-android-14) below for migration instructions. Upstream hardcoded `shortService`, which caused a manifest merger failure ([#1108](https://github.com/invertase/notifee/issues/1108)) and a 3-minute timeout ANR crash ([#703](https://github.com/invertase/notifee/issues/703)).

- **Foreground service notifications use `FOREGROUND_SERVICE_IMMEDIATE` by default** (since 9.4.0 — **BREAKING vs upstream**). Upstream Notifee never called `setForegroundServiceBehavior()`, causing Android 12+ to defer foreground service notification display by up to 10 seconds unless the notification qualified for a system exemption. The fork now sets `FOREGROUND_SERVICE_IMMEDIATE` by default when `asForegroundService: true`, eliminating the delay. Opt out per-notification with `foregroundServiceBehavior: AndroidForegroundServiceBehavior.DEFERRED`. Additionally, the library now pre-loads critical foreground service classes and Binder proxies on a background thread during app startup (`InitProvider.onCreate`), reducing first-display cold-start latency by ~50–100 ms. Opt out of the warmup via `<meta-data android:name="notifee_init_warmup_enabled" android:value="false" />` in your app's `AndroidManifest.xml`.

- **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.

- **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.

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.

## Foreground Service Setup (Android 14+)

Android 14 (API 34) requires all foreground services to declare an explicit `foregroundServiceType`. If you use `asForegroundService: true` in your notifications, add the following to your app's `AndroidManifest.xml`:

1. **Add the required permissions:**

   ```xml
   <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
   ```

   Some foreground service types require a matching `FOREGROUND_SERVICE_*` permission. `shortService` does not use a
   dedicated `FOREGROUND_SERVICE_SHORT_SERVICE` permission; keep `FOREGROUND_SERVICE`, declare the service type that
   matches your use case, and add any real type-specific permissions required by the Android platform.

2. **Declare the service type on Notifee's ForegroundService:**

   ```xml
   <application ...>
     <service
       android:name="app.notifee.core.ForegroundService"
       android:exported="false"
       android:foregroundServiceType="shortService" />
   </application>
   ```

Available types: `camera`, `connectedDevice`, `dataSync`, `health`, `location`, `mediaPlayback`, `mediaProjection`, `microphone`, `phoneCall`, `remoteMessaging`, `shortService`, `specialUse`, `systemExempted`. Choose the type that matches your use case — using the wrong type may cause Google Play policy violations.

> **Note:** `shortService` has a 3-minute timeout on Android 14+. If your foreground service needs to run longer, use a different type. The library's `onTimeout()` handler will gracefully stop the service if the timeout fires.

### Foreground service use case guide

Choosing the right `foregroundServiceType` matters — the wrong choice can cause Doze-driven CPU suspension with the screen off, Google Play policy rejection, or premature kills by the Android 14+ type-specific timeouts. This matrix maps common use cases to the recommended type and calls out the caveats you need to know before shipping:

| Use case                                     | Recommended type                                   | Doze CPU exempt? | Type timeout                      | Key caveat                                                                                                                        |
| -------------------------------------------- | -------------------------------------------------- | ---------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Silent rest / workout / cooking timer        | **`SET_ALARM_CLOCK` trigger — not an FGS**         | N/A              | N/A                               | See the ["Timers: foreground service or `SET_ALARM_CLOCK`?"](#timers-foreground-service-or-set_alarm_clock) decision guide below. |
| Timer with audio cue (metronome, guided set) | `mediaPlayback`                                    | Yes              | None                              | Must actually play audio — silent `mediaPlayback` is a Play Store policy violation.                                               |
| Short operation (< 3 min)                    | `shortService`                                     | No               | **3 min**                         | Library's `onTimeout()` stops cleanly and emits `TYPE_FG_TIMEOUT` to JS.                                                          |
| Long-running data sync                       | `dataSync`                                         | No               | 6 h (API 34); stricter on API 35+ | Pair with `openBatteryOptimizationSettings()` for reliability on OEM devices.                                                     |
| Location / navigation / fitness GPS          | `location`                                         | Yes              | None                              | Requires `ACCESS_FINE_LOCATION` runtime permission.                                                                               |
| Music / podcast / audiobook playback         | `mediaPlayback`                                    | Yes              | None                              | Must be real playback — see policy callout below.                                                                                 |
| Bluetooth / USB device sync                  | `connectedDevice`                                  | No               | None                              | Requires companion-device or Bluetooth permission.                                                                                |
| Enterprise / DPC / system-critical           | `specialUse` or `systemExempted`                   | Varies           | None                              | `specialUse` requires a `<property>` element and Play Store justification review.                                                 |
| Arbitrary deferrable background work         | **None — use `WorkManager` directly, not an FGS.** | N/A              | N/A                               | FGS is not the right abstraction for deferrable work.                                                                             |

> **Warning:** **`mediaPlayback` requires active audio playback.** [Google Play's Foreground Service Types policy](https://support.google.com/googleplay/android-developer/answer/13392821) explicitly prohibits declaring `mediaPlayback` for services that do not play audio. A silent timer, stopwatch, or rest-timer declared as `mediaPlayback` will be rejected during Play Store review. For silent long-running timers, prefer the `SET_ALARM_CLOCK` trigger path (see the decision guide below).

### Android 15+ additional FGS restrictions

Android 15 (API 35) tightens foreground service restrictions further:

- **`dataSync` cumulative 6-hour limit per 24-hour window.** Apps that previously started a fresh `dataSync` FGS repeatedly will hit the new cap.
- **`mediaProcessing` is a new dedicated type** for short media transcode / processing operations, with its own timeout.
- **`specialUse` requires a `<property>` element** on the `<service>` tag with `android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"` and a user-visible justification string. Play Store review uses this property to evaluate the declaration.
- **Type-specific timeouts fire `onTimeout(int startId, int fgsType)`.** This fork already implements the API 35+ overload — at timeout the service stops cleanly and the library emits `TYPE_FG_TIMEOUT` to JS with both `startId` and `fgsType` in the event payload.

If you target API 35+, audit your `foregroundServiceType` choice against the matrix above before shipping. The canonical reference is the [Android 15 foreground service behavior changes](https://developer.android.com/about/versions/15/behavior-changes-15#fgs-changes) documentation.

### OEM Background Restrictions

Some Android vendors (Xiaomi/Redmi MIUI, Huawei/Honor EMUI, Oppo/Realme ColorOS, Vivo/iQOO FuntouchOS and OriginOS, Samsung OneUI) apply aggressive autostart and battery-saver restrictions that affect **both scheduled trigger notifications and running foreground services**:

- **Trigger notifications** — the vendor OS suppresses the `BOOT_COMPLETED` broadcast to apps the user has not explicitly whitelisted, so `AlarmManager`-backed triggers never re-arm after a device reboot until the user opens the app.
- **Foreground services** — the same vendor policy pauses or terminates a running foreground service as soon as the app is backgrounded. Symptoms reported in [invertase/notifee#410](https://github.com/invertase/notifee/issues/410) include the service pausing after ~6 seconds with the screen off on Samsung OneUI on battery, and immediate kill on Xiaomi MIUI when the app moves to the background.

This is platform-level behavior imposed by the vendor — no library can make `AlarmManager` deliver an alarm to, or a foreground service survive inside, an app the OEM has explicitly paused.

The fork mitigates this with two layers that work together:

**1. Automatic cold-start recovery.** On every app init, the library compares `Settings.Global.BOOT_COUNT` against the value recorded on the previous run. If a reboot has occurred since the last run — whether or not `BOOT_COMPLETED` was delivered to your app — the library re-arms every persisted trigger on a background thread. This means that on an OEM device where `BOOT_COMPLETED` was suppressed, simply opening your app (or having it cold-started by any other entry point: push notification, geofence, share intent) recovers all missed and upcoming alarms. Previously, opening the app alone did not recover them. This recovery runs unconditionally — it is not gated by the `notifee_init_warmup_enabled` metadata flag, because it is a correctness fix rather than a startup optimization.

**2. Vendor settings helper APIs.** The existing `getPowerManagerInfo()` and `openPowerManagerSettings()` APIs let your app guide the user toward known vendor settings candidates (Xiaomi Autostart, Huawei Protected Apps, Oppo Startup Manager, and 13 more vendors) to whitelist the app. These candidates are opened best-effort: Android may reject or fail to resolve a vendor-specific settings activity on some firmware variants, and the helper fails safely instead of crashing. Consumer apps do not need to inherit package-visibility `<queries>` declarations for these helpers, and the helper does not use the direct `ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` request path. Whitelisting the app in vendor settings can restore normal reboot delivery and reduce background foreground-service kills on affected devices, but this remains subject to OEM firmware and user/device policy — so this helper is a mitigation path for **both** trigger-notification reliability and foreground-service reliability on OEM devices.

A typical integration that combines both layers looks like this:

```typescript
import notifee from 'react-native-notify-kit';
import { Alert, Platform } from 'react-native';

if (Platform.OS === 'android') {
  const info = await notifee.getPowerManagerInfo();
  if (info.activity) {
    // The user is on a device with a known vendor autostart activity.
    // Prompt them once (e.g. on first run, or after a scheduled notification
    // fails to fire on time), explaining why exact timing depends on this
    // permission.
    Alert.alert(
      'Allow background activity',
      'Your device restricts background apps by default. To reliably receive scheduled notifications, please enable autostart for this app.',
      [
        { text: 'Open settings', onPress: () => notifee.openPowerManagerSettings() },
        { text: 'Later', style: 'cancel' },
      ],
    );
  }
}
```

For the authoritative vendor-by-vendor matrix of autostart, battery optimization, and background-restriction behavior, see [dontkillmyapp.com](https://dontkillmyapp.com/).

> **Scope note:** the cold-start recovery path is best-effort. It runs as soon as Android invokes `InitProvider.onCreate` (before `Application.onCreate`), but may still be delayed by minutes or hours on a device where the user never opens your app after a reboot. For use cases that require guaranteed sub-second timing (alarm clocks, time-sensitive reminders, calendar events), also declare `USE_EXACT_ALARM` in your manifest (see the [note above](#bugs-fixed-from-upstream-notifee)) and prompt the user to whitelist your app via the vendor settings helper.
>
> **Defense in depth:** the cold-start `BOOT_COUNT` path and the traditional `RebootBroadcastReceiver` path both funnel into the same `NotifeeAlarmManager.rescheduleNotifications` entry point, which is guarded by a process-wide `AtomicBoolean` — whichever path runs first wins the reschedule cycle, and the second logs `Reschedule already in progress, skipping duplicate request` and exits cleanly. On real devices the two paths often _both_ fire, for a subtle reason observed during Step 6 smoke testing: when the system force-stops your app (during an install, crash recovery, or a `pm clear` from a QA tool) and then Android re-delivers `BOOT_COMPLETED` as soon as the package is launched again, the reboot receiver runs at the same time as `InitProvider.onCreate`'s cold-start check. You get both paths for free — proof of the race guard's design. On an OEM device that suppresses `BOOT_COMPLETED` outright, only the cold-start path runs. Either way the zombie re-fire loop is broken.

### Trigger Notification Reliability

This fork defaults to AlarmManager for trigger notifications on Android, instead of WorkManager.
This ensures scheduled notifications are delivered reliably even when the app is killed.

The original Notifee used WorkManager by default, which is battery-friendly but unreliable
for time-sensitive notifications — Android may defer or drop WorkManager tasks based on
battery optimization, Doze mode, and OEM power management.

This path also handles normal reboot recovery. Android clears `AlarmManager` registrations
during reboot, but NotifyKit persists local trigger notifications and re-arms them after boot.
Future one-shot `TriggerType.TIMESTAMP` triggers are supported by this recovery path; a trigger
does not need to be recurring or daily only to survive reboot, and push notifications are not
required for local scheduled trigger recovery. If exact-alarm access is missing, NotifyKit falls
back to an inexact alarm so the trigger is retained, but exact fire timing is not guaranteed.

If you need battery-friendly scheduling where exact timing is not critical (e.g., daily digest
notifications), you can opt out:

```typescript
await notifee.createTriggerNotification(notification, {
  type: TriggerType.TIMESTAMP,
  timestamp: Date.now() + 60000,
  alarmManager: false, // Uses WorkManager instead
});
```

#### AlarmType guide

When `alarmManager` is enabled (the default), the `alarmManager.type` field selects which
`android.app.AlarmManager` primitive is used to schedule the trigger. This fork supports all
five `AlarmType` values — including `SET_ALARM_CLOCK`, which upstream Notifee tracked in
[invertase/notifee#655](https://github.com/invertase/notifee/issues/655) and merged via
[#749](https://github.com/invertase/notifee/pull/749).

| AlarmType                        | Exact? | Wakes device? | Doze bypass? | Status bar icon | When to use                                                                    |
| -------------------------------- | ------ | ------------- | ------------ | --------------- | ------------------------------------------------------------------------------ |
| `SET`                            | No     | Yes           | No           | No              | Non-critical reminders that can slip by several minutes (daily digest).        |
| `SET_AND_ALLOW_WHILE_IDLE`       | No     | Yes           | Yes          | No              | Non-critical reminders that must still fire while the device is in Doze.       |
| `SET_EXACT`                      | Yes    | Yes           | No           | No              | Time-sensitive reminders when the app is reasonably sure not to be in Doze.    |
| `SET_EXACT_AND_ALLOW_WHILE_IDLE` | Yes    | Yes           | Yes          | No              | **Fork default.** Time-sensitive reminders that must fire even in Doze.        |
| `SET_ALARM_CLOCK`                | Yes    | Yes           | Yes          | **Yes**         | True alarm-clock / recovery-timer use cases — highest priority, OEM-resilient. |

`SET_ALARM_CLOCK` is the strongest Android guarantee available for a scheduled notification:

- **Status-bar alarm-clock icon.** The system renders the alarm-clock glyph in the status bar
  until the trigger fires, signalling to the user that an alarm is pending.
- **Least susceptible to OEM power management.** Vendor aggressive-kill policies (Xiaomi MIUI,
  Oppo ColorOS, Huawei EMUI, Vivo FuntouchOS — documented in the "OEM Background Restrictions"
  section and on [dontkillmyapp.com](https://dontkillmyapp.com/)) generally respect
  `setAlarmClock` even when they would otherwise drop `setExactAndAllowWhileIdle`. This is the
  same mechanism the stock Clock app uses.
- **Intended for the same reliability problem as [invertase/notifee#734](https://github.com/invertase/notifee/issues/734).**
  If your use case is a time-sensitive reminder, a rest-timer between gym sets, a cooking timer,
  or any recovery-timer scenario where a missed notification is user-visible damage, prefer
  `SET_ALARM_CLOCK` over the fork default.

```typescript
import notifee, { AlarmType, TriggerType } from 'react-native-notify-kit';

await notifee.createTriggerNotification(
  {
    title: 'Rest complete',
    body: 'Next set is ready.',
    android: { channelId: 'timers' },
  },
  {
    type: TriggerType.TIMESTAMP,
    timestamp: Date.now() + 90_000,
    alarmManager: {
      type: AlarmType.SET_ALARM_CLOCK,
    },
  },
);
```

**Exact-alarm permission on Android 12+.** `SET_EXACT`, `SET_EXACT_AND_ALLOW_WHILE_IDLE`,
and `SET_ALARM_CLOCK` all require exact-alarm access through `SCHEDULE_EXACT_ALARM` or
`USE_EXACT_ALARM`. This affects exact fire timing, not trigger persistence. If permission is
not granted, Notifee falls back to `setAndAllowWhileIdle` (inexact) instead of crashing or
dropping the trigger. Apps that require exact timing should check
`getNotificationSettings().android.alarm`, explain the permission, and guide users with
`openAlarmPermissionSettings()`. `SET_ALARM_CLOCK` can be considered for alarm-clock or
timer-like use cases within Android and Google Play policy constraints.

### Timers: foreground service or `SET_ALARM_CLOCK`?

A common question for this fork: **should a rest / cooking / recovery timer be a foreground service, or a scheduled trigger notification?** For most timer use cases the answer is the trigger path — and specifically `SET_ALARM_CLOCK`.

| Timer characteristic                                                    | Recommended approach                                                       |
| ----------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| Fires once at a known time, no live UI update while app is backgrounded | **`SET_ALARM_CLOCK` trigger** (see [AlarmType guide](#alarmtype-guide))    |
| Fires repeatedly at known intervals                                     | **`SET_ALARM_CLOCK` trigger** with app-side scheduling of the next cycle   |
| Needs a live ticking notification UI while app is backgrounded          | Foreground service (`mediaPlayback` if audio, otherwise reconsider the UX) |
| Streams audio, music, or guided voice                                   | Foreground service with `mediaPlayback`                                    |
| Continuous background work (location, Bluetooth)                        | Foreground service with the matching type from the matrix above            |

**Why `SET_ALARM_CLOCK` is usually the right choice for timers:**

- **OEM-resilient.** Vendor aggressive-kill policies (Xiaomi MIUI, Oppo ColorOS, Huawei EMUI, Vivo FuntouchOS, Samsung OneUI) generally respect `setAlarmClock` even when they drop `setExactAndAllowWhileIdle` and kill foreground services. This is the same primitive the stock Clock app uses.
- **No `foregroundServiceType` to pick.** You avoid the Doze / Play-policy / Android 15 timeout maze entirely.
- **No risk of Play Store rejection** for misusing `mediaPlayback` on a silent timer.
- **No wake lock to manage.** The library's foreground-service path does not acquire a wake lock on your behalf — under Doze on a non-exempt `foregroundServiceType`, the CPU can still suspend with the screen off. `SET_ALARM_CLOCK` wakes the device at fire time regardless of Doze state.

**When a foreground service _is_ the right choice:**

- You need the notification to tick every second while the app is backgrounded (metronome with audio, VoIP call, active GPS track). A `SET_ALARM_CLOCK` trigger fires once at the scheduled time, not continuously.
- You need actual audio playback — use `mediaPlayback`.
- You need continuous location updates — use `location`.

For the specific use case in [invertase/notifee#410](https://github.com/invertase/notifee/issues/410) (rest timer between workout sets, screen off, OEM device), `SET_ALARM_CLOCK` is the recommended path. Pair it with `openPowerManagerSettings()` for defense in depth on OEM devices — see the [OEM Background Restrictions](#oem-background-restrictions) section above.

### Android: `pressAction` defaults to opening the app on tap

On Android, `pressAction` now defaults to `{ id: 'default', launchActivity: 'default' }` when omitted from the notification payload. This means tapping a notification opens the app's main activity by default — matching iOS behavior and eliminating a common footgun where trigger notifications appeared to work but tapping them did nothing after an app kill.

You can still provide an explicit `pressAction` to customize tap behavior:

```typescript
await notifee.displayNotification({
  title: 'Hello',
  body: 'Tap to open',
  android: {
    channelId: 'default',
    pressAction: { id: 'default', launchActivity: 'default' }, // same as the default
  },
});
```

To create a non-tappable notification (e.g. purely informative notifications from a background service), pass `pressAction: null` explicitly:

```typescript
await notifee.displayNotification({
  title: 'Sync in progress',
  body: 'Uploading files...',
  android: {
    channelId: 'default',
    pressAction: null, // notification displays but tapping does nothing
  },
});
```

## New APIs

### Full-Screen Intent availability (Android)

Use `getNotificationSettings()` to check whether the app currently has access to use Full-Screen Intents:

```typescript
import notifee, { AndroidNotificationSetting } from 'react-native-notify-kit';

const settings = await notifee.getNotificationSettings();

if (settings.android.fullScreenIntent === AndroidNotificationSetting.DISABLED) {
  // Full-Screen Intent access is currently unavailable.
}
```

`AndroidNotificationSetting.ENABLED` reports Full-Screen Intent access; it does not guarantee that a particular
notification will be presented full-screen. See [Android Behaviour: Full-screen](https://docs.page/marcocrupi/react-native-notify-kit/react-native/android/behaviour#full-screen)
for the full requirements and presentation behavior.

### `setNotificationConfig` (iOS)

Controls whether Notifee intercepts remote (push) notification tap events on iOS.
When using React Native Firebase Messaging alongside Notifee, call this at app startup
to let Firebase handle remote notification taps:

```typescript
import notifee from 'react-native-notify-kit';

await notifee.setNotificationConfig({
  ios: { handleRemoteNotifications: false },
});
```

With `handleRemoteNotifications: false`:

- Remote notifications (FCM) → handled by Firebase Messaging (`onNotificationOpenedApp`, `getInitialNotification`)
- Local Notifee notifications → still handled by Notifee (unchanged)

Default is `true` (backward compatible — Notifee handles everything, same as original Notifee behavior).

## Advanced

### Troubleshooting

#### Scheduled Android trigger notifications do not fire after reboot

Android removes `AlarmManager` registrations during reboot. NotifyKit persists local trigger
notifications and re-arms them through `app.notifee.core.RebootBroadcastReceiver` after a
normal reboot, with a cold-start `BOOT_COUNT` recovery path for devices that delay or suppress
`BOOT_COMPLETED`. If triggers do not return after reboot, check the generated app rather than
only the source manifest:

- Verify the final merged manifest contains `android.permission.RECEIVE_BOOT_COMPLETED`,
  `android.permission.SCHEDULE_EXACT_ALARM`, `app.notifee.core.RebootBroadcastReceiver` with
  `android.intent.action.BOOT_COMPLETED`, `app.notifee.core.NotificationAlarmReceiver`, and
  `io.invertase.notifee.NotifeeInitProvider`.
- For Expo CNG/prebuild apps, these entries normally arrive from the NotifyKit library manifest
  through manifest merge. A custom boot-receiver config plugin is usually unnecessary and can
  break recovery if it replaces the receiver declaration or intent filters.
- Use package-specific logcat output. Do not rely only on `adb logcat -s NOTIFEE`; reboot
  recovery logs use native tags such as `RebootReceiver`, `NotifeeAlarmManager`, `InitProvider`,
  and `AlarmPermissionReceiver`, while Android broadcast logs include your real application ID.
- Compare `adb shell dumpsys alarm` before and after reboot, filtered by your real package name,
  to confirm the pending alarm disappears during reboot and is re-armed afterward.
- Check exact-alarm permission if timing is late. Without permission, NotifyKit falls back to an
  inexact alarm: the trigger is retained, but exact fire timing is not guaranteed.
- Check OEM autostart and background restrictions, especially on Xiaomi/Redmi, Oppo/Realme,
  Huawei/Honor, Vivo/iQOO, and Samsung devices.
- Validate with 1-5 future one-shot `TriggerType.TIMESTAMP` triggers before scaling up to large
  batches such as 50 active triggers.

#### Small icon not showing in Android release builds (falls back to launcher icon)

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.

Three causes account for the vast majority of reports:

**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.

**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`:

```xml
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@drawable/ic_notification" />
```

**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.

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).

#### Custom sounds for push notifications in background or killed state

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.

The fix is to set the sound in the push payload **server-side**:

- **Android (FCM)**: set `AndroidNotification.sound` in the FCM payload to the name of a sound file bundled in `android/app/src/main/res/raw/`. Make sure the `NotificationChannel` was created with the same sound — the channel sound is immutable after creation, so changing the sound requires creating a channel under a new `channelId`.
- **iOS (APNs)**: either set `aps.sound` directly to the name of a sound file bundled in your app, or — if you need richer rewriting (image attachments, dynamic content) — install the Notification Service Extension and ship the sound under `notifee_options.ios.sound` in the push payload. See [`docs/react-native/ios/remote-notification-support.mdx`](docs/react-native/ios/remote-notification-support.mdx).

An advanced alternative on Android is to switch the backend to an FCM **data-only** payload and call `notifee.displayNotification()` from a headless task — this lets the JS-side `android.sound` win, at the cost of running JS on every push. Most apps should prefer the server-side payload approach.

**Local notifications are different.** This limitation only affects **remote pushes** delivered by FCM/APNs while the app is killed. Notifications scheduled locally via `notifee.displayNotification()` or `notifee.createTriggerNotification()` — for example, a timer firing after the user closed the app — _do_ honor the JS-side `sound` parameter, because the library itself wakes up and presents the notification (via `AlarmManager` on Android or `UNUserNotificationCenter` on iOS). The usual platform rules still apply: on Android the `NotificationChannel` sound is immutable after creation and wins over the per-notification `sound`; on iOS the sound file must be bundled in the app (`.wav`/`.aiff`/`.caf`, under 30 seconds, in the main bundle). For reliable local timer notifications on OEM devices that aggressively kill background work, prefer `AlarmType.SET_ALARM_CLOCK` — see the [Timers: foreground service or `SET_ALARM_CLOCK`?](#timers-foreground-service-or-set_alarm_clock) section.

Reference: [invertase/notifee#927](https://github.com/invertase/notifee/issues/927).

#### Silent pushes and background fetch — handled by Firebase, not by this library

`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`.

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.

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.

#### `Could not resolve app.notifee:core:+` — does not apply to this fork

If you arrived here from a Google search for this error, note: the error is specific
to `@notifee/react-native` (the archived upstream package), where the native core was
distributed as a pre-compiled AAR inside a bundled Maven repo at
`node_modules/@notifee/react-native/android/libs/`. The fork eliminated that
distribution model in 9.2.0 — the core compiles from source as part of the bridge
module, so there is no Maven coordinate to resolve, no `extraMavenRepos` to configure
on Expo, and no `FAIL_ON_PROJECT_REPOS` conflict on RN 0.74+. Migrating from
`@notifee/react-native` to `react-native-notify-kit` removes the error with no
additional `build.gradle` patching, no Android config plugin for this Maven issue,
and no Expo `extraMavenRepos` entry.

References: upstream issues [#1079](https://github.com/invertase/notifee/issues/1079),
[#1226](https://github.com/invertase/notifee/issues/1226),
[#1262](https://github.com/invertase/notifee/issues/1262).

### Manual warmup control

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:

- **Lazy-loaded library** — if `react-native-notify-kit` is code-split or lazy-loaded, `InitProvider` runs but the TurboModule/JS bridge side isn't initialized yet.
- **Post-splash-screen warmup** — apps that want to defer warmup to after the splash screen instead of during `Application.onCreate()`.
- **Low-end devices** — rare cases where the `InitProvider` warmup hasn't finished by the time the user triggers the first notification.

For these cases, call `prewarmForegroundService()` at a moment of your choosing:

```typescript
import notifee from 'react-native-notify-kit';

// Call after splash screen, during onboarding, or before the user
// is likely to trigger a foreground service notification.
await notifee.prewarmForegroundService();
```

**Key facts:**

- **Idempotent** — safe to call multiple times; class loading after the first call is a no-op from ART's perspective.
- **iOS no-op** — resolves immediately on iOS (Android-only optimization).
- **Does NOT start a foreground service** — it only performs class loading and Binder proxy warming. No Google Play policy risk.
- **Best-effort** — internal failures are logged and swallowed; the promise always resolves.

To verify whether calling this method provides a measurable benefit for your app, capture a Perfetto trace with the `notifee:*` trace sections enabled and compare the `notifee:displayNotification` duration with and without the prewarm call.

### Regenerating the Baseline Profile

The library ships a Baseline Profile (`packages/react-native/android/src/main/baseline-prof.txt`) that instructs ART to AOT-compile the notification hot path at install time. This profile should be regenerated after significant changes to the notification display code path.

**Prerequisites:**

- A physical device connected via adb (Pixel 9 Pro XL with Android 16+ recommended) or a running emulator with API 33+
- The smoke app must be buildable (`yarn install` in the repo root)

**Command:**

```bash
bash scripts/generate-baseline-profile.sh
```

The script runs the macrobenchmark test in `apps/smoke/android/baselineprofile/`, captures the profile on the connected device, filters it to library-only rules, and copies it to the library's `src/main/baseline-prof.txt`. Review the generated file for unexpected entries, then commit it.

## Documentation

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.

- [Overview](https://docs.page/marcocrupi/react-native-notify-kit/react-native/overview)
- [Reference](https://docs.page/marcocrupi/react-native-notify-kit/react-native/reference)

### Android

The APIs for Android allow for creating rich, styled and highly interactive notifications. Below you'll find guides that cover the supported Android features.

| Topic                                                                                                                |                                                                                                                                   |
| -------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| [Appearance](https://docs.page/marcocrupi/react-native-notify-kit/react-native/android/appearance)                   | Change the appearance of a notification; icons, colors, visibility etc.                                                           |
| [Behaviour](https://docs.page/marcocrupi/react-native-notify-kit/react-native/android/behaviour)                     | Customize how a notification behaves when it is delivered to a device; sound, vibration, lights etc.                              |
| [Channels & Groups](https://docs.page/marcocrupi/react-native-notify-kit/react-native/android/channels)              | Organize your notifications into channels & groups to allow users to control how notifications are handled on their device.       |
| [Foreground Service](https://docs.page/marcocrupi/react-native-notify-kit/react-native/android/foreground-service)   | Long running background tasks can take advantage of an Android Foreground Service to display an on-going, prominent notification. |
| [Grouping & Sorting](https://docs.page/marcocrupi/react-native-notify-kit/react-native/android/grouping-and-sorting) | Group and sort related notifications in a single notification pane.                                                               |
| [Interaction](https://docs.page/marcocrupi/react-native-notify-kit/react-native/android/interaction)                 | Allow users to interact with your application directly from the notification, with actions.                                       |
| [Progress Indicators](https://docs.page/marcocrupi/react-native-notify-kit/react-native/android/progress-indicators) | Show users a progress indicator of an on-going background task, and learn how to keep it updated.                                 |
| [Styles](https://docs.page/marcocrupi/react-native-notify-kit/react-native/android/styles)                           | Style notifications to show richer content, such as expandable images/text, or message conversations.                             |
| [Timers](https://docs.page/marcocrupi/react-native-notify-kit/react-native/android/timers)                           | Display counting timers on your notification, useful for on-going tasks such as a phone call, or event time remaining.            |

### iOS

Below you'll find guides that cover the supported iOS features.

| Topic                                                                                                                            |                                                                                                    |
| -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| [Appearance](https://docs.page/marcocrupi/react-native-notify-kit/react-native/ios/appearance)                                   | Change how the notification is displayed to your users.                                            |
| [Badges](https://docs.page/marcocrupi/react-native-notify-kit/react-native/ios/badges)                                           | Manage the app icon badge count on iOS devices.                                                    |
| [Behaviour](https://docs.page/marcocrupi/react-native-notify-kit/react-native/ios/behaviour)                                     | Control how notifications behave when they are displayed on a device; sound, critical alerts, etc. |
| [Categories](https://docs.page/marcocrupi/react-native-notify-kit/react-native/ios/categories)                                   | Create & assign categories to notifications.                                                       |
| [Interaction](https://docs.page/marcocrupi/react-native-notify-kit/react-native/ios/interaction)                                 | Handle user interaction with your notifications.                                                   |
| [Permissions](https://docs.page/marcocrupi/react-native-notify-kit/react-native/ios/permissions)                                 | Request permission from your application users to display notifications.                           |
| [Remote Notification Support](https://docs.page/marcocrupi/react-native-notify-kit/react-native/ios/remote-notification-support) | Handle and display remote notifications with Notification Service Extension.                       |

## Trademark Notice

"Notifee" is a trademark of Invertase. This project is not affiliated with, endorsed by, or sponsored by Invertase. The name "Notifee" is used solely to describe the origin and compatibility of this fork, as permitted under nominative fair use.

## License

- See [LICENSE](/LICENSE). This fork remains licensed under Apache-2.0.

---

<p align="center">
  Originally built by Invertase. This fork is independently maintained by Marco Crupi.
</p>
