Kaynağa Gözat

Add YellowBoxed back to ReactRootViews before unmount

Apparently, UiImplementation doesn't like it when views disappear from hierarchy unexpectedly.
As yellow boxes are added to all ReactRootViews, we remove them from TopBar components.
When these components got unmounted, UiImplementation tried to remove the yellow box and since it
couldn't find it - it crashed.

This commit simply adds the yellow box before views are unmounted.
Guy Carmeli 6 yıl önce
ebeveyn
işleme
97b83e8124

+ 8
- 0
e2e/ScreenStyle.test.js Dosyayı Görüntüle

@@ -1,4 +1,5 @@
1 1
 const Utils = require('./Utils');
2
+const Android = require('./AndroidUtils');
2 3
 const testIDs = require('../playground/src/testIDs');
3 4
 
4 5
 const { elementById, elementByLabel } = Utils;
@@ -175,4 +176,11 @@ describe('screen style', () => {
175 176
     await expect(elementById(testIDs.TOP_BAR_ELEMENT)).toBeVisible();
176 177
     await expect(elementById(testIDs.TOP_BAR_BUTTON)).toBeVisible();
177 178
   });
179
+
180
+  test(':android: Popping screen with yellow box in button, title and background components should not crash', async () => {
181
+    await elementById(testIDs.PUSH_OPTIONS_BUTTON).tap();
182
+    await elementById(testIDs.SHOW_YELLOW_BOX).tap();
183
+    Android.pressBack();
184
+    await expect(elementByLabel('React Native Navigation!')).toBeVisible();    
185
+  });
178 186
 });

+ 1
- 1
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ChildController.java Dosyayı Görüntüle

@@ -17,7 +17,7 @@ public abstract class ChildController<T extends ViewGroup> extends ViewControlle
17 17
     }
18 18
 
19 19
     public ChildController(Activity activity, ChildControllersRegistry childRegistry, String id, OptionsPresenter presenter, Options initialOptions) {
20
-        super(activity, id, initialOptions);
20
+        super(activity, id, new NoOpYellowBoxDelegate(), initialOptions);
21 21
         this.presenter = presenter;
22 22
         this.childRegistry = childRegistry;
23 23
     }

+ 10
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/NoOpYellowBoxDelegate.java Dosyayı Görüntüle

@@ -0,0 +1,10 @@
1
+package com.reactnativenavigation.viewcontrollers;
2
+
3
+import android.view.View;
4
+
5
+public class NoOpYellowBoxDelegate extends YellowBoxDelegate {
6
+    @Override
7
+    public void onChildViewAdded(View parent, View child) {
8
+
9
+    }
10
+}

+ 1
- 8
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/TitleBarButtonController.java Dosyayı Görüntüle

@@ -9,8 +9,6 @@ import android.support.annotation.RestrictTo;
9 9
 import android.support.v7.widget.ActionMenuView;
10 10
 import android.support.v7.widget.Toolbar;
11 11
 import android.view.MenuItem;
12
-import android.view.View;
13
-import android.view.ViewManager;
14 12
 import android.widget.ImageButton;
15 13
 import android.widget.TextView;
16 14
 
@@ -58,7 +56,7 @@ public class TitleBarButtonController extends ViewController<TitleBarReactButton
58 56
                                     Button button,
59 57
                                     ReactViewCreator viewCreator,
60 58
                                     OnClickListener onClickListener) {
61
-        super(activity, button.id, new Options());
59
+        super(activity, button.id, new YellowBoxDelegate(), new Options());
62 60
         this.navigationIconResolver = navigationIconResolver;
63 61
         this.imageLoader = imageLoader;
64 62
         this.optionsPresenter = optionsPresenter;
@@ -97,11 +95,6 @@ public class TitleBarButtonController extends ViewController<TitleBarReactButton
97 95
         return true;
98 96
     }
99 97
 
