Commit 4392135f authored by Lidan Hifi's avatar Lidan Hifi

optimized action handling using single listener, added reset method that...

optimized action handling using single listener, added reset method that removes the action event listener to prevent memory leaks
parent b09fa232
...@@ -282,7 +282,7 @@ RCT_EXPORT_MODULE() ...@@ -282,7 +282,7 @@ RCT_EXPORT_MODULE()
// add notification custom data // add notification custom data
if (userInfo != NULL) { if (userInfo != NULL) {
info[@"data"] = userInfo; info[@"notification"] = userInfo;
} }
// Emit event // Emit event
......
...@@ -94,6 +94,7 @@ class NotificationsExampleApp extends Component { ...@@ -94,6 +94,7 @@ class NotificationsExampleApp extends Component {
NotificationsIOS.removeEventListener('notificationReceivedForeground', this.onNotificationReceivedForeground.bind(this)); NotificationsIOS.removeEventListener('notificationReceivedForeground', this.onNotificationReceivedForeground.bind(this));
NotificationsIOS.removeEventListener('notificationReceivedBackground', this.onNotificationReceivedBackground.bind(this)); NotificationsIOS.removeEventListener('notificationReceivedBackground', this.onNotificationReceivedBackground.bind(this));
NotificationsIOS.removeEventListener('notificationOpened', this.onNotificationOpened.bind(this)); NotificationsIOS.removeEventListener('notificationOpened', this.onNotificationOpened.bind(this));
NotificationsIOS.resetCategories();
} }
_onNotification(notification) { _onNotification(notification) {
......
...@@ -6,8 +6,12 @@ ...@@ -6,8 +6,12 @@
"start": "node node_modules/react-native/local-cli/cli.js start" "start": "node node_modules/react-native/local-cli/cli.js start"
}, },
"dependencies": { "dependencies": {
"babel-preset-react-native-stage-0": "^1.0.1",
"react": "^0.14.7", "react": "^0.14.7",
"react-native": "^0.22.2", "react-native": "^0.22.2",
"react-native-notifications": "../" "react-native-notifications": "../"
},
"babel": {
"presets": ["react-native-stage-0"]
} }
} }
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* @flow * @flow
*/ */
"use strict"; "use strict";
import { NativeModules, DeviceEventEmitter } from "react-native"; import { NativeModules, DeviceEventEmitter, NativeAppEventEmitter } from "react-native";
import Map from "core-js/library/es6/map"; import Map from "core-js/library/es6/map";
const NativeRNNotifications = NativeModules.RNNotifications; // eslint-disable-line no-unused-vars const NativeRNNotifications = NativeModules.RNNotifications; // eslint-disable-line no-unused-vars
import IOSNotification from "./notification.ios"; import IOSNotification from "./notification.ios";
...@@ -14,6 +14,8 @@ export const DEVICE_NOTIFICATION_OPENED_EVENT = "notificationOpened"; ...@@ -14,6 +14,8 @@ export const DEVICE_NOTIFICATION_OPENED_EVENT = "notificationOpened";
export const DEVICE_NOTIFICATION_ACTION_RECEIVED = "notificationActionReceived"; export const DEVICE_NOTIFICATION_ACTION_RECEIVED = "notificationActionReceived";
let _notificationHandlers = new Map(); let _notificationHandlers = new Map();
let _actionHandlers = new Map();
let _actionListener;
export class NotificationAction { export class NotificationAction {
constructor(options: Object, handler: Function) { constructor(options: Object, handler: Function) {
...@@ -70,12 +72,12 @@ export default class NotificationsIOS { ...@@ -70,12 +72,12 @@ export default class NotificationsIOS {
} }
} }
static _actionHandlerGenerator(identifier: string, handler: Function) { static _actionHandlerDispatcher(action: Object) {
return (action) => { let actionHandler = _actionHandlers.get(action.identifier);
if (action.identifier === identifier) {
handler(action); if (actionHandler) {
actionHandler(action);
} }
};
} }
/** /**
...@@ -86,11 +88,14 @@ export default class NotificationsIOS { ...@@ -86,11 +88,14 @@ export default class NotificationsIOS {
let notificationCategories = []; let notificationCategories = [];
if (categories) { if (categories) {
// subscribe once for all actions
_actionListener = NativeAppEventEmitter.addListener(DEVICE_NOTIFICATION_ACTION_RECEIVED, this._actionHandlerDispatcher);
notificationCategories = categories.map(category => { notificationCategories = categories.map(category => {
return Object.assign({}, category.options, { return Object.assign({}, category.options, {
actions: category.options.actions.map(action => { actions: category.options.actions.map(action => {
// subscribe to action event // 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; return action.options;
}) })
...@@ -100,4 +105,16 @@ export default class NotificationsIOS { ...@@ -100,4 +105,16 @@ export default class NotificationsIOS {
NativeRNNotifications.updateNotificationCategories(notificationCategories); NativeRNNotifications.updateNotificationCategories(notificationCategories);
} }
/**
* Removes the event listener. Do this in `componentWillUnmount` to prevent
* memory leaks
*/
static resetCategories() {
if (_actionListener) {
_actionListener.remove();
}
_actionHandlers.clear();
}
} }
...@@ -12,13 +12,15 @@ describe("NotificationsIOS", () => { ...@@ -12,13 +12,15 @@ describe("NotificationsIOS", () => {
"notificationOpened" "notificationOpened"
]; ];
let nativeAddEventListener, nativeRemoveEventListener, nativeUpdateNotificationCategories; let deviceAddEventListener, deviceRemoveEventListener, nativeAppAddEventListener, nativeAppRemoveEventListener, nativeUpdateNotificationCategories;
let NotificationIOS, NotificationAction, NotificationCategory; let NotificationsIOS, NotificationAction, NotificationCategory;
let someHandler = () => {}; let someHandler = () => {};
before(() => { before(() => {
nativeAddEventListener = sinon.spy(); deviceAddEventListener = sinon.spy();
nativeRemoveEventListener = sinon.spy(); deviceRemoveEventListener = sinon.spy();
nativeAppAddEventListener = sinon.spy();
nativeAppRemoveEventListener = sinon.spy();
nativeUpdateNotificationCategories = sinon.spy(); nativeUpdateNotificationCategories = sinon.spy();
let libUnderTest = proxyquire("../index.ios", { let libUnderTest = proxyquire("../index.ios", {
...@@ -28,33 +30,45 @@ describe("NotificationsIOS", () => { ...@@ -28,33 +30,45 @@ describe("NotificationsIOS", () => {
updateNotificationCategories: nativeUpdateNotificationCategories updateNotificationCategories: nativeUpdateNotificationCategories
} }
}, },
NativeAppEventEmitter: {
addListener: (...args) => {
nativeAppAddEventListener(...args);
return { remove: nativeAppRemoveEventListener };
}
},
DeviceEventEmitter: { DeviceEventEmitter: {
addListener: (...args) => { addListener: (...args) => {
nativeAddEventListener(...args); deviceAddEventListener(...args);
return { remove: nativeRemoveEventListener }; return { remove: deviceRemoveEventListener };
} }
}, },
"@noCallThru": true "@noCallThru": true
} }
}); });
NotificationIOS = libUnderTest.default; NotificationsIOS = libUnderTest.default;
NotificationAction = libUnderTest.NotificationAction; NotificationAction = libUnderTest.NotificationAction;
NotificationCategory = libUnderTest.NotificationCategory; NotificationCategory = libUnderTest.NotificationCategory;
}); });
afterEach(() => { afterEach(() => {
nativeAddEventListener.reset(); deviceAddEventListener.reset();
nativeRemoveEventListener.reset(); deviceRemoveEventListener.reset();
nativeAppAddEventListener.reset();
nativeAppRemoveEventListener.reset();
nativeUpdateNotificationCategories.reset(); nativeUpdateNotificationCategories.reset();
}); });
after(() => { after(() => {
nativeAddEventListener = null; deviceAddEventListener = null;
nativeRemoveEventListener = null; deviceRemoveEventListener = null;
nativeAppAddEventListener = null;
nativeAppRemoveEventListener = null;
nativeUpdateNotificationCategories = null; nativeUpdateNotificationCategories = null;
NotificationIOS = null;
NotificationsIOS = null;
NotificationAction = null; NotificationAction = null;
NotificationCategory = null; NotificationCategory = null;
}); });
...@@ -62,39 +76,39 @@ describe("NotificationsIOS", () => { ...@@ -62,39 +76,39 @@ describe("NotificationsIOS", () => {
describe("Add Event Listener", () => { describe("Add Event Listener", () => {
deviceEvents.forEach(event => { deviceEvents.forEach(event => {
it(`should subscribe the given handler to device event: ${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", () => { 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", () => { describe("Remove Event Listener", () => {
deviceEvents.forEach(event => { deviceEvents.forEach(event => {
it(`should unsubscribe the given handler from device event: ${event}`, () => { it(`should unsubscribe the given handler from device event: ${event}`, () => {
NotificationIOS.addEventListener(event, someHandler); NotificationsIOS.addEventListener(event, someHandler);
NotificationIOS.removeEventListener(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", () => { it("should not unsubscribe to unknown device events", () => {
let someUnsupportedEvent = "someUnsupportedEvent"; let someUnsupportedEvent = "someUnsupportedEvent";
NotificationIOS.addEventListener(someUnsupportedEvent, someHandler); NotificationsIOS.addEventListener(someUnsupportedEvent, someHandler);
NotificationIOS.removeEventListener(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 someAction, someCategory;
let actionOpts = { let actionOpts = {
...@@ -114,8 +128,9 @@ describe("NotificationsIOS", () => { ...@@ -114,8 +128,9 @@ describe("NotificationsIOS", () => {
}); });
}); });
describe("register categories", () => {
it("should call native update categories with array of categories", () => { it("should call native update categories with array of categories", () => {
NotificationIOS.setCategories([someCategory]); NotificationsIOS.setCategories([someCategory]);
expect(nativeUpdateNotificationCategories).to.have.been.calledWith([{ expect(nativeUpdateNotificationCategories).to.have.been.calledWith([{
identifier: "SOME_CATEGORY", identifier: "SOME_CATEGORY",
...@@ -125,16 +140,25 @@ describe("NotificationsIOS", () => { ...@@ -125,16 +140,25 @@ describe("NotificationsIOS", () => {
}); });
it("should call native update categories with empty array if no categories specified", () => { it("should call native update categories with empty array if no categories specified", () => {
NotificationIOS.setCategories(); NotificationsIOS.setCategories();
expect(nativeUpdateNotificationCategories).to.have.been.calledWith([]); expect(nativeUpdateNotificationCategories).to.have.been.calledWith([]);
}); });
it("should subscribe to 'notificationActionReceived' event for each action identifier", () => { it("should subscribe to 'notificationActionReceived' event once, with a single event handler", () => {
NotificationIOS.setCategories([someCategory]); NotificationsIOS.setCategories([someCategory]);
expect(nativeAddEventListener).to.have.been.calledOnce; expect(nativeAppAddEventListener).to.have.been.calledOnce;
expect(nativeAddEventListener).to.have.been.calledWith("notificationActionReceived", sinon.match.func); expect(nativeAppAddEventListener).to.have.been.calledWith("notificationActionReceived", NotificationsIOS._actionHandlerDispatcher);
});
});
describe("reset categories", () => {
it("should remove 'notificationActionReceived' event handler", function () {
NotificationsIOS.resetCategories();
expect(nativeAppRemoveEventListener).to.have.been.calledOnce;
});
}); });
}); });
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment