Browse Source

Apply resolved options each time a child controller appears

This commit fixes #4045, which revealed a design flaw in the options applying logic.
Child options are applied when a ViewController.onViewAppeared is invoked.
A ViewController's appearance is determined from a GlobalLayoutListener.

Consider the following layout hierarchy:
BottomTabs > Stack > Component

onViewAppeared will be triggered for the BottomTabs first, then for the Stack and last for the component.
This means that if the Stack and the Component options will be applied consecutively which in some cases
might case flickering, mostly when drawBehind options are applied.
Guy Carmeli 6 years ago
parent
commit
c5b77de568

+ 1
- 1
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ParentController.java View File

96
 
96
 
97
     @CallSuper
97
     @CallSuper
98
     public void applyChildOptions(Options options, Component child) {
98
     public void applyChildOptions(Options options, Component child) {
99
-        this.options = this.initialOptions.mergeWith(options);
99
+        this.options = initialOptions.mergeWith(options);
100
         if (isRoot()) {
100
         if (isRoot()) {
101
             presenter.applyRootOptions(getView(), options);
101
             presenter.applyRootOptions(getView(), options);
102
         }
102
         }

+ 7
- 2
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java View File

91
     @Override
91
     @Override
92
     public void applyChildOptions(Options options, Component child) {
92
     public void applyChildOptions(Options options, Component child) {
93
         super.applyChildOptions(options, child);
93
         super.applyChildOptions(options, child);
94
-        presenter.applyChildOptions(this.options, child);
94
+        presenter.applyChildOptions(resolveCurrentOptions(), child);
95
         performOnParentController(parentController ->
95
         performOnParentController(parentController ->
96
-                ((ParentController) parentController).applyChildOptions(this.options.copy().clearBottomTabsOptions().clearBottomTabOptions(), child)
96
+                ((ParentController) parentController).applyChildOptions(
97
+                        this.options.copy()
98
+                                .clearBottomTabsOptions()
99
+                                .clearBottomTabOptions(),
100
+                        child
101
+                )
97
         );
102
         );
98
     }
103
     }
99
 
104
 

+ 42
- 5
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/BottomTabsControllerTest.java View File

11
 import com.reactnativenavigation.TestUtils;
11
 import com.reactnativenavigation.TestUtils;
12
 import com.reactnativenavigation.mocks.ImageLoaderMock;
12
 import com.reactnativenavigation.mocks.ImageLoaderMock;
13
 import com.reactnativenavigation.mocks.SimpleViewController;
13
 import com.reactnativenavigation.mocks.SimpleViewController;
14
-import com.reactnativenavigation.mocks.TitleBarReactViewCreatorMock;
15
-import com.reactnativenavigation.mocks.TopBarButtonCreatorMock;
16
 import com.reactnativenavigation.parse.Options;
14
 import com.reactnativenavigation.parse.Options;
17
 import com.reactnativenavigation.parse.params.Bool;
15
 import com.reactnativenavigation.parse.params.Bool;
18
 import com.reactnativenavigation.parse.params.Colour;
16
 import com.reactnativenavigation.parse.params.Colour;
21
 import com.reactnativenavigation.presentation.BottomTabOptionsPresenter;
19
 import com.reactnativenavigation.presentation.BottomTabOptionsPresenter;
22
 import com.reactnativenavigation.presentation.BottomTabsOptionsPresenter;
20
 import com.reactnativenavigation.presentation.BottomTabsOptionsPresenter;
23
 import com.reactnativenavigation.presentation.OptionsPresenter;
21
 import com.reactnativenavigation.presentation.OptionsPresenter;
24
-import com.reactnativenavigation.presentation.StackOptionsPresenter;
25
 import com.reactnativenavigation.react.EventEmitter;
22
 import com.reactnativenavigation.react.EventEmitter;
26
 import com.reactnativenavigation.utils.CommandListenerAdapter;
23
 import com.reactnativenavigation.utils.CommandListenerAdapter;
27
 import com.reactnativenavigation.utils.ImageLoader;
24
 import com.reactnativenavigation.utils.ImageLoader;
36
 import org.mockito.ArgumentCaptor;
33
 import org.mockito.ArgumentCaptor;
37
 import org.mockito.Mockito;
34
 import org.mockito.Mockito;
38
 
35
 
36
+import java.util.ArrayList;
39
 import java.util.Arrays;
37
 import java.util.Arrays;
40
 import java.util.List;
38
 import java.util.List;
41
 
39
 
40
+import edu.emory.mathcs.backport.java.util.Collections;
41
+
42
 import static com.reactnativenavigation.TestUtils.hideBackButton;
42
 import static com.reactnativenavigation.TestUtils.hideBackButton;
43
 import static org.assertj.core.api.Java6Assertions.assertThat;
43
 import static org.assertj.core.api.Java6Assertions.assertThat;
44
 import static org.mockito.ArgumentMatchers.any;
44
 import static org.mockito.ArgumentMatchers.any;
211
         assertThat(childLayoutParams(0).bottomMargin).isEqualTo(0);
211
         assertThat(childLayoutParams(0).bottomMargin).isEqualTo(0);
212
     }
212
     }
213
 
213
 
214
+    @Test
215
+    public void applyChildOptions_resolvedOptionsAreUsed() {
216
+        Options childOptions = new Options();
217
+        SimpleViewController pushedScreen = new SimpleViewController(activity, childRegistry, "child4.1", childOptions);
218
+        disablePushAnimation(pushedScreen);
219
+        child4 = createStack(pushedScreen);
220
+
221
+        tabs = new ArrayList<>(Collections.singletonList(child4));
222
+
223
+        initialOptions.bottomTabsOptions.currentTabIndex = new Number(3);
224
+        Options resolvedOptions = new Options();
225
+        uut = new BottomTabsController(activity,
226
+                tabs,
227
+                childRegistry,
228
+                eventEmitter,
229
+                imageLoaderMock,
230
+                "uut",
231
+                initialOptions,
232
+                new OptionsPresenter(activity, new Options()),
233
+                presenter,
234
+                new BottomTabOptionsPresenter(activity, tabs, new Options())) {
235
+            @Override
236
+            public Options resolveCurrentOptions() {
237
+                return resolvedOptions;
238
+            }
239
+        };
240
+
241
+        activity.setContentView(uut.getView());
242
+        verify(presenter, times(2)).applyChildOptions(eq(resolvedOptions), any());
243
+    }
244
+
214
     @Test
245
     @Test
215
     public void child_mergeOptions_currentTabIndex() {
246
     public void child_mergeOptions_currentTabIndex() {
216
         uut.ensureViewIsCreated();
247
         uut.ensureViewIsCreated();
285
         return TestUtils.newStackController(activity)
316
         return TestUtils.newStackController(activity)
286
                 .setId(id)
317
                 .setId(id)
287
                 .setInitialOptions(tabOptions)
318
                 .setInitialOptions(tabOptions)
288
-                .setStackPresenter(new StackOptionsPresenter(activity, new TitleBarReactViewCreatorMock(), new TopBarButtonCreatorMock(), new ImageLoader(), new Options()))
319
+                .build();
320
+    }
321
+
322
+    private StackController createStack(ViewController initialChild) {
323
+        return TestUtils.newStackController(activity)
324
+                .setInitialOptions(tabOptions)
325
+                .setChildren(new ArrayList<>(Collections.singleton(initialChild)))
289
                 .build();
326
                 .build();
290
     }
327
     }
291
 
328
 
293
         return (ViewGroup.MarginLayoutParams) tabs.get(index).getView().getLayoutParams();
330
         return (ViewGroup.MarginLayoutParams) tabs.get(index).getView().getLayoutParams();
294
     }
331
     }
295
 
332
 
296
-    public BottomTabsController createBottomTabs() {
333
+    private BottomTabsController createBottomTabs() {
297
         return new BottomTabsController(activity,
334
         return new BottomTabsController(activity,
298
                 tabs,
335
                 tabs,
299
                 childRegistry,
336
                 childRegistry,

+ 11
- 1
playground/src/screens/ComplexLayout.js View File

64
                 children: [
64
                 children: [
65
                   {
65
                   {
66
                     component: {
66
                     component: {
67
-                      name: 'navigation.playground.TextScreen'
67
+                      name: 'navigation.playground.TextScreen',
68
+                      options: {
69
+                        topBar: {
70
+                          animate: false
71
+                        }
72
+                      }
68
                     }
73
                     }
69
                   }
74
                   }
70
                 ],
75
                 ],
81
                 left: {
86
                 left: {
82
                   component: {
87
                   component: {
83
                     name: 'navigation.playground.SideMenuScreen',
88
                     name: 'navigation.playground.SideMenuScreen',
89
+                    options: {
90
+                      topBar: {
91
+                        animate: false
92
+                      }
93
+                    },
84
                     passProps: {
94
                     passProps: {
85
                       side: 'left'
95
                       side: 'left'
86
                     }
96
                     }

+ 3
- 1
playground/src/screens/WelcomeScreen.js View File

79
                       options: {
79
                       options: {
80
                         topBar: {
80
                         topBar: {
81
                           visible: true,
81
                           visible: true,
82
+                          animate: false,
82
                           title: {
83
                           title: {
83
                             text: 'React Native Navigation!'
84
                             text: 'React Native Navigation!'
84
                           }
85
                           }
130
                 },
131
                 },
131
                 options: {
132
                 options: {
132
                   topBar: {
133
                   topBar: {
133
-                    visible: true
134
+                    visible: true,
135
+                    animate: false
134
                   },
136
                   },
135
                   bottomTab: {
137
                   bottomTab: {
136
                     text: 'Tab 3',
138
                     text: 'Tab 3',