소스 검색

External component lifecycle events (#4872)

* Add external component e2e
* Emit appear and disappear events for external components
Guy Carmeli 5 년 전
부모
커밋
602c669b02
No account linked to committer's email address

+ 23
- 0
e2e/ExternalComponent.test.js 파일 보기

@@ -0,0 +1,23 @@
1
+const Utils = require('./Utils');
2
+const TestIDs = require('../playground/src/testIDs');
3
+const Android = require('./AndroidUtils');
4
+
5
+const { elementByLabel, elementById } = Utils;
6
+
7
+describe('External Component', () => {
8
+  beforeEach(async () => {
9
+    await device.relaunchApp();
10
+    await elementById(TestIDs.NAVIGATION_TAB).tap();
11
+    await elementById(TestIDs.EXTERNAL_COMP_BTN).tap();
12
+  });
13
+
14
+  test('Push external component', async () => {
15
+    await elementById(TestIDs.PUSH_BTN).tap();
16
+    await expect(elementByLabel('This is an external component')).toBeVisible();
17
+  });
18
+
19
+  test('Show external component in deep stack in modal', async () => {
20
+    await elementById(TestIDs.MODAL_BTN).tap();
21
+    await expect(elementByLabel('External component in deep stack')).toBeVisible();
22
+  });
23
+});

+ 1
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/LayoutFactory.java 파일 보기

@@ -158,6 +158,7 @@ public class LayoutFactory {
158 158
                 externalComponent,
159 159
                 externalComponentCreators.get(externalComponent.name.get()),
160 160
                 reactInstanceManager,
161
+                new EventEmitter(reactInstanceManager.getCurrentReactContext()),
161 162
                 parse(typefaceManager, node.getOptions())
162 163
         );
163 164
     }

+ 60
- 51
lib/android/app/src/main/java/com/reactnativenavigation/react/EventEmitter.java 파일 보기

@@ -1,65 +1,69 @@
1 1
 package com.reactnativenavigation.react;
2 2
 
3
+import android.util.Log;
4
+
3 5
 import com.facebook.react.bridge.Arguments;
4 6
 import com.facebook.react.bridge.ReactContext;
5 7
 import com.facebook.react.bridge.WritableMap;
6 8
 import com.facebook.react.modules.core.DeviceEventManagerModule;
7 9
 
10
+import javax.annotation.Nullable;
11
+
8 12
 import static com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
9 13
 