100
-    @Override
101
-    protected void onYellowBoxAdded(View yellowBox) {
102
-        ((ViewManager) yellowBox.getParent()).removeView(yellowBox);
103
-    }
104
-
105 98
     public void applyNavigationIcon(Toolbar toolbar) {
106 99
         navigationIconResolver.resolve(button, icon -> {
107 100
             setIconColor(icon);

+ 1
- 8
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/TitleBarReactViewController.java Dosyayı Görüntüle

@@ -1,8 +1,6 @@
1 1
 package com.reactnativenavigation.viewcontrollers;
2 2
 
3 3
 import android.app.Activity;
4
-import android.view.View;
5
-import android.view.ViewManager;
6 4
 
7 5
 import com.reactnativenavigation.parse.Component;
8 6
 import com.reactnativenavigation.parse.Options;
@@ -16,7 +14,7 @@ public class TitleBarReactViewController extends ViewController<TitleBarReactVie
16 14
     private Component component;
17 15
 
18 16
     public TitleBarReactViewController(Activity activity, TitleBarReactViewCreator reactViewCreator) {
19
-        super(activity, CompatUtils.generateViewId() + "", new Options());
17
+        super(activity, CompatUtils.generateViewId() + "", new YellowBoxDelegate(), new Options());
20 18
         this.reactViewCreator = reactViewCreator;
21 19
     }
22 20
 
@@ -43,11 +41,6 @@ public class TitleBarReactViewController extends ViewController<TitleBarReactVie
43 41
 
44 42
     }
45 43
 
46
-    @Override
47
-    protected void onYellowBoxAdded(View yellowBox) {
48
-        ((ViewManager) yellowBox.getParent()).removeView(yellowBox);
49
-    }
50
-
51 44
     public void setComponent(Component component) {
52 45
         this.component = component;
53 46
     }

+ 5
- 10
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ViewController.java Dosyayı Görüntüle

@@ -50,6 +50,7 @@ public abstract class ViewController<T extends ViewGroup> implements ViewTreeObs
50 50
 
51 51
     private final Activity activity;
52 52
     private final String id;
53
+    private YellowBoxDelegate yellowBoxDelegate;
53 54
     protected T view;
54 55
     @Nullable private ParentController<T> parentController;
55 56
     private boolean isShown;
@@ -57,9 +58,10 @@ public abstract class ViewController<T extends ViewGroup> implements ViewTreeObs
57 58
     private ViewVisibilityListener viewVisibilityListener = new ViewVisibilityListenerAdapter();
58 59
     protected FabOptionsPresenter fabOptionsPresenter;
59 60
 
60
-    public ViewController(Activity activity, String id, Options initialOptions) {
61
+    public ViewController(Activity activity, String id, YellowBoxDelegate yellowBoxDelegate, Options initialOptions) {
61 62
         this.activity = activity;
62 63
         this.id = id;
64
+        this.yellowBoxDelegate = yellowBoxDelegate;
63 65
         fabOptionsPresenter = new FabOptionsPresenter();
64 66
         this.initialOptions = initialOptions;
65 67
         options = initialOptions.copy();
@@ -222,6 +224,7 @@ public abstract class ViewController<T extends ViewGroup> implements ViewTreeObs
222 224
             isShown = false;
223 225
             onViewDisappear();
224 226
         }
227
+        yellowBoxDelegate.destroy();
225 228
         if (view instanceof Destroyable) {
226 229
             ((Destroyable) view).destroy();
227 230
         }
@@ -257,11 +260,7 @@ public abstract class ViewController<T extends ViewGroup> implements ViewTreeObs
257 260
 
258 261
     @Override
259 262
     public void onChildViewAdded(View parent, View child) {
260
-        if (parent instanceof ViewGroup &&
261
-            child instanceof ViewGroup &&
262
-            YellowBoxHelper.isYellowBox((ViewGroup) parent, (ViewGroup) child)) {
263
-            onYellowBoxAdded(child);
264
-        }
263
+        yellowBoxDelegate.onChildViewAdded(parent, child);
265 264
     }
266 265
 
267 266
     @Override
@@ -269,10 +268,6 @@ public abstract class ViewController<T extends ViewGroup> implements ViewTreeObs
269 268
 
270 269
     }
271 270
 
272
-    protected void onYellowBoxAdded(View yellowBox) {
273
-
274
-    }
275
-
276 271
     void runOnPreDraw(Task<T> task) {
277 272
         UiUtils.runOnPreDrawOnce(getView(), () -> task.run(getView()));
278 273
     }

+ 50
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/YellowBoxDelegate.java Dosyayı Görüntüle

@@ -0,0 +1,50 @@
1
+package com.reactnativenavigation.viewcontrollers;
2
+
3
+import android.support.annotation.RestrictTo;
4
+import android.view.View;
5
+import android.view.ViewGroup;
6
+
7
+public class YellowBoxDelegate {
8
+    private ViewGroup parent;
9
+    private View yellowBox;
10
+    private YellowBoxHelper yellowBoxHelper;
11
+    private boolean isDestroyed;
12
+
13
+    public YellowBoxDelegate() {
14
+        this.yellowBoxHelper = new YellowBoxHelper();
15
+    }
16
+
17
+    YellowBoxDelegate(YellowBoxHelper yellowBoxHelper) {
18
+        this.yellowBoxHelper = yellowBoxHelper;
19
+    }
20
+
21
+    @RestrictTo(RestrictTo.Scope.TESTS)
22
+    public ViewGroup getParent() {
23
+        return parent;
24
+    }
25
+
26
+    @RestrictTo(RestrictTo.Scope.TESTS)
27
+    public View getYellowBox() {
28
+        return yellowBox;
29
+    }
30
+
31
+    public void onChildViewAdded(View parent, View child) {
32
+        if (yellowBoxHelper.isYellowBox(parent, child)) {
33
+            onYellowBoxAdded(parent, child);
34
+        }
35
+    }
36
+
37
+    protected void onYellowBoxAdded(View parent, View yellowBox) {
38
+        if (isDestroyed) return;
39
+        this.yellowBox = yellowBox;
40
+        this.parent = (ViewGroup) parent;
41
+        this.parent.removeView(yellowBox);
42
+    }
43
+
44
+    public void destroy() {
45
+        isDestroyed = true;
46
+        if (yellowBox != null) {
47
+            parent.addView(yellowBox);
48
+        }
49
+    }
50
+}

+ 5
- 3
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/YellowBoxHelper.java Dosyayı Görüntüle

@@ -10,9 +10,11 @@ import com.reactnativenavigation.utils.ViewUtils;
10 10
 public class YellowBoxHelper {
11 11
     private final static int YELLOW_BOX_COLOR = -218449360;
12 12
 
13
-    public static boolean isYellowBox(ViewGroup parent, ViewGroup child) {
14
-        return parent.getChildCount() > 1 &&
15
-               !ViewUtils.findChildrenByClassRecursive(child, View.class, YellowBackgroundMather()).isEmpty();
13
+    public boolean isYellowBox(View parent, View child) {
14
+        return parent instanceof ViewGroup &&
15
+               child instanceof ViewGroup &&
16
+                ((ViewGroup) parent).getChildCount() > 1 &&
17
+               !ViewUtils.findChildrenByClassRecursive(((ViewGroup) child), View.class, YellowBackgroundMather()).isEmpty();
16 18
     }
17 19
 
18 20
     @NonNull

+ 2
- 1
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/externalcomponent/ExternalComponentViewController.java Dosyayı Görüntüle

@@ -6,6 +6,7 @@ import android.support.v4.app.FragmentActivity;
6 6
 import com.facebook.react.ReactInstanceManager;
7 7
 import com.reactnativenavigation.parse.ExternalComponent;
8 8
 import com.reactnativenavigation.parse.Options;
9
+import com.reactnativenavigation.viewcontrollers.NoOpYellowBoxDelegate;
9 10
 import com.reactnativenavigation.viewcontrollers.ViewController;
10 11
 import com.reactnativenavigation.views.ExternalComponentLayout;
11 12
 
@@ -15,7 +16,7 @@ public class ExternalComponentViewController extends ViewController<ExternalComp
15 16
     private ReactInstanceManager reactInstanceManager;
16 17
 
17 18
     public ExternalComponentViewController(Activity activity, String id, ExternalComponent externalComponent, ExternalComponentCreator componentCreator, ReactInstanceManager reactInstanceManager, Options initialOptions) {
18
-        super(activity, id, initialOptions);
19
+        super(activity, id, new NoOpYellowBoxDelegate(), initialOptions);
19 20
         this.externalComponent = externalComponent;
20 21
         this.componentCreator = componentCreator;
21 22
         this.reactInstanceManager = reactInstanceManager;

+ 3
- 9
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/topbar/TopBarBackgroundViewController.java Dosyayı Görüntüle

@@ -1,13 +1,12 @@
1 1
 package com.reactnativenavigation.viewcontrollers.topbar;
2 2
 
3 3
 import android.app.Activity;
4
-import android.view.View;
5
-import android.view.ViewManager;
6 4
 
7 5
 import com.reactnativenavigation.parse.Component;
8 6
 import com.reactnativenavigation.parse.Options;
9 7
 import com.reactnativenavigation.utils.CompatUtils;
10 8
 import com.reactnativenavigation.viewcontrollers.ViewController;
9
+import com.reactnativenavigation.viewcontrollers.YellowBoxDelegate;
11 10
 import com.reactnativenavigation.views.topbar.TopBarBackgroundView;
12 11
 import com.reactnativenavigation.views.topbar.TopBarBackgroundViewCreator;
13 12
 
@@ -17,12 +16,12 @@ public class TopBarBackgroundViewController extends ViewController<TopBarBackgro
17 16
     private Component component;
18 17
 
19 18
     public TopBarBackgroundViewController(Activity activity, TopBarBackgroundViewCreator viewCreator) {
20
-        super(activity, CompatUtils.generateViewId() + "", new Options());
19
+        super(activity, CompatUtils.generateViewId() + "", new YellowBoxDelegate(), new Options());
21 20
         this.viewCreator = viewCreator;
22 21
     }
23 22
 
24 23
     public TopBarBackgroundViewController(TopBarBackgroundViewController controller) {
25
-        super(controller.getActivity(), controller.getId(), controller.options);
24
+        super(controller.getActivity(), controller.getId(), new YellowBoxDelegate(), controller.options);
26 25
         this.viewCreator = controller.viewCreator;
27 26
     }
28 27
 
@@ -43,11 +42,6 @@ public class TopBarBackgroundViewController extends ViewController<TopBarBackgro
43 42
         super.onViewDisappear();
44 43
     }
45 44
 
46
-    @Override
47
-    protected void onYellowBoxAdded(View yellowBox) {
48
-        ((ViewManager) yellowBox.getParent()).removeView(yellowBox);
49
-    }
50
-
51 45
     @Override
52 46
     public void sendOnNavigationButtonPressed(String buttonId) {
53 47
 

+ 26
- 4
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ViewControllerTest.java Dosyayı Görüntüle

@@ -36,14 +36,16 @@ public class ViewControllerTest extends BaseTest {
36 36
     private ViewController uut;
37 37
     private Activity activity;
38 38
     private ChildControllersRegistry childRegistry;
39
+    private YellowBoxDelegate yellowBoxDelegate;
39 40
 
40 41
     @Override
41 42
     public void beforeEach() {
42 43
         super.beforeEach();
44
+        yellowBoxDelegate = Mockito.mock(YellowBoxDelegate.class);
43 45
         activity = newActivity();
44 46
         childRegistry = new ChildControllersRegistry();
45 47
         uut = new SimpleViewController(activity, childRegistry, "uut", new Options());
46
-        uut.setParentController(Mockito.mock(ParentController.class));
48
+        uut.setParentController(mock(ParentController.class));
47 49
     }
48 50
 
49 51
     @Test
@@ -59,7 +61,8 @@ public class ViewControllerTest extends BaseTest {
59 61
     @Test
60 62
     public void canOverrideViewCreation() {
61 63
         final FrameLayout otherView = new FrameLayout(activity);
62
-        ViewController myController = new ViewController(activity, "vc", new Options()) {
64
+        yellowBoxDelegate = spy(new YellowBoxDelegate());
65
+        ViewController myController = new ViewController(activity, "vc", yellowBoxDelegate, new Options()) {
63 66
             @Override
64 67
             protected FrameLayout createView() {
65 68
                 return otherView;
@@ -108,6 +111,25 @@ public class ViewControllerTest extends BaseTest {
108 111
         assertThat(uut.findControllerById("uut")).isEqualTo(uut);
109 112
     }
110 113
 
114
+    @Test
115
+    public void onChildViewAdded_delegatesToYellowBoxDelegate() {
116
+        View child = new View(activity);
117
+        ViewGroup view = new FrameLayout(activity);
118
+        ViewController vc = new ViewController(activity, "", yellowBoxDelegate, new Options()) {
119
+            @Override
120
+            protected ViewGroup createView() {
121
+                return view;
122
+            }
123
+
124
+            @Override
125
+            public void sendOnNavigationButtonPressed(String buttonId) {
126
+
127
+            }
128
+        };
129
+        vc.onChildViewAdded(view, child);
130
+        verify(yellowBoxDelegate).onChildViewAdded(view, child);
131
+    }
132
+
111 133
     @Test
112 134
     public void onAppear_WhenShown() {
113 135
         ViewController spy = spy(uut);
@@ -237,7 +259,7 @@ public class ViewControllerTest extends BaseTest {
237 259
     @Test
238 260
     public void isRendered_delegatesToView() {
239 261
         uut.setWaitForRender(new Bool(true));
240
-        uut.view = Mockito.mock(ViewGroup.class, withSettings().extraInterfaces(Component.class));
262
+        uut.view = mock(ViewGroup.class, withSettings().extraInterfaces(Component.class));
241 263
         uut.isRendered();
242 264
         verify((Component) uut.view).isRendered();
243 265
     }
@@ -245,7 +267,7 @@ public class ViewControllerTest extends BaseTest {
245 267
     @Test
246 268
     public void isRendered_returnsTrueForEveryViewByDefault() {
247 269
         uut.setWaitForRender(new NullBool());
248
-        uut.view = Mockito.mock(ViewGroup.class);
270
+        uut.view = mock(ViewGroup.class);
249 271
         assertThat(uut.isRendered()).isTrue();
250 272
     }
251 273
 }

+ 66
- 0
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/YellowBoxDelegateTest.java Dosyayı Görüntüle

@@ -0,0 +1,66 @@
1
+package com.reactnativenavigation.viewcontrollers;
2
+
3
+import android.app.Activity;
4
+import android.view.View;
5
+import android.view.ViewGroup;
6
+import android.widget.FrameLayout;
7
+
8
+import com.reactnativenavigation.BaseTest;
9
+
10
+import org.junit.Test;
11
+import org.mockito.Mockito;
12
+
13
+import static org.assertj.core.api.Java6Assertions.assertThat;
14
+import static org.mockito.Mockito.verify;
15
+
16
+public class YellowBoxDelegateTest extends BaseTest {
17
+    private YellowBoxDelegate uut;
18
+    private YellowBoxHelper yellowBoxHelper;
19
+    private View yellowBox;
20
+    private ViewGroup parent;
21
+
22
+    @Override
23
+    public void beforeEach() {
24
+        Activity context = newActivity();
25
+        yellowBox = new View(context);
26
+        parent = new FrameLayout(context);
27
+        yellowBoxHelper = Mockito.mock(YellowBoxHelper.class);
28
+        uut = new YellowBoxDelegate(yellowBoxHelper);
29
+        parent.addView(yellowBox);
30
+    }
31
+
32
+    @Test
33
+    public void onYellowBoxAdded_removedFromParent() {
34
+        uut.onYellowBoxAdded(parent, yellowBox);
35
+        assertThat(yellowBox.getParent()).isNull();
36
+    }
37
+
38
+    @Test
39
+    public void onYellowBoxAdded_storesRefToYellowBoxAndParent() {
40
+        uut.onYellowBoxAdded(parent, yellowBox);
41
+        assertThat(uut.getYellowBox()).isEqualTo(yellowBox);
42
+        assertThat(uut.getParent()).isEqualTo(parent);
43
+    }
44
+
45
+    @Test
46
+    public void onReactViewDestroy_yellowBoxIsAddedBackToParent() {
47
+        uut.onYellowBoxAdded(parent, yellowBox);
48
+        uut.destroy();
49
+        assertThat(yellowBox.getParent()).isEqualTo(parent);
50
+    }
51
+
52
+    @Test
53
+    public void onChildViewAdded() {
54
+        uut.onChildViewAdded(parent, yellowBox);
55
+        verify(yellowBoxHelper).isYellowBox(parent, yellowBox);
56
+    }
57
+
58
+    @Test
59
+    public void onYellowBoxAdded_notHandledIfDelegateIsDestroyed() {
60
+        uut.onYellowBoxAdded(parent, yellowBox);
61
+        uut.destroy();
62
+
63
+        uut.onYellowBoxAdded(parent, yellowBox);
64
+        assertThat(yellowBox.getParent()).isEqualTo(parent);
65
+    }
66
+}

+ 1
- 0
playground/src/screens/OptionsScreen.js Dosyayı Görüntüle

@@ -137,6 +137,7 @@ class OptionsScreen extends Component {
137 137
         <Button title='Push Default Options Screen' testID={testIDs.PUSH_DEFAULT_OPTIONS_BUTTON} onPress={this.onClickPushDefaultOptionsScreen} />
138 138
         <Button title='Show TopBar react view' testID={testIDs.SHOW_TOPBAR_REACT_VIEW} onPress={this.onShowTopBarReactView} />
139 139
         {Platform.OS === 'android' && <Button title='Push' testID={testIDs.PUSH_BUTTON} onPress={this.onPush} />}
140
+        <Button title='Show Yellow Box' testID={testIDs.SHOW_YELLOW_BOX} onPress={() => console.warn('Yellow Box')} />
140 141
         <Text style={styles.footer}>{`this.props.containerId = ${this.props.containerId}`}</Text>
141 142
       </View>
142 143
     );

+ 1
- 0
playground/src/testIDs.js Dosyayı Görüntüle

@@ -7,6 +7,7 @@ module.exports = {
7 7
   PUSH_LIFECYCLE_BUTTON: `PUSH_LIFECYCLE_BUTTON`,
8 8
   PUSH_STATIC_LIFECYCLE_BUTTON: `PUSH_STATIC_LIFECYCLE_BUTTON`,
9 9
   PUSH_BUTTON: `PUSH_BUTTON`,
10
+  SHOW_YELLOW_BOX: `SHOW_YELLOW_BOX`,
10 11
   SIDE_MENU_PUSH_BUTTON: `SIDE_MENU_PUSH_BUTTON`,
11 12
   LEFT_SIDE_MENU_PUSH_BUTTON: `leftSIDE_MENU_PUSH_BUTTON`,
12 13
   RIGHT_SIDE_MENU_PUSH_BUTTON: `rightSIDE_MENU_PUSH_BUTTON`,

+ 1
- 1
scripts/test-e2e.js Dosyayı Görüntüle

@@ -21,5 +21,5 @@ function run() {
21 21
     if (!skipBuild) {
22 22
         exec.execSync(`detox build --configuration ${configuration}`);
23 23
     }
24
-    exec.execSync(`detox test --configuration ${configuration} --platform ${platform} ${cleanup} ${headless$} ${!android ? `-w ${workers}` : ``}`); //--loglevel trace
24
+    exec.execSync(`detox test --configuration ${configuration} --platform ${platform} ${cleanup} ${headless$} ${!android ? `-w ${workers}` : ``}`); //-f "ScreenStyle.test.js" --loglevel trace
25 25
 }