diff --git a/RNNotifications/RNNotifications.m b/RNNotifications/RNNotifications.m index 5561228c427a1304d4c19d58e2df52b646851222..5f18ed0d3db0d0e66c409863a1cb82ff28195358 100644 --- a/RNNotifications/RNNotifications.m +++ b/RNNotifications/RNNotifications.m @@ -282,7 +282,7 @@ RCT_EXPORT_MODULE() // add notification custom data if (userInfo != NULL) { - info[@"data"] = userInfo; + info[@"notification"] = userInfo; } // Emit event diff --git a/example/index.ios.js b/example/index.ios.js index ffc76cd5a750c7c93365fe8d19eb48648a7348f4..63e846886a644b56337349726f4a9212f990c037 100644 --- a/example/index.ios.js +++ b/example/index.ios.js @@ -94,6 +94,7 @@ class NotificationsExampleApp extends Component { NotificationsIOS.removeEventListener('notificationReceivedForeground', this.onNotificationReceivedForeground.bind(this)); NotificationsIOS.removeEventListener('notificationReceivedBackground', this.onNotificationReceivedBackground.bind(this)); NotificationsIOS.removeEventListener('notificationOpened', this.onNotificationOpened.bind(this)); + NotificationsIOS.resetCategories(); } _onNotification(notification) { diff --git a/example/package.json b/example/package.json index b1e9f9cf046446f0dfdf5192ad6cba9a5aa74ba5..4e00ec349c99384566716cbc10abf5f2e34b1c57 100644 --- a/example/package.json +++ b/example/package.json @@ -6,8 +6,12 @@ "start": "node node_modules/react-native/local-cli/cli.js start" }, "dependencies": { + "babel-preset-react-native-stage-0": "^1.0.1", "react": "^0.14.7", "react-native": "^0.22.2", "react-native-notifications": "../" + }, + "babel": { + "presets": ["react-native-stage-0"] } } diff --git a/index.ios.js b/index.ios.js index 768c52c7582899f86dc3da093a75ce1257491a34..5d3a29ff948d2ac82e36b68f4ba311fb209f5f25 100644 --- a/index.ios.js +++ b/index.ios.js @@ -3,7 +3,7 @@ * @flow */ "use strict"; -import { NativeModules, DeviceEventEmitter } from "react-native"; +import { NativeModules, DeviceEventEmitter, NativeAppEventEmitter } from "react-native"; import Map from "core-js/library/es6/map"; const NativeRNNotifications = NativeModules.RNNotifications; // eslint-disable-line no-unused-vars import IOSNotification from "./notification.ios"; @@ -14,6 +14,8 @@ export const DEVICE_NOTIFICATION_OPENED_EVENT = "notificationOpened"; export const DEVICE_NOTIFICATION_ACTION_RECEIVED = "notificationActionReceived"; let _notificationHandlers = new Map(); +let _actionHandlers = new Map(); +let _actionListener; export class NotificationAction { constructor(options: Object, handler: Function) { @@ -70,12 +72,12 @@ export default class NotificationsIOS { } } - static _actionHandlerGenerator(identifier: string, handler: Function) { - return (action) => { - if (action.identifier === identifier) { - handler(action); - } - }; + static _actionHandlerDispatcher(action: Object) { + let actionHandler = _actionHandlers.get(action.identifier); + + if (actionHandler) { + actionHandler(action); + } } /** @@ -86,11 +88,14 @@ export default class NotificationsIOS { let notificationCategories = []; if (categories) { + // subscribe once for all actions + _actionListener = NativeAppEventEmitter.addListener(DEVICE_NOTIFICATION_ACTION_RECEIVED, this._actionHandlerDispatcher); + notificationCategories = categories.map(category => { return Object.assign({}, category.options, { actions: category.options.actions.map(action => { // subscribe to action event - DeviceEventEmitter.addListener(DEVICE_NOTIFICATION_ACTION_RECEIVED, this._actionHandlerGenerator(action.options.identifier, action.handler)); + _actionHandlers.set(action.options.identifier, action.handler); return action.options; }) @@ -100,4 +105,16 @@ export default class NotificationsIOS { NativeRNNotifications.updateNotificationCategories(notificationCategories); } + + /** + * Removes the event listener. Do this in `componentWillUnmount` to prevent + * memory leaks + */ + static resetCategories() { + if (_actionListener) { + _actionListener.remove(); + } + + _actionHandlers.clear(); + } } diff --git a/test/index.ios.spec.js b/test/index.ios.spec.js index e97e6a0e044c02d52377826fba7324aa94cc3d1e..57dfeb2d2b8642384c11eae9ca90393b88d5f68d 100644 --- a/test/index.ios.spec.js +++ b/test/index.ios.spec.js @@ -12,13 +12,15 @@ describe("NotificationsIOS", () => { "notificationOpened" ]; - let nativeAddEventListener, nativeRemoveEventListener, nativeUpdateNotificationCategories; - let NotificationIOS, NotificationAction, NotificationCategory; + let deviceAddEventListener, deviceRemoveEventListener, nativeAppAddEventListener, nativeAppRemoveEventListener, nativeUpdateNotificationCategories; + let NotificationsIOS, NotificationAction, NotificationCategory; let someHandler = () => {}; before(() => { - nativeAddEventListener = sinon.spy(); - nativeRemoveEventListener = sinon.spy(); + deviceAddEventListener = sinon.spy(); + deviceRemoveEventListener = sinon.spy(); + nativeAppAddEventListener = sinon.spy(); + nativeAppRemoveEventListener = sinon.spy(); nativeUpdateNotificationCategories = sinon.spy(); let libUnderTest = proxyquire("../index.ios", { @@ -28,33 +30,45 @@ describe("NotificationsIOS", () => { updateNotificationCategories: nativeUpdateNotificationCategories } }, + NativeAppEventEmitter: { + addListener: (...args) => { + nativeAppAddEventListener(...args); + + return { remove: nativeAppRemoveEventListener }; + } + }, DeviceEventEmitter: { addListener: (...args) => { - nativeAddEventListener(...args); + deviceAddEventListener(...args); - return { remove: nativeRemoveEventListener }; + return { remove: deviceRemoveEventListener }; } }, "@noCallThru": true } }); - NotificationIOS = libUnderTest.default; + NotificationsIOS = libUnderTest.default; NotificationAction = libUnderTest.NotificationAction; NotificationCategory = libUnderTest.NotificationCategory; }); afterEach(() => { - nativeAddEventListener.reset(); - nativeRemoveEventListener.reset(); + deviceAddEventListener.reset(); + deviceRemoveEventListener.reset(); + nativeAppAddEventListener.reset(); + nativeAppRemoveEventListener.reset(); nativeUpdateNotificationCategories.reset(); }); after(() => { - nativeAddEventListener = null; - nativeRemoveEventListener = null; + deviceAddEventListener = null; + deviceRemoveEventListener = null; + nativeAppAddEventListener = null; + nativeAppRemoveEventListener = null; nativeUpdateNotificationCategories = null; - NotificationIOS = null; + + NotificationsIOS = null; NotificationAction = null; NotificationCategory = null; }); @@ -62,39 +76,39 @@ describe("NotificationsIOS", () => { describe("Add Event Listener", () => { deviceEvents.forEach(event => { it(`should subscribe the given handler to device event: ${event}`, () => { - NotificationIOS.addEventListener(event, someHandler); + NotificationsIOS.addEventListener(event, someHandler); - expect(nativeAddEventListener).to.have.been.calledWith(event, sinon.match.func); + expect(deviceAddEventListener).to.have.been.calledWith(event, sinon.match.func); }); }); it("should not subscribe to unknown device events", () => { - NotificationIOS.addEventListener("someUnsupportedEvent", someHandler); + NotificationsIOS.addEventListener("someUnsupportedEvent", someHandler); - expect(nativeAddEventListener).to.not.have.been.called; + expect(deviceAddEventListener).to.not.have.been.called; }); }); describe("Remove Event Listener", () => { deviceEvents.forEach(event => { it(`should unsubscribe the given handler from device event: ${event}`, () => { - NotificationIOS.addEventListener(event, someHandler); - NotificationIOS.removeEventListener(event, someHandler); + NotificationsIOS.addEventListener(event, someHandler); + NotificationsIOS.removeEventListener(event, someHandler); - expect(nativeRemoveEventListener).to.have.been.calledOnce; + expect(deviceRemoveEventListener).to.have.been.calledOnce; }); }); it("should not unsubscribe to unknown device events", () => { let someUnsupportedEvent = "someUnsupportedEvent"; - NotificationIOS.addEventListener(someUnsupportedEvent, someHandler); - NotificationIOS.removeEventListener(someUnsupportedEvent, someHandler); + NotificationsIOS.addEventListener(someUnsupportedEvent, someHandler); + NotificationsIOS.removeEventListener(someUnsupportedEvent, someHandler); - expect(nativeRemoveEventListener).to.not.have.been.called; + expect(deviceRemoveEventListener).to.not.have.been.called; }); }); - describe("Update notification categories", () => { + describe("Notification actions handling", () => { let someAction, someCategory; let actionOpts = { @@ -114,27 +128,37 @@ describe("NotificationsIOS", () => { }); }); - it("should call native update categories with array of categories", () => { - NotificationIOS.setCategories([someCategory]); + describe("register categories", () => { + it("should call native update categories with array of categories", () => { + NotificationsIOS.setCategories([someCategory]); - expect(nativeUpdateNotificationCategories).to.have.been.calledWith([{ - identifier: "SOME_CATEGORY", - actions: [actionOpts], - context: "default" - }]); - }); + expect(nativeUpdateNotificationCategories).to.have.been.calledWith([{ + identifier: "SOME_CATEGORY", + actions: [actionOpts], + context: "default" + }]); + }); - it("should call native update categories with empty array if no categories specified", () => { - NotificationIOS.setCategories(); + it("should call native update categories with empty array if no categories specified", () => { + NotificationsIOS.setCategories(); + + expect(nativeUpdateNotificationCategories).to.have.been.calledWith([]); + }); - expect(nativeUpdateNotificationCategories).to.have.been.calledWith([]); + it("should subscribe to 'notificationActionReceived' event once, with a single event handler", () => { + NotificationsIOS.setCategories([someCategory]); + + expect(nativeAppAddEventListener).to.have.been.calledOnce; + expect(nativeAppAddEventListener).to.have.been.calledWith("notificationActionReceived", NotificationsIOS._actionHandlerDispatcher); + }); }); - it("should subscribe to 'notificationActionReceived' event for each action identifier", () => { - NotificationIOS.setCategories([someCategory]); + describe("reset categories", () => { + it("should remove 'notificationActionReceived' event handler", function () { + NotificationsIOS.resetCategories(); - expect(nativeAddEventListener).to.have.been.calledOnce; - expect(nativeAddEventListener).to.have.been.calledWith("notificationActionReceived", sinon.match.func); + expect(nativeAppRemoveEventListener).to.have.been.calledOnce; + }); }); }); });