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()
// add notification custom data
if (userInfo != NULL) {
info[@"data"] = userInfo;
info[@"notification"] = userInfo;
}
// Emit event
......
......@@ -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) {
......
......@@ -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"]
}
}
......@@ -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();
}
}
......@@ -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,8 +128,9 @@ describe("NotificationsIOS", () => {
});
});
describe("register categories", () => {
it("should call native update categories with array of categories", () => {
NotificationIOS.setCategories([someCategory]);
NotificationsIOS.setCategories([someCategory]);
expect(nativeUpdateNotificationCategories).to.have.been.calledWith([{
identifier: "SOME_CATEGORY",
......@@ -125,16 +140,25 @@ describe("NotificationsIOS", () => {
});
it("should call native update categories with empty array if no categories specified", () => {
NotificationIOS.setCategories();
NotificationsIOS.setCategories();
expect(nativeUpdateNotificationCategories).to.have.been.calledWith([]);
});
it("should subscribe to 'notificationActionReceived' event for each action identifier", () => {
NotificationIOS.setCategories([someCategory]);
it("should subscribe to 'notificationActionReceived' event once, with a single event handler", () => {
NotificationsIOS.setCategories([someCategory]);
expect(nativeAddEventListener).to.have.been.calledOnce;
expect(nativeAddEventListener).to.have.been.calledWith("notificationActionReceived", sinon.match.func);
expect(nativeAppAddEventListener).to.have.been.calledOnce;
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