10 14
 public class EventEmitter {
11
-	private static final String AppLaunched             = "RNN.AppLaunched";
12
-	private static final String CommandCompleted        = "RNN.CommandCompleted";
13
-	private static final String BottomTabSelected       = "RNN.BottomTabSelected";
14
-	private static final String ComponentDidAppear      = "RNN.ComponentDidAppear";
15
-	private static final String ComponentDidDisappear   = "RNN.ComponentDidDisappear";
16
-	private static final String NavigationButtonPressed = "RNN.NavigationButtonPressed";
17
-    private static final String ModalDismissed          = "RNN.ModalDismissed";
18
-
19
-	private final RCTDeviceEventEmitter emitter;
15
+    private static final String AppLaunched = "RNN.AppLaunched";
16
+    private static final String CommandCompleted = "RNN.CommandCompleted";
17
+    private static final String BottomTabSelected = "RNN.BottomTabSelected";
18
+    private static final String ComponentDidAppear = "RNN.ComponentDidAppear";
19
+    private static final String ComponentDidDisappear = "RNN.ComponentDidDisappear";
20
+    private static final String NavigationButtonPressed = "RNN.NavigationButtonPressed";
21
+    private static final String ModalDismissed = "RNN.ModalDismissed";
22
+    @Nullable
23
+    private ReactContext reactContext;
20 24
 
21
-	public EventEmitter(ReactContext reactContext) {
22
-		this.emitter = reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
23
-	}
25
+    public EventEmitter(@Nullable ReactContext reactContext) {
26
+        this.reactContext = reactContext;
27
+    }
24 28
 
25
-	public void appLaunched() {
26
-		emit(AppLaunched);
27
-	}
29
+    public void appLaunched() {
30
+        emit(AppLaunched);
31
+    }
28 32
 
29
-	public void componentDidDisappear(String id, String componentName) {
30
-		WritableMap event = Arguments.createMap();
31
-		event.putString("componentId", id);
32
-		event.putString("componentName", componentName);
33
-		emit(ComponentDidDisappear, event);
34
-	}
33
+    public void emitComponentDidDisappear(String id, String componentName) {
34
+        WritableMap event = Arguments.createMap();
35
+        event.putString("componentId", id);
36
+        event.putString("componentName", componentName);
37
+        emit(ComponentDidDisappear, event);
38
+    }
35 39
 
36
-	public void componentDidAppear(String id, String componentName) {
37
-		WritableMap event = Arguments.createMap();
38
-		event.putString("componentId", id);
39
-		event.putString("componentName", componentName);
40
-		emit(ComponentDidAppear, event);
41
-	}
40
+    public void emitComponentDidAppear(String id, String componentName) {
41
+        WritableMap event = Arguments.createMap();
42
+        event.putString("componentId", id);
43
+        event.putString("componentName", componentName);
44
+        emit(ComponentDidAppear, event);
45
+    }
42 46
 
43
-	public void emitOnNavigationButtonPressed(String id, String buttonId) {
44
-		WritableMap event = Arguments.createMap();
45
-		event.putString("componentId", id);
46
-		event.putString("buttonId", buttonId);
47
-		emit(NavigationButtonPressed, event);
48
-	}
47
+    public void emitOnNavigationButtonPressed(String id, String buttonId) {
48
+        WritableMap event = Arguments.createMap();
49
+        event.putString("componentId", id);
50
+        event.putString("buttonId", buttonId);
51
+        emit(NavigationButtonPressed, event);
52
+    }
49 53
 
50
-	public void emitBottomTabSelected(int unselectedTabIndex, int selectedTabIndex) {
51
-		WritableMap event = Arguments.createMap();
52
-		event.putInt("unselectedTabIndex", unselectedTabIndex);
53
-		event.putInt("selectedTabIndex", selectedTabIndex);
54
-		emit(BottomTabSelected, event);
55
-	}
54
+    public void emitBottomTabSelected(int unselectedTabIndex, int selectedTabIndex) {
55
+        WritableMap event = Arguments.createMap();
56
+        event.putInt("unselectedTabIndex", unselectedTabIndex);
57
+        event.putInt("selectedTabIndex", selectedTabIndex);
58
+        emit(BottomTabSelected, event);
59
+    }
56 60
 
57
-	public void emitCommandCompleted(String commandId, long completionTime) {
58
-		WritableMap event = Arguments.createMap();
59
-		event.putString("commandId", commandId);
60
-		event.putDouble("completionTime", completionTime);
61
-		emit(CommandCompleted, event);
62
-	}
61
+    public void emitCommandCompleted(String commandId, long completionTime) {
62
+        WritableMap event = Arguments.createMap();
63
+        event.putString("commandId", commandId);
64
+        event.putDouble("completionTime", completionTime);
65
+        emit(CommandCompleted, event);
66
+    }
63 67
 
64 68
     public void emitModalDismissed(String id, int modalsDismissed) {
65 69
         WritableMap event = Arguments.createMap();
@@ -68,11 +72,16 @@ public class EventEmitter {
68 72
         emit(ModalDismissed, event);
69 73
     }
70 74
 
71
-	private void emit(String eventName) {
72
-		emit(eventName, Arguments.createMap());
73
-	}
75
+    private void emit(String eventName) {
76
+        emit(eventName, Arguments.createMap());
77
+    }
74 78
 
75
-	private void emit(String eventName, WritableMap data) {
76
-		emitter.emit(eventName, data);
77
-	}
79
+    private void emit(String eventName, WritableMap data) {
80
+        if (reactContext == null) {
81
+            Log.e("RNN", "Could not send event " + eventName + ". React context is null!");
82
+            return;
83
+        }
84
+        RCTDeviceEventEmitter emitter = reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
85
+        emitter.emit(eventName, data);
86
+    }
78 87
 }

+ 2
- 2
lib/android/app/src/main/java/com/reactnativenavigation/react/ReactView.java 파일 보기

@@ -68,7 +68,7 @@ public class ReactView extends ReactRootView implements IReactView, Renderable {
68 68
 	public void sendComponentStart() {
69 69
         ReactContext currentReactContext = reactInstanceManager.getCurrentReactContext();
70 70
         if (currentReactContext != null) {
71
-            new EventEmitter(currentReactContext).componentDidAppear(componentId, componentName);
71
+            new EventEmitter(currentReactContext).emitComponentDidAppear(componentId, componentName);
72 72
         }
73 73
 	}
74 74
 
@@ -76,7 +76,7 @@ public class ReactView extends ReactRootView implements IReactView, Renderable {
76 76
 	public void sendComponentStop() {
77 77
         ReactContext currentReactContext = reactInstanceManager.getCurrentReactContext();
78 78
         if (currentReactContext != null) {
79
-            new EventEmitter(currentReactContext).componentDidDisappear(componentId, componentName);
79
+            new EventEmitter(currentReactContext).emitComponentDidDisappear(componentId, componentName);
80 80
         }
81 81
 	}
82 82
 

+ 19
- 7
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/externalcomponent/ExternalComponentViewController.java 파일 보기

@@ -4,7 +4,6 @@ import android.app.Activity;
4 4
 import android.support.v4.app.FragmentActivity;
5 5
 
6 6
 import com.facebook.react.ReactInstanceManager;
7
-import com.facebook.react.bridge.ReactContext;
8 7
 import com.reactnativenavigation.parse.ExternalComponent;
9 8
 import com.reactnativenavigation.parse.Options;
10 9
 import com.reactnativenavigation.react.EventEmitter;
@@ -16,27 +15,28 @@ public class ExternalComponentViewController extends ViewController<ExternalComp
16 15
     private final ExternalComponent externalComponent;
17 16
     private final ExternalComponentCreator componentCreator;
18 17
     private ReactInstanceManager reactInstanceManager;
18
+    private final EventEmitter emitter;
19 19
 
20
-    public ExternalComponentViewController(Activity activity, String id, ExternalComponent externalComponent, ExternalComponentCreator componentCreator, ReactInstanceManager reactInstanceManager, Options initialOptions) {
20
+    public ExternalComponentViewController(Activity activity, String id, ExternalComponent externalComponent, ExternalComponentCreator componentCreator, ReactInstanceManager reactInstanceManager, EventEmitter emitter, Options initialOptions) {
21 21
         super(activity, id, new NoOpYellowBoxDelegate(), initialOptions);
22 22
         this.externalComponent = externalComponent;
23 23
         this.componentCreator = componentCreator;
24 24
         this.reactInstanceManager = reactInstanceManager;
25
+        this.emitter = emitter;
25 26
     }
26 27
 
27 28
     @Override
28 29
     protected ExternalComponentLayout createView() {
29 30
         ExternalComponentLayout content = new ExternalComponentLayout(getActivity());
30
-        content.addView(componentCreator.create(getActivity(), reactInstanceManager, externalComponent.passProps).asView());
31
+        content.addView(componentCreator
32
+                .create(getActivity(), reactInstanceManager, externalComponent.passProps)
33
+                .asView());
31 34
         return content;
32 35
     }
33 36
 
34 37
     @Override
35 38
     public void sendOnNavigationButtonPressed(String buttonId) {
36
-        ReactContext currentReactContext = reactInstanceManager.getCurrentReactContext();
37
-        if (currentReactContext != null) {
38
-            new EventEmitter(currentReactContext).emitOnNavigationButtonPressed(getId(), buttonId);
39
-        }
39
+        emitter.emitOnNavigationButtonPressed(getId(), buttonId);
40 40
     }
41 41
 
42 42
     @Override
@@ -46,6 +46,18 @@ public class ExternalComponentViewController extends ViewController<ExternalComp
46 46
         super.mergeOptions(options);
47 47
     }
48 48
 
49
+    @Override
50
+    public void onViewAppeared() {
51
+        super.onViewAppeared();
52
+        emitter.emitComponentDidAppear(getId(), externalComponent.name.get());
53
+    }
54
+
55
+    @Override
56
+    public void onViewDisappear() {
57
+        super.onViewDisappear();
58
+        emitter.emitComponentDidDisappear(getId(), externalComponent.name.get());
59
+    }
60
+
49 61
     public FragmentActivity getActivity() {
50 62
         return (FragmentActivity) super.getActivity();
51 63
     }

+ 23
- 7
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ExternalComponentViewControllerTest.java 파일 보기

@@ -9,6 +9,7 @@ import com.reactnativenavigation.BaseTest;
9 9
 import com.reactnativenavigation.parse.ExternalComponent;
10 10
 import com.reactnativenavigation.parse.Options;
11 11
 import com.reactnativenavigation.parse.params.Text;
12
+import com.reactnativenavigation.react.EventEmitter;
12 13
 import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalComponentViewController;
13 14
 import com.reactnativenavigation.viewcontrollers.externalcomponent.FragmentCreatorMock;
14 15
 import com.reactnativenavigation.views.ExternalComponentLayout;
@@ -28,6 +29,7 @@ public class ExternalComponentViewControllerTest extends BaseTest {
28 29
     private Activity activity;
29 30
     private ExternalComponent ec;
30 31
     private ReactInstanceManager reactInstanceManager;
32
+    private EventEmitter emitter;
31 33
 
32 34
     @Override
33 35
     public void beforeEach() {
@@ -35,22 +37,17 @@ public class ExternalComponentViewControllerTest extends BaseTest {
35 37
         activity = newActivity();
36 38
         ec = createExternalComponent();
37 39
         reactInstanceManager = Mockito.mock(ReactInstanceManager.class);
40
+        emitter = Mockito.mock(EventEmitter.class);
38 41
         uut = spy(new ExternalComponentViewController(activity,
39 42
                 "fragmentId",
40 43
                 ec,
41 44
                 componentCreator,
42 45
                 reactInstanceManager,
46
+                emitter,
43 47
                 new Options())
44 48
         );
45 49
     }
46 50
 
47
-    private ExternalComponent createExternalComponent() {
48
-        ExternalComponent component = new ExternalComponent();
49
-        component.name = new Text("fragmentComponent");
50
-        component.passProps = new JSONObject();
51
-        return component;
52
-    }
53
-
54 51
     @Test
55 52
     public void createView_returnsFrameLayout() {
56 53
         ExternalComponentLayout view = uut.getView();
@@ -63,4 +60,23 @@ public class ExternalComponentViewControllerTest extends BaseTest {
63 60
         verify(componentCreator, times(1)).create((FragmentActivity) activity, reactInstanceManager, ec.passProps);
64 61
         assertThat(view.getChildCount()).isGreaterThan(0);
65 62
     }
63
+
64
+    @Test
65
+    public void onViewAppeared_appearEventIsEmitted() {
66
+        uut.onViewAppeared();
67
+        verify(emitter).emitComponentDidAppear(uut.getId(), ec.name.get());
68
+    }
69
+
70
+    @Test
71
+    public void onViewDisappear_disappearEventIsEmitted() {
72
+        uut.onViewDisappear();
73
+        verify(emitter).emitComponentDidDisappear(uut.getId(), ec.name.get());
74
+    }
75
+
76
+    private ExternalComponent createExternalComponent() {
77
+        ExternalComponent component = new ExternalComponent();
78
+        component.name = new Text("fragmentComponent");
79
+        component.passProps = new JSONObject();
80
+        return component;
81
+    }
66 82
 }

+ 55
- 0
playground/src/screens/ExternalComponentScreen.js 파일 보기

@@ -0,0 +1,55 @@
1
+const React = require('react');
2
+const Root = require('../components/Root');
3
+const Button = require('../components/Button');
4
+const Screens = require('./Screens');
5
+const Navigation = require('../services/Navigation');
6
+const { stack } = require('../commons/Layouts');
7
+const {
8
+  PUSH_BTN,
9
+  MODAL_BTN
10
+} = require('../testIDs');
11
+
12
+class ExternalComponentScreen extends React.Component {
13
+  static options() {
14
+    return {
15
+      topBar: {
16
+        title: {
17
+          text: 'External Component'
18
+        }
19
+      }
20
+    }
21
+  }
22
+
23
+  render() {
24
+    return (
25
+      <Root componentId={this.props.componentId}>
26
+        <Button label='Push' testID={PUSH_BTN} onPress={this.push} />
27
+        <Button label='Show Modal' testID={MODAL_BTN} onPress={this.modal} />
28
+      </Root>
29
+    );
30
+  }
31
+
32
+  push = () => Navigation.push(this, {
33
+    externalComponent: {
34
+      name: Screens.NativeScreen,
35
+      passProps: {
36
+        text: 'This is an external component'
37
+      }
38
+    }
39
+  });
40
+  modal = () => Navigation.showModal(
41
+    stack([
42
+      Screens.Pushed,
43
+      {
44
+        externalComponent: {
45
+          name: Screens.NativeScreen,
46
+          passProps: {
47
+            text: 'External component in deep stack'
48
+          }
49
+        }
50
+      }
51
+    ])
52
+  );
53
+}
54
+
55
+module.exports = ExternalComponentScreen;

+ 2
- 1
playground/src/screens/NavigationScreen.js 파일 보기

@@ -8,7 +8,8 @@ const {
8 8
   OVERLAY_BTN,
9 9
   EXTERNAL_COMP_BTN,
10 10
   SHOW_STATIC_EVENTS_SCREEN,
11
-  SHOW_ORIENTATION_SCREEN
11
+  SHOW_ORIENTATION_SCREEN,
12
+  TOP_BAR_ELEMENT
12 13
 } = require('../testIDs');
13 14
 const Screens = require('./Screens');
14 15
 

+ 1
- 0
playground/src/screens/index.js 파일 보기

@@ -38,6 +38,7 @@ function registerScreens() {
38 38
   Navigation.registerComponent(Screens.Orientation, () => require('./OrientationScreen'));
39 39
   Navigation.registerComponent(Screens.OrientationDetect, () => require('./OrientationDetectScreen'));
40 40
   Navigation.registerComponent(Screens.Search, () => require('./SearchScreen'));
41
+  Navigation.registerComponent(Screens.ExternalComponent, () => require('./ExternalComponentScreen'));
41 42
 
42 43
   Navigation.registerComponent(`navigation.playground.CustomTransitionDestination`, () => CustomTransitionDestination);
43 44
   Navigation.registerComponent(`navigation.playground.CustomTransitionOrigin`, () => CustomTransitionOrigin);