Browse Source

lifecycle start works

Daniel Zlotin 8 years ago
parent
commit
9efb7d10c8

+ 3
- 1
ios/RNN.h View File

@@ -2,12 +2,14 @@
2 2
 #import <Foundation/Foundation.h>
3 3
 #import <UIKit/UIKit.h>
4 4
 
5
-@class RCTBridge;
5
+#import "RNNEventEmitter.h"
6
+#import "RCTBridge.h"
6 7
 
7 8
 @interface RNN : NSObject
8 9
 
9 10
 @property (readonly) BOOL isReadyToReceiveCommands;
10 11
 @property (readonly) RCTBridge* bridge;
12
+@property (readonly) RNNEventEmitter* eventEmitter;
11 13
 
12 14
 +(instancetype)instance;
13 15
 

+ 3
- 1
ios/RNN.m View File

@@ -6,9 +6,11 @@
6 6
 #import "RNNSplashScreen.h"
7 7
 
8 8
 @interface RNN()
9
-@property RNNEventEmitter* eventEmitter;
9
+
10 10
 @property (readwrite) RCTBridge* bridge;
11 11
 @property (readwrite) BOOL isReadyToReceiveCommands;
12
+@property (readwrite) RNNEventEmitter* eventEmitter;
13
+
12 14
 @end
13 15
 
14 16
 @implementation RNN

+ 4
- 0
ios/RNNEventEmitter.h View File

@@ -8,4 +8,8 @@
8 8
 
9 9
 -(void)sendOnAppLaunched;
10 10
 
11
+-(void)sendContainerStart:(NSString*)containerId;
12
+
13
+-(void)sendContainerStop:(NSString*)containerId;
14
+
11 15
 @end

+ 21
- 5
ios/RNNEventEmitter.m View File

@@ -6,18 +6,34 @@
6 6
 
7 7
 RCT_EXPORT_MODULE();
8 8
 
9
-static NSString* const onAppLaunched = @"RNN_onAppLaunched";
9
+static NSString* const onAppLaunched	= @"RNN.appLaunched";
10
+static NSString* const containerStart	= @"RNN.containerStart";
11
+static NSString* const containerStop	= @"RNN.containerStop";
10 12
 
11
--(NSArray<NSString *> *)supportedEvents
12
-{
13
-	return @[onAppLaunched];
14
-}
13
+# pragma mark public
15 14
 
16 15
 -(void)sendOnAppLaunched
17 16
 {
18 17
 	[self send:onAppLaunched body:nil];
19 18
 }
20 19
 
20
+-(void)sendContainerStart:(NSString *)containerId
21
+{
22
+	[self send:containerStart body:@{@"id": containerId}];
23
+}
24
+
25
+-(void)sendContainerStop:(NSString *)containerId
26
+{
27
+	[self send:containerStop body:@{@"id": containerId}];
28
+}
29
+
30
+# pragma mark private
31
+
32
+-(NSArray<NSString *> *)supportedEvents
33
+{
34
+	return @[onAppLaunched, containerStart, containerStop];
35
+}
36
+
21 37
 -(void)send:(NSString *)eventName body:(id)body
22 38
 {
23 39
 	[[RNN.instance.bridge moduleForClass:[RNNEventEmitter class]] sendEventWithName:eventName body:body];

+ 8
- 5
ios/RNNRootViewController.m View File

@@ -4,6 +4,8 @@
4 4
 #import "RNN.h"
5 5
 
6 6
 @interface RNNRootViewController()
7
+@property NSString* containerId;
8
+@property NSString* containerName;
7 9
 @end
8 10
 
9 11
 @implementation RNNRootViewController
@@ -11,24 +13,25 @@
11 13
 -(instancetype)initWithNode:(RNNLayoutNode*)node
12 14
 {
13 15
 	self = [super init];
14
-	NSString* containerName = node.data[@"name"];
16
+	self.containerId = node.nodeId;
17
+	self.containerName = node.data[@"name"];
15 18
 	
16 19
 	self.view = [[RCTRootView alloc] initWithBridge:RNN.instance.bridge
17
-													  moduleName:containerName
18
-											   initialProperties:@{@"id": node.nodeId}];
20
+										 moduleName:self.containerName
21
+								  initialProperties:@{@"id": self.containerId}];
19 22
 	return self;
20 23
 }
21 24
 
22 25
 -(void)viewDidAppear:(BOOL)animated
23 26
 {
24 27
 	[super viewDidAppear:animated];
25
-	//send the event onAppear
28
+	[RNN.instance.eventEmitter sendContainerStart:self.containerId];
26 29
 }
27 30
 
28 31
 -(void)viewDidDisappear:(BOOL)animated
29 32
 {
30 33
 	[super viewDidDisappear:animated];
31
-	//send the event onDisappear
34
+	[RNN.instance.eventEmitter sendContainerStop:self.containerId];
32 35
 }
33 36
 
34 37
 @end

+ 1
- 0
playground/src/containers/WelcomeScreen.js View File

@@ -7,6 +7,7 @@ class WelcomeScreen extends Component {
7 7
   constructor(props) {
8 8
     super(props);
9 9
     this.onClickPush = this.onClickPush.bind(this);
10
+    this.onClickLifecycleScreen = this.onClickLifecycleScreen.bind(this);
10 11
   }
11 12
 
12 13
   render() {

+ 7
- 2
src/Navigation.js View File

@@ -8,6 +8,8 @@ import AppCommands from './commands/AppCommands';
8 8
 import ContainerCommands from './commands/ContainerCommands';
9 9
 import LayoutTreeParser from './commands/LayoutTreeParser';
10 10
 import LayoutTreeCrawler from './commands/LayoutTreeCrawler';
11
+import PrivateEventsListener from './events/PrivateEventsListener';
12
+import PublicEventsRegistry from './events/PublicEventsRegistry';
11 13
 
12 14
 class Navigation {
13 15
   constructor() {
@@ -19,6 +21,9 @@ class Navigation {
19 21
     this.layoutTreeCrawler = new LayoutTreeCrawler(this.uniqueIdProvider, this.store);
20 22
     this.nativeCommandsSender = new NativeCommandsSender();
21 23
     this.appCommands = new AppCommands(this.nativeCommandsSender, this.layoutTreeParser, this.layoutTreeCrawler);
24
+    this.publicEventsRegistry = new PublicEventsRegistry(this.nativeEventsReceiver);
25
+    this.privateEventsListener = new PrivateEventsListener(this.nativeEventsReceiver, this.store);
26
+    this.privateEventsListener.listenAndHandlePrivateEvents();
22 27
   }
23 28
 
24 29
   registerContainer(containerName, getContainerFunc) {
@@ -30,7 +35,7 @@ class Navigation {
30 35
   }
31 36
 
32 37
   events() {
33
-    return this.nativeEventsReceiver;
38
+    return this.publicEventsRegistry;
34 39
   }
35 40
 
36 41
   on(containerId) {
@@ -39,4 +44,4 @@ class Navigation {
39 44
 }
40 45
 
41 46
 const singleton = new Navigation();
42
-module.exports = singleton;
47
+export default singleton;

+ 10
- 5
src/Navigation.test.js View File

@@ -9,9 +9,8 @@ describe('Navigation', () => {
9 9
 
10 10
     jest.mock('./containers/ContainerRegistry');
11 11
     jest.mock('./commands/AppCommands');
12
-    jest.mock('./commands/ContainerCommands');
13 12
 
14
-    Navigation = require('./Navigation');
13
+    Navigation = require('./Navigation').default;
15 14
   });
16 15
 
17 16
   it('registerContainer delegates to ContainerRegistry', () => {
@@ -31,12 +30,18 @@ describe('Navigation', () => {
31 30
     expect(Navigation.appCommands.setRoot).toHaveBeenCalledWith(params);
32 31
   });
33 32
 
34
-  it('events return the events registry', () => {
35
-    expect(Navigation.events()).toBeDefined();
36
-    expect(Navigation.events().onAppLaunched).toBeInstanceOf(Function);
33
+  it('events return public events', () => {
34
+    const cb = jest.fn();
35
+    Navigation.events().onAppLaunched(cb);
36
+    expect(Navigation.nativeEventsReceiver.appLaunched).toHaveBeenCalledTimes(1);
37
+    expect(Navigation.nativeEventsReceiver.appLaunched).toHaveBeenCalledWith(cb);
37 38
   });
38 39
 
39 40
   it('on containerId returns an object that performs commands on a container', () => {
40 41
     expect(Navigation.on('myUniqueId').push).toBeInstanceOf(Function);
41 42
   });
43
+
44
+  it('starts listening and handles internal events', () => {
45
+    expect(Navigation.nativeEventsReceiver.containerStart).toHaveBeenCalledTimes(1);
46
+  });
42 47
 });

+ 6
- 2
src/adapters/NativeEventsReceiver.js View File

@@ -5,7 +5,11 @@ export default class NativeEventsReceiver {
5 5
     this.emitter = new NativeEventEmitter(NativeModules.RNNEventEmitter);
6 6
   }
7 7
 
8
-  onAppLaunched(callback) {
9
-    this.emitter.addListener('RNN_onAppLaunched', callback);
8
+  containerStart(callback) {
9
+    this.emitter.addListener('RNN.containerStart', callback);
10
+  }
11
+
12
+  appLaunched(callback) {
13
+    this.emitter.addListener('RNN.appLaunched', callback);
10 14
   }
11 15
 }

+ 11
- 3
src/adapters/NativeEventsReceiver.test.js View File

@@ -11,11 +11,19 @@ describe('NativeEventsReceiver', () => {
11 11
     uut.emitter = eventEmitterMock;
12 12
   });
13 13
 
14
-  it('register for onAppLaunched', () => {
14
+  it('register for appLaunched', () => {
15 15
     const callback = jest.fn();
16
-    uut.onAppLaunched(callback);
16
+    uut.appLaunched(callback);
17 17
     expect(callback).not.toHaveBeenCalled();
18 18
     expect(eventEmitterMock.addListener).toHaveBeenCalledTimes(1);
19
-    expect(eventEmitterMock.addListener).toHaveBeenCalledWith('RNN_onAppLaunched', callback);
19
+    expect(eventEmitterMock.addListener).toHaveBeenCalledWith('RNN.appLaunched', callback);
20
+  });
21
+
22
+  it('register for internal events', () => {
23
+    const callback = jest.fn();
24
+    uut.containerStart(callback);
25
+    expect(callback).not.toHaveBeenCalled();
26
+    expect(eventEmitterMock.addListener).toHaveBeenCalledTimes(1);
27
+    expect(eventEmitterMock.addListener).toHaveBeenCalledWith('RNN.containerStart', callback);
20 28
   });
21 29
 });

+ 18
- 5
src/containers/ContainerWrapper.js View File

@@ -1,3 +1,4 @@
1
+import _ from 'lodash';
1 2
 import React, { Component } from 'react';
2 3
 
3 4
 export default class ContainerWrapper {
@@ -6,10 +7,7 @@ export default class ContainerWrapper {
6 7
       constructor(props) {
7 8
         super(props);
8 9
         this._assertId(props);
9
-        this.state = {
10
-          id: props.id,
11
-          allProps: { ...props, ...store.getPropsForContainerId(props.id) }
12
-        };
10
+        this._createState(props);
13 11
       }
14 12
 
15 13
       _assertId(props) {
@@ -18,9 +16,24 @@ export default class ContainerWrapper {
18 16
         }
19 17
       }
20 18
 
19
+      _createState(props) {
20
+        this.state = {
21
+          id: props.id,
22
+          allProps: _.merge({}, props, store.getPropsForContainerId(props.id))
23
+        };
24
+      }
25
+
26
+      componentWillMount() {
27
+        store.setRefForId(this.state.id, this);
28
+      }
29
+
30
+      componentWillUnmount() {
31
+        store.cleanId(this.state.id);
32
+      }
33
+
21 34
       componentWillReceiveProps(nextProps) {
22 35
         this.setState({
23
-          allProps: { ...nextProps, ...store.getPropsForContainerId(this.state.id) }
36
+          allProps: _.merge({}, nextProps, store.getPropsForContainerId(this.state.id))
24 37
         });
25 38
       }
26 39
 

+ 18
- 3
src/containers/ContainerWrapper.test.js View File

@@ -100,9 +100,24 @@ describe('ContainerWrapper', () => {
100 100
 
101 101
   it('assignes key by id', () => {
102 102
     const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
103
-    renderer.create(<NavigationContainer id={'container123'} />);
104
-    expect(myContainerRef.props.id).toEqual('container123');
105
-    expect(myContainerRef._reactInternalInstance._currentElement.key).toEqual('container123');
103
+    renderer.create(<NavigationContainer id={'container1'} />);
104
+    expect(myContainerRef.props.id).toEqual('container1');
105
+    expect(myContainerRef._reactInternalInstance._currentElement.key).toEqual('container1');
106
+  });
107
+
108
+  it('saves self ref into store', () => {
109
+    const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
110
+    const tree = renderer.create(<NavigationContainer id={'container1'} />);
111
+    expect(store.getRefForId('container1')).toBeDefined();
112
+    expect(store.getRefForId('container1')).toBe(tree.getInstance());
113
+  });
114
+
115
+  it('cleans ref from store on unMount', () => {
116
+    const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
117
+    const tree = renderer.create(<NavigationContainer id={'container1'} />);
118
+    expect(store.getRefForId('container1')).toBeDefined();
119
+    tree.unmount();
120
+    expect(store.getRefForId('container1')).toBeUndefined();
106 121
   });
107 122
 
108 123
   xdescribe('container lifecycle', () => {

+ 6
- 6
src/containers/Store.js View File

@@ -16,23 +16,23 @@ export default class Store {
16 16
   }
17 17
 
18 18
   setContainerClassForName(containerName, ContainerClass) {
19
-    this.containersByName[containerName] = ContainerClass;
19
+    _.set(this.containersByName, containerName, ContainerClass);
20 20
   }
21 21
 
22 22
   getContainerClassForName(containerName) {
23
-    return this.containersByName[containerName];
23
+    return _.get(this.containersByName, containerName);
24 24
   }
25 25
 
26 26
   setRefForId(id, ref) {
27
-    this.refsById[id] = ref;
27
+    _.set(this.refsById, id, ref);
28 28
   }
29 29
 
30 30
   getRefForId(id) {
31
-    return this.refsById[id];
31
+    return _.get(this.refsById, id);
32 32
   }
33 33
 
34 34
   cleanId(id) {
35
-    this.refsById[id] = undefined;
36
-    this.propsByContainerId[id] = undefined;
35
+    _.unset(this.refsById, id);
36
+    _.unset(this.propsByContainerId, id);
37 37
   }
38 38
 }

+ 24
- 0
src/events/PrivateEventsListener.js View File

@@ -0,0 +1,24 @@
1
+import _ from 'lodash';
2
+
3
+export default class PrivateEventsListener {
4
+  constructor(nativeEventsReceiver, store) {
5
+    this.nativeEventsReceiver = nativeEventsReceiver;
6
+    this.store = store;
7
+  }
8
+
9
+  listenAndHandlePrivateEvents() {
10
+    this.nativeEventsReceiver.containerStart(this._handleContainerStart);
11
+  }
12
+
13
+  _handleContainerStart(params) {
14
+    // const id = params.id;
15
+    // try {
16
+    //   const ref = this.store.getRefForId(id);
17
+    //   if (ref && ref.onStart) {
18
+    //     ref.onStart();
19
+    //   }
20
+    // } catch (e) {
21
+    //   console.warn(e);
22
+    // }
23
+  }
24
+}

+ 21
- 0
src/events/PrivateEventsListener.test.js View File

@@ -0,0 +1,21 @@
1
+import _ from 'lodash';
2
+import PrivateEventsListener from './PrivateEventsListener';
3
+import NativeEventsReceiver from '../adapters/NativeEventsReceiver.mock';
4
+
5
+describe('PrivateEventsListener', () => {
6
+  let uut;
7
+  let nativeEventsReceiver;
8
+
9
+  beforeEach(() => {
10
+    nativeEventsReceiver = new NativeEventsReceiver();
11
+    uut = new PrivateEventsListener(nativeEventsReceiver);
12
+  });
13
+
14
+  it('registers for private events against nativeEventsReceiver', () => {
15
+    uut.listenAndHandlePrivateEvents();
16
+    expect(nativeEventsReceiver.containerStart).toHaveBeenCalledTimes(1);
17
+    const callbackFunction = nativeEventsReceiver.containerStart.mock.calls[0][0];
18
+    expect(callbackFunction).toBeInstanceOf(Function);
19
+    callbackFunction('myContainerId');
20
+  });
21
+});

+ 9
- 0
src/events/PublicEventsRegistry.js View File

@@ -0,0 +1,9 @@
1
+export default class PublicEventsRegistry {
2
+  constructor(nativeEventsReceiver) {
3
+    this.nativeEventsReceiver = nativeEventsReceiver;
4
+  }
5
+
6
+  onAppLaunched(callback) {
7
+    this.nativeEventsReceiver.appLaunched(callback);
8
+  }
9
+}

+ 19
- 0
src/events/PublicEventsRegistry.test.js View File

@@ -0,0 +1,19 @@
1
+import PublicEventsRegistry from './PublicEventsRegistry';
2
+import NativeEventsReceiver from '../adapters/NativeEventsReceiver.mock';
3
+
4
+describe('PublicEventsRegistry', () => {
5
+  let uut;
6
+  let nativeEventsReceiver;
7
+
8
+  beforeEach(() => {
9
+    nativeEventsReceiver = new NativeEventsReceiver();
10
+    uut = new PublicEventsRegistry(nativeEventsReceiver);
11
+  });
12
+
13
+  it('exposes onAppLaunch event', () => {
14
+    const cb = jest.fn();
15
+    uut.onAppLaunched(cb);
16
+    expect(nativeEventsReceiver.appLaunched).toHaveBeenCalledTimes(1);
17
+    expect(nativeEventsReceiver.appLaunched).toHaveBeenCalledWith(cb);
18
+  });
19
+});