Browse Source

External component lifecycle events (#4872)

* Add external component e2e
* Emit appear and disappear events for external components
Guy Carmeli 5 years ago
parent
commit
602c669b02
No account linked to committer's email address

+ 23
- 0
e2e/ExternalComponent.test.js View File

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 View File

158
                 externalComponent,
158
                 externalComponent,
159
                 externalComponentCreators.get(externalComponent.name.get()),
159
                 externalComponentCreators.get(externalComponent.name.get()),
160
                 reactInstanceManager,
160
                 reactInstanceManager,
161
+                new EventEmitter(reactInstanceManager.getCurrentReactContext()),
161
                 parse(typefaceManager, node.getOptions())
162
                 parse(typefaceManager, node.getOptions())
162
         );
163
         );
163
     }
164
     }

+ 60
- 51
lib/android/app/src/main/java/com/reactnativenavigation/react/EventEmitter.java View File

1
 package com.reactnativenavigation.react;
1
 package com.reactnativenavigation.react;
2
 
2
 
3
+import android.util.Log;
4
+
3
 import com.facebook.react.bridge.Arguments;
5
 import com.facebook.react.bridge.Arguments;
4
 import com.facebook.react.bridge.ReactContext;
6
 import com.facebook.react.bridge.ReactContext;
5
 import com.facebook.react.bridge.WritableMap;
7
 import com.facebook.react.bridge.WritableMap;
6
 import com.facebook.react.modules.core.DeviceEventManagerModule;
8
 import com.facebook.react.modules.core.DeviceEventManagerModule;
7
 
9
 
10
+import javax.annotation.Nullable;
11
+
8
 import static com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
12
 import static com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
9
 
13
 
10
 public class EventEmitter {
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
     public void emitModalDismissed(String id, int modalsDismissed) {
68
     public void emitModalDismissed(String id, int modalsDismissed) {
65
         WritableMap event = Arguments.createMap();
69
         WritableMap event = Arguments.createMap();
68
         emit(ModalDismissed, event);
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 View File

68
 	public void sendComponentStart() {
68
 	public void sendComponentStart() {
69
         ReactContext currentReactContext = reactInstanceManager.getCurrentReactContext();
69
         ReactContext currentReactContext = reactInstanceManager.getCurrentReactContext();
70
         if (currentReactContext != null) {
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
 	public void sendComponentStop() {
76
 	public void sendComponentStop() {
77
         ReactContext currentReactContext = reactInstanceManager.getCurrentReactContext();
77
         ReactContext currentReactContext = reactInstanceManager.getCurrentReactContext();
78
         if (currentReactContext != null) {
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 View File

4
 import android.support.v4.app.FragmentActivity;
4
 import android.support.v4.app.FragmentActivity;
5
 
5
 
6
 import com.facebook.react.ReactInstanceManager;
6
 import com.facebook.react.ReactInstanceManager;
7
-import com.facebook.react.bridge.ReactContext;
8
 import com.reactnativenavigation.parse.ExternalComponent;
7
 import com.reactnativenavigation.parse.ExternalComponent;
9
 import com.reactnativenavigation.parse.Options;
8
 import com.reactnativenavigation.parse.Options;
10
 import com.reactnativenavigation.react.EventEmitter;
9
 import com.reactnativenavigation.react.EventEmitter;
16
     private final ExternalComponent externalComponent;
15
     private final ExternalComponent externalComponent;
17
     private final ExternalComponentCreator componentCreator;
16
     private final ExternalComponentCreator componentCreator;
18
     private ReactInstanceManager reactInstanceManager;
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
         super(activity, id, new NoOpYellowBoxDelegate(), initialOptions);
21
         super(activity, id, new NoOpYellowBoxDelegate(), initialOptions);
22
         this.externalComponent = externalComponent;
22
         this.externalComponent = externalComponent;
23
         this.componentCreator = componentCreator;
23
         this.componentCreator = componentCreator;
24
         this.reactInstanceManager = reactInstanceManager;
24
         this.reactInstanceManager = reactInstanceManager;
25
+        this.emitter = emitter;
25
     }
26
     }
26
 
27
 
27
     @Override
28
     @Override
28
     protected ExternalComponentLayout createView() {
29
     protected ExternalComponentLayout createView() {
29
         ExternalComponentLayout content = new ExternalComponentLayout(getActivity());
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
         return content;
34
         return content;
32
     }
35
     }
33
 
36
 
34
     @Override
37
     @Override
35
     public void sendOnNavigationButtonPressed(String buttonId) {
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
     @Override
42
     @Override
46
         super.mergeOptions(options);
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
     public FragmentActivity getActivity() {
61
     public FragmentActivity getActivity() {
50
         return (FragmentActivity) super.getActivity();
62
         return (FragmentActivity) super.getActivity();
51
     }
63
     }

+ 23
- 7
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ExternalComponentViewControllerTest.java View File

9
 import com.reactnativenavigation.parse.ExternalComponent;
9
 import com.reactnativenavigation.parse.ExternalComponent;
10
 import com.reactnativenavigation.parse.Options;
10
 import com.reactnativenavigation.parse.Options;
11
 import com.reactnativenavigation.parse.params.Text;
11
 import com.reactnativenavigation.parse.params.Text;
12
+import com.reactnativenavigation.react.EventEmitter;
12
 import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalComponentViewController;
13
 import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalComponentViewController;
13
 import com.reactnativenavigation.viewcontrollers.externalcomponent.FragmentCreatorMock;
14
 import com.reactnativenavigation.viewcontrollers.externalcomponent.FragmentCreatorMock;
14
 import com.reactnativenavigation.views.ExternalComponentLayout;
15
 import com.reactnativenavigation.views.ExternalComponentLayout;
28
     private Activity activity;
29
     private Activity activity;
29
     private ExternalComponent ec;
30
     private ExternalComponent ec;
30
     private ReactInstanceManager reactInstanceManager;
31
     private ReactInstanceManager reactInstanceManager;
32
+    private EventEmitter emitter;
31
 
33
 
32
     @Override
34
     @Override
33
     public void beforeEach() {
35
     public void beforeEach() {
35
         activity = newActivity();
37
         activity = newActivity();
36
         ec = createExternalComponent();
38
         ec = createExternalComponent();
37
         reactInstanceManager = Mockito.mock(ReactInstanceManager.class);
39
         reactInstanceManager = Mockito.mock(ReactInstanceManager.class);
40
+        emitter = Mockito.mock(EventEmitter.class);
38
         uut = spy(new ExternalComponentViewController(activity,
41
         uut = spy(new ExternalComponentViewController(activity,
39
                 "fragmentId",
42
                 "fragmentId",
40
                 ec,
43
                 ec,
41
                 componentCreator,
44
                 componentCreator,
42
                 reactInstanceManager,
45
                 reactInstanceManager,
46
+                emitter,
43
                 new Options())
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
     @Test
51
     @Test
55
     public void createView_returnsFrameLayout() {
52
     public void createView_returnsFrameLayout() {
56
         ExternalComponentLayout view = uut.getView();
53
         ExternalComponentLayout view = uut.getView();
63
         verify(componentCreator, times(1)).create((FragmentActivity) activity, reactInstanceManager, ec.passProps);
60
         verify(componentCreator, times(1)).create((FragmentActivity) activity, reactInstanceManager, ec.passProps);
64
         assertThat(view.getChildCount()).isGreaterThan(0);
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 View File

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 View File

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

+ 1
- 0
playground/src/screens/index.js View File

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