--- title: Events description: Handling notification and device events. --- Notification & device events are an important concept as they allow applications to react to events (e.g. a user pressing a displayed notification and changing the in-app screen). ## Listening for events Using Notify Kit it is possible to display notifications in various situations, for example via [Headless JS](https://facebook.github.io/react-native/docs/headless-js-android) tasks, directly from your application (e.g. Button press) or via 3rd party services such as [FCM](/react-native/integrations/fcm). This flexibility opens up a key challenge for React Native developers; handling events when the application is both active & killed. Notify Kit provides a simple API for handling both scenarios. ## Foreground Events An application is deemed in the "foreground" only under the following situation: - The device is unlocked, and the application is running & is in view (foreground). In any other situation a [Background Event](/react-native/events#background-events) is used instead. If the user has opened your application and switched to another task (e.g. opened another app, pressed the "home" button) but not closed/quit your application, it is still classed as being in the background. To handle foreground events, the [`useEffect`](https://reactjs.org/docs/hooks-effect.html) hook can be used with the [`onForegroundEvent`](/react-native/reference/onforegroundevent) method: ```jsx import { useEffect } from 'react'; import notifee, { EventType } from 'react-native-notify-kit'; function App() { // Subscribe to events useEffect(() => { return notifee.onForegroundEvent(({ type, detail }) => { switch (type) { case EventType.DISMISSED: console.log('User dismissed notification', detail.notification); break; case EventType.PRESS: console.log('User pressed notification', detail.notification); break; } }); }, []); } ``` > To view all event types, view the [EventType](/react-native/reference/eventtype) documentation. The foreground event handler runs inside of our React Native code, allowing you to update the application UI or perform asynchronous actions such as performing a HTTP request. To learn about handling user interaction with events, view the [Android Interaction](/react-native/android/interaction) documentation. > **iOS tap routing.** On iOS, a tap on a notification while the app is in **background or killed** is routed to `onBackgroundEvent` — not `onForegroundEvent` — even though iOS then brings the app to the foreground. At the moment iOS delivers the tap to the `UNUserNotificationCenter` delegate, `UIApplication.applicationState` is `Inactive` (not `Active`), so the routing is based on "app was not active when the tap happened". Register **both** handlers if you need to react to taps in every app state. See [iOS Interaction → Foreground vs background routing](/react-native/ios/interaction#foreground-vs-background-routing) for the native-side detail. Resolves the confusion reported in upstream [invertase/notifee#1155](https://github.com/invertase/notifee/issues/1155). ### Android event buffering and ReactContext transitions On Android, registering `onForegroundEvent()` initializes the native event relay, so no warm-up call to another native API is required. If an event arrives while the `ReactContext` is unavailable, the relay adds it to the pending event buffer. If the `ReactContext` becomes null or inactive after a flush has taken its snapshot, an event that was not emitted is requeued. This preserves the event for a later flush and prevents it from being permanently discarded. The next native event relay flush makes another delivery attempt. In the validated recovery path, the event is delivered exactly once, while the existing FIFO order, capacity of 10 pending events, and drop-oldest policy are preserved. Existing application code does not need to change; continue registering foreground and background handlers in the recommended locations. This hardens the Android event path investigated in [react-native-notify-kit#47](https://github.com/marcocrupi/react-native-notify-kit/issues/47) and is related to the event reliability and buffering problem described in upstream [invertase/notifee#1279](https://github.com/invertase/notifee/issues/1279); the scenarios are not necessarily identical. ### Unsubscribing The `onForegroundEvent` method returns a function which can be used to unsubscribe from future events: ```js const unsubscribe = notifee.onForegroundEvent(...); // Sometime later... unsubscribe(); ``` ## Background events An application is deemed in the "background" under the following situations: - The device is locked. - The application is running & is not in view (minimized). - The application is killed/quit. The [`onBackgroundEvent`](/react-native/reference/onbackgroundevent) method is used to register a callback handler which will be executed whenever a background event is sent. Executing the callback handler in the background requires device resources so it is important that any code is executed efficiently & quickly. > To run long running tasks on Android, view the [Foreground Service](/react-native/android/foreground-service) documentation. Background tasks run without React context, meaning you cannot update your application UI. You can however perform logic to update a remote database, update local device storage or even display/update a notification with Notify Kit! An example of a background event would be handling a "Mark as read" action by updating your database and cancelling the notification. Only a single background event handler can be registered. To register your handler, the [`onBackgroundEvent`](/react-native/reference/onbackgroundevent) method should be registered as early on in your project as possible (e.g. the `index.js` file): ```js // index.js import { AppRegistry } from 'react-native'; import notifee, { EventType } from 'react-native-notify-kit'; import App from './App'; notifee.onBackgroundEvent(async ({ type, detail }) => { const { notification, pressAction } = detail; // Check if the user pressed the "Mark as read" action if (type === EventType.ACTION_PRESS && pressAction.id === 'mark-as-read') { // Update external API await fetch(`https://my-api.com/chat/${notification.data.chatId}/read`, { method: 'POST', }); // Remove the notification await notifee.cancelNotification(notification.id); } }); // Register main application AppRegistry.registerComponent('app', () => App); ``` The handler callback expects that once the task has completed a Promise is returned/resolved. The above example makes use of an [`async function`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) which returns an implicit Promise once complete. If you are not using `async`, ensure your handler callback returns a promise once complete. > **iOS delivery events.** `DELIVERED` is emitted to `onForegroundEvent` for **all** Notify Kit-owned notifications presented while the app is in foreground — immediate `displayNotification` calls, trigger notifications that fire in foreground, and `handleFcmMessage` foreground displays. Trigger notifications that fire while the app is in background or killed do **not** emit `DELIVERED` on iOS (platform limitation — `willPresentNotification:` only fires in foreground and iOS provides no equivalent callback for background-presented local notifications). Android emits `DELIVERED` unconditionally in both cases. If you need delivery confirmation for background trigger notifications on iOS, query `getDisplayedNotifications()` after the app returns to foreground. ## Android foreground service timeouts `EventType.FG_TIMEOUT = 9` is Android-only and is attempted best-effort by the native foreground service timeout path. Its `EventDetail` can include `notification` when available, optional `startId`, and optional `fgsType`; the API 34 single-parameter timeout callback can report `fgsType = -1`. Use the foreground listener while the app is in view and the single background handler registered early in `index.js` for background/headless delivery, as described above. A background handler must return a Promise and cannot update the UI. JavaScript delivery, including exactly-once delivery, is not guaranteed if the process is suspended or terminated. The event does not acknowledge Android service teardown, guarantee cancellation of JavaScript work, or authorize a new START. See [Handling FG_TIMEOUT](/react-native/android/foreground-service#handling-fg_timeout) for the example and budget guidance. ## Android voluntary foreground service run limit `EventType.FG_RUN_LIMIT_REACHED = 10` is a separate Android-only, best-effort event for an accepted voluntary expiry requested with `notification.android.foregroundServiceBestEffortStopAfterMs`. Native code attempts demotion with REMOVE and selective conditional stopping before publishing outside lifecycle locks. It does not wait for a React context. `FG_TIMEOUT = 9` retains its framework-timeout meaning; voluntary expiry never synthesizes it. The event carries the latest owned `detail.notification` and optional `detail.foregroundServiceRunLimit` metadata: `requestedDurationMs`, `deadlineElapsed`, `deliveredElapsed`, `demotion` (`returned` or `failed`) and `conditionalStop` (`accepted`, `refused`, `deferred` or `failed`). Elapsed values include deep sleep. A returned demotion or accepted conditional stop is not an AMS teardown acknowledgement. The event does not certify task completion, JavaScript cancellation, a final service stop or protection against later crashes. Add a branch for this event to your existing foreground listener and single background handler. Use it to request cooperative cancellation or checkpointing when received; JavaScript delivery may be delayed or absent after process suspension/death. Native teardown is independent of event delivery. Scheduling failures are reported through native diagnostics, not a misleading expiry event. See the [run-limit contract and example](/react-native/android/foreground-service#voluntary-best-effort-run-limit-api-35). ## App open events If user interaction on a notification has caused your application to open, you may want to obtain the notification which triggered the app to open (e.g. a specific chat message). Call `getInitialNotification()` early in your React lifecycle to retrieve that notification. > `getInitialNotification()` works on **both Android and iOS**. An iOS cold-start bug that caused it to return `null` even when the app was launched by a notification was fixed in v9.1.12. Apps that were migrating to the `PRESS` event via `onForegroundEvent` as a workaround can continue to do so — both patterns are supported and idiomatic. It is recommended to always check if a notification has caused your app to open before displaying the main app content. Once consumed, the initial notification is removed. For example, setup a "bootstrap" function inside of the root component of your application: ```jsx import React, { useState, useEffect } from 'react'; import notifee from 'react-native-notify-kit'; function App() { const [loading, setLoading] = useState(true); // Bootstrap sequence function async function bootstrap() { const initialNotification = await notifee.getInitialNotification(); if (initialNotification) { console.log('Notification caused application to open', initialNotification.notification); console.log('Press action used to open the app', initialNotification.pressAction); } } useEffect(() => { bootstrap() .then(() => setLoading(false)) .catch(console.error); }, []); if (loading) { return null; } ... } ``` If available, `initialNotification.notification` identifies the notification associated with the app opening. Depending on the delivery path, `pressAction` and reply `input` may also be available; check for them before use. Use the available details to perform the expected action, such as opening a chat screen. ### Android consumption and recovery On Android, `getInitialNotification()` reads the pending interaction made available by a notification press, an action press, or a supported payload-bearing [full-screen launch](/react-native/android/behaviour#full-screen). It returns `null` when no eligible interaction is available; displaying a notification alone does not make it available. A returned interaction is consumed before the result reaches JavaScript. For identifiable library interactions, pending sticky events, the Activity's launch Intent and persistent consumption state are coordinated so the same interaction cannot be read again. For example, after reading A, later calls return `null` until a new interaction becomes available. Reading B must not expose the old A again on the next call, on return through Recents, or after supported restoration. A new press is a new interaction even when it uses the same `notification.id`; redisplaying a notification does not by itself count as a press. Supported identifiable launch paths can recover an unread interaction after Activity recreation or process death when Android restores an Intent carrying its identity and sufficient persistent state remains. This is conditional recovery, not a durable queue of notification payloads. In particular, recovery from an Activity Intent does not promise to restore all event-only details such as RemoteInput. Read the interaction early, and use `onForegroundEvent` / `onBackgroundEvent` for `PRESS`, `ACTION_PRESS` and reply input as described above. This initial-notification lookup is not a FIFO queue. Valid, identifiable full-screen launches use **strict latest-wins**, ordered by the first valid capture of each interaction at the app. A newly captured valid interaction replaces the previous captured launch. Android can deliver captures in a different order from notification display or PendingIntent send order. Replaced captures stay retired, including after process death; invalid or already-retired deliveries do not replace the pending valid capture. Merely displaying another notification does not retire a captured launch. Calling this API consumes its pending result; it does not suppress the separate event handlers. If both paths trigger navigation or other application work, coordinate them in your app to avoid handling the same user action twice. The guarantee is **at-most-once consumption through this API** within the limits below, not exactly-once end-to-end execution of navigation, network requests or other JavaScript work. #### Recovery limits - Legacy or host-created Intents without a library interaction identity have only in-memory consumption protection; cross-process deduplication and recovery are limited. Older Android tap paths do not all carry recoverable identities. - Full-screen payloads can be lost if the process terminates after native capture but before JavaScript consumption and Android does not restore the matching payload-bearing Intent. Persisted identity/consumption state does not persist the full payload, and an older replaced launch is not a substitute for the lost payload. - If a required consumption write fails, the identifiable interaction is not returned as successfully consumed; a later call can retry. The narrow latest-wins exception requires all attempts to persist replacement invalidation to fail, process death before the replacement is consumed, and no sufficient durable state to distinguish receipt from non-receipt. Only in that total-persistence-failure window can an older launch be recovered. This does not permit replay after a successful consumption write, or an older launch to win in the same process. These Android rules do not extend recovery guarantees to iOS. Full-screen presentation and device-specific behaviour remain subject to [Android requirements](/react-native/android/behaviour#full-screen); validation on the tested Pixel and emulators does not guarantee identical behaviour on every OEM device.