Browse Source

lifecycle start works

Daniel Zlotin 7 years ago
parent
commit
9efb7d10c8

+ 3
- 1
ios/RNN.h View File

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

+ 3
- 1
ios/RNN.m View File

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

+ 4
- 0
ios/RNNEventEmitter.h View File

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

+ 21
- 5
ios/RNNEventEmitter.m View File

6
 
6
 
7
 RCT_EXPORT_MODULE();
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
 -(void)sendOnAppLaunched
15
 -(void)sendOnAppLaunched
17
 {
16
 {
18
 	[self send:onAppLaunched body:nil];
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
 -(void)send:(NSString *)eventName body:(id)body
37
 -(void)send:(NSString *)eventName body:(id)body
22
 {
38
 {
23
 	[[RNN.instance.bridge moduleForClass:[RNNEventEmitter class]] sendEventWithName:eventName body:body];
39
 	[[RNN.instance.bridge moduleForClass:[RNNEventEmitter class]] sendEventWithName:eventName body:body];

+ 8
- 5
ios/RNNRootViewController.m View File

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

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

7
   constructor(props) {
7
   constructor(props) {
8
     super(props);
8
     super(props);
9
     this.onClickPush = this.onClickPush.bind(this);
9
     this.onClickPush = this.onClickPush.bind(this);
10
+    this.onClickLifecycleScreen = this.onClickLifecycleScreen.bind(this);
10
   }
11
   }
11
 
12
 
12
   render() {
13
   render() {

+ 7
- 2
src/Navigation.js View File

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

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

9
 
9
 
10
     jest.mock('./containers/ContainerRegistry');
10
     jest.mock('./containers/ContainerRegistry');
11
     jest.mock('./commands/AppCommands');
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
   it('registerContainer delegates to ContainerRegistry', () => {
16
   it('registerContainer delegates to ContainerRegistry', () => {
31
     expect(Navigation.appCommands.setRoot).toHaveBeenCalledWith(params);
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
   it('on containerId returns an object that performs commands on a container', () => {
40
   it('on containerId returns an object that performs commands on a container', () => {
40
     expect(Navigation.on('myUniqueId').push).toBeInstanceOf(Function);
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
     this.emitter = new NativeEventEmitter(NativeModules.RNNEventEmitter);
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
     uut.emitter = eventEmitterMock;
11
     uut.emitter = eventEmitterMock;
12
   });
12
   });
13
 
13
 
14
-  it('register for onAppLaunched', () => {
14
+  it('register for appLaunched', () => {
15
     const callback = jest.fn();
15
     const callback = jest.fn();
16
-    uut.onAppLaunched(callback);
16
+    uut.appLaunched(callback);
17
     expect(callback).not.toHaveBeenCalled();
17
     expect(callback).not.toHaveBeenCalled();
18
     expect(eventEmitterMock.addListener).toHaveBeenCalledTimes(1);
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
+import _ from 'lodash';
1
 import React, { Component } from 'react';
2
 import React, { Component } from 'react';
2
 
3
 
3
 export default class ContainerWrapper {
4
 export default class ContainerWrapper {
6
       constructor(props) {
7
       constructor(props) {
7
         super(props);
8
         super(props);
8
         this._assertId(props);
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
       _assertId(props) {
13
       _assertId(props) {
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
       componentWillReceiveProps(nextProps) {
34
       componentWillReceiveProps(nextProps) {
22
         this.setState({
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
 
100
 
101
   it('assignes key by id', () => {
101
   it('assignes key by id', () => {
102
     const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
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
   xdescribe('container lifecycle', () => {
123
   xdescribe('container lifecycle', () => {

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

16
   }
16
   }
17
 
17
 
18
   setContainerClassForName(containerName, ContainerClass) {
18
   setContainerClassForName(containerName, ContainerClass) {
19
-    this.containersByName[containerName] = ContainerClass;
19
+    _.set(this.containersByName, containerName, ContainerClass);
20
   }
20
   }
21
 
21
 
22
   getContainerClassForName(containerName) {
22
   getContainerClassForName(containerName) {
23
-    return this.containersByName[containerName];
23
+    return _.get(this.containersByName, containerName);
24
   }
24
   }
25
 
25
 
26
   setRefForId(id, ref) {
26
   setRefForId(id, ref) {
27
-    this.refsById[id] = ref;
27
+    _.set(this.refsById, id, ref);
28
   }
28
   }
29
 
29
 
30
   getRefForId(id) {
30
   getRefForId(id) {
31
-    return this.refsById[id];
31
+    return _.get(this.refsById, id);
32
   }
32
   }
33
 
33
 
34
   cleanId(id) {
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

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

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

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

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
+});