Browse Source

Fix Topbar custom component flicker across screens #3864 (#5110)

When a topBar.backgroundComponent is defined, check if a corresponding ReactView has already been created with matching componentName and componentId. If such view is found, reuse it instead of creating another view.

This change will fix a flickering in the TopBar background component when a screen was pushed into a stack with a background component.
Fabrizio Rizzonelli 5 years ago
parent
commit
99032e060d

+ 3
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Text.java View File

1
 package com.reactnativenavigation.parse.params;
1
 package com.reactnativenavigation.parse.params;
2
 
2
 
3
+import android.support.annotation.NonNull;
4
+
3
 public class Text extends Param<String> {
5
 public class Text extends Param<String> {
4
     public Text(String value) {
6
     public Text(String value) {
5
         super(value);
7
         super(value);
6
     }
8
     }
7
 
9
 
10
+    @NonNull
8
     @Override
11
     @Override
9
     public String toString() {
12
     public String toString() {
10
         return hasValue() ? value : "No Value";
13
         return hasValue() ? value : "No Value";

+ 19
- 8
lib/android/app/src/main/java/com/reactnativenavigation/presentation/StackPresenter.java View File

45
 import java.util.List;
45
 import java.util.List;
46
 import java.util.Map;
46
 import java.util.Map;
47
 
47
 
48
-import static com.reactnativenavigation.utils.CollectionUtils.filter;
49
-import static com.reactnativenavigation.utils.CollectionUtils.forEach;
50
-import static com.reactnativenavigation.utils.CollectionUtils.keyBy;
51
-import static com.reactnativenavigation.utils.CollectionUtils.merge;
48
+import static com.reactnativenavigation.utils.CollectionUtils.*;
52
 import static com.reactnativenavigation.utils.ObjectUtils.perform;
49
 import static com.reactnativenavigation.utils.ObjectUtils.perform;
53
 
50
 
54
 public class StackPresenter {
51
 public class StackPresenter {
205
         topBar.setBackgroundColor(options.background.color.get(Color.WHITE));
202
         topBar.setBackgroundColor(options.background.color.get(Color.WHITE));
206
 
203
 
207
         if (options.background.component.hasValue()) {
204
         if (options.background.component.hasValue()) {
208
-            if (backgroundControllers.containsKey(component)) {
209
-                topBar.setBackgroundComponent(backgroundControllers.get(component).getView());
205
+            View createdComponent = findBackgroundComponent(options.background.component);
206
+            if (createdComponent != null) {
207
+                topBar.setBackgroundComponent(createdComponent);
210
             } else {
208
             } else {
211
                 TopBarBackgroundViewController controller = new TopBarBackgroundViewController(activity, topBarBackgroundViewCreator);
209
                 TopBarBackgroundViewController controller = new TopBarBackgroundViewController(activity, topBarBackgroundViewCreator);
212
                 controller.setWaitForRender(options.background.waitForRender);
210
                 controller.setWaitForRender(options.background.waitForRender);
215
                 controller.getView().setLayoutParams(new RelativeLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
213
                 controller.getView().setLayoutParams(new RelativeLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
216
                 topBar.setBackgroundComponent(controller.getView());
214
                 topBar.setBackgroundComponent(controller.getView());
217
             }
215
             }
216
+        } else {
217
+            topBar.clearBackgroundComponent();
218
         }
218
         }
219
 
219
 
220
         if (options.testId.hasValue()) topBar.setTestId(options.testId.get());
220
         if (options.testId.hasValue()) topBar.setTestId(options.testId.get());
233
         }
233
         }
234
     }
234
     }
235
 
235
 
236
+    @Nullable
237
+    private View findBackgroundComponent(com.reactnativenavigation.parse.Component component) {
238
+        for (TopBarBackgroundViewController controller : backgroundControllers.values()) {
239
+            if (ObjectUtils.equalsNotNull(controller.getComponent().name.get(null), component.name.get(null)) &&
240
+                ObjectUtils.equalsNotNull(controller.getComponent().componentId.get(null), component.componentId.get(null))) {
241
+                return controller.getView();
242
+            }
243
+        }
244
+        return null;
245
+    }
246
+
236
     private void setInitialTopBarVisibility(TopBarOptions options) {
247
     private void setInitialTopBarVisibility(TopBarOptions options) {
237
         if (options.visible.isFalse()) {
248
         if (options.visible.isFalse()) {
238
             topBar.hide();
249
             topBar.hide();
268
             componentRightButtons.put(child, keyBy(rightButtonControllers, TitleBarButtonController::getButtonInstanceId));
279
             componentRightButtons.put(child, keyBy(rightButtonControllers, TitleBarButtonController::getButtonInstanceId));
269
             topBar.setRightButtons(rightButtonControllers);
280
             topBar.setRightButtons(rightButtonControllers);
270
         } else {
281
         } else {
271
-            topBar.setRightButtons(null);
282
+            topBar.clearRightButtons();
272
         }
283
         }
273
 
284
 
274
         if (leftButtons != null) {
285
         if (leftButtons != null) {
276
             componentLeftButtons.put(child, keyBy(leftButtonControllers, TitleBarButtonController::getButtonInstanceId));
287
             componentLeftButtons.put(child, keyBy(leftButtonControllers, TitleBarButtonController::getButtonInstanceId));
277
             topBar.setLeftButtons(leftButtonControllers);
288
             topBar.setLeftButtons(leftButtonControllers);
278
         } else {
289
         } else {
279
-            topBar.setLeftButtons(null);
290
+            topBar.clearLeftButtons();
280
         }
291
         }
281
 
292
 
282
         if (options.buttons.back.visible.isTrue() && !options.buttons.hasLeftButtons()) {
293
         if (options.buttons.back.visible.isTrue() && !options.buttons.hasLeftButtons()) {

+ 4
- 0
lib/android/app/src/main/java/com/reactnativenavigation/utils/ObjectUtils.java View File

17
     public static boolean notNull(Object o) {
17
     public static boolean notNull(Object o) {
18
         return o != null;
18
         return o != null;
19
     }
19
     }
20
+
21
+    public static <T> boolean equalsNotNull(@Nullable T a, @Nullable T b) {
22
+        return a != null && a.equals(b);
23
+    }
20
 }
24
 }

+ 0
- 12
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackController.java View File

132
         );
132
         );
133
     }
133
     }
134
 
134
 
135
-    @Override
136
-    public void destroy() {
137
-        topBarController.clear();
138
-        super.destroy();
139
-    }
140
-
141
-    @Override
142
-    public void clearOptions() {
143
-        super.clearOptions();
144
-        topBarController.clear();
145
-    }
146
-
147
     @Override
135
     @Override
148
     public void onChildDestroyed(Component child) {
136
     public void onChildDestroyed(Component child) {
149
         super.onChildDestroyed(child);
137
         super.onChildDestroyed(child);

+ 4
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/topbar/TopBarBackgroundViewController.java View File

45
     public void setComponent(Component component) {
45
     public void setComponent(Component component) {
46
         this.component = component;
46
         this.component = component;
47
     }
47
     }
48
+
49
+    public Component getComponent() {
50
+        return component;
51
+    }
48
 }
52
 }

+ 0
- 6
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/topbar/TopBarController.java View File

24
         return new TopBar(context, stackLayout);
24
         return new TopBar(context, stackLayout);
25
     }
25
     }
26
 
26
 
27
-    public void clear() {
28
-        if (topBar != null) {
29
-            topBar.clear();
30
-        }
31
-    }
32
-
33
     public TopBar getView() {
27
     public TopBar getView() {
34
         return topBar;
28
         return topBar;
35
     }
29
     }

+ 11
- 2
lib/android/app/src/main/java/com/reactnativenavigation/views/topbar/TopBar.java View File

36
 import com.reactnativenavigation.views.titlebar.TitleBar;
36
 import com.reactnativenavigation.views.titlebar.TitleBar;
37
 import com.reactnativenavigation.views.toptabs.TopTabs;
37
 import com.reactnativenavigation.views.toptabs.TopTabs;
38
 
38
 
39
+import java.util.Collections;
39
 import java.util.List;
40
 import java.util.List;
40
 
41
 
41
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
42
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
174
     }
175
     }
175
 
176
 
176
     public void setBackgroundComponent(View component) {
177
     public void setBackgroundComponent(View component) {
178
+        if (this.component == component || component.getParent() != null) return;
177
         this.component = component;
179
         this.component = component;
178
         root.addView(component, 0);
180
         root.addView(component, 0);
179
     }
181
     }
208
         titleBar.setLeftButtons(leftButtons);
210
         titleBar.setLeftButtons(leftButtons);
209
     }
211
     }
210
 
212
 
213
+    public void clearLeftButtons() {
214
+        titleBar.setLeftButtons(Collections.emptyList());
215
+    }
216
+
211
     public void setRightButtons(List<TitleBarButtonController> rightButtons) {
217
     public void setRightButtons(List<TitleBarButtonController> rightButtons) {
212
         titleBar.setRightButtons(rightButtons);
218
         titleBar.setRightButtons(rightButtons);
213
     }
219
     }
214
 
220
 
221
+    public void clearRightButtons() {
222
+        titleBar.setRightButtons(Collections.emptyList());
223
+    }
224
+
215
     public void setElevation(Double elevation) {
225
     public void setElevation(Double elevation) {
216
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && getElevation() != elevation.floatValue()) {
226
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && getElevation() != elevation.floatValue()) {
217
             this.elevation = UiUtils.dpToPx(getContext(), elevation.floatValue());
227
             this.elevation = UiUtils.dpToPx(getContext(), elevation.floatValue());
271
         animator.hide(options, onAnimationEnd);
281
         animator.hide(options, onAnimationEnd);
272
     }
282
     }
273
 
283
 
274
-    public void clear() {
284
+    public void clearBackgroundComponent() {
275
         if (component != null) {
285
         if (component != null) {
276
             root.removeView(component);
286
             root.removeView(component);
277
             component = null;
287
             component = null;
278
         }
288
         }
279
-        titleBar.clear();
280
     }
289
     }
281
 
290
 
282
     public void clearTopTabs() {
291
     public void clearTopTabs() {

+ 14
- 0
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/StackPresenterTest.java View File

36
 import org.json.JSONObject;
36
 import org.json.JSONObject;
37
 import org.junit.Test;
37
 import org.junit.Test;
38
 import org.mockito.ArgumentCaptor;
38
 import org.mockito.ArgumentCaptor;
39
+import org.mockito.Mockito;
39
 
40
 
40
 import java.util.ArrayList;
41
 import java.util.ArrayList;
41
 import java.util.Collection;
42
 import java.util.Collection;
354
         assertThat(leftCaptor.getValue().get(0)).isNotEqualTo(leftButton);
355
         assertThat(leftCaptor.getValue().get(0)).isNotEqualTo(leftButton);
355
     }
356
     }
356
 
357
 
358
+    @Test
359
+    public void applyTopBarOptions_backgroundComponentIsCreatedOnceIfNameAndIdAreEqual() {
360
+        Options o = new Options();
361
+        o.topBar.background.component.name = new Text("comp");
362
+        o.topBar.background.component.componentId = new Text("compId");
363
+
364
+        uut.applyChildOptions(o, Mockito.mock(com.reactnativenavigation.views.Component.class));
365
+        assertThat(uut.getBackgroundComponents().size()).isOne();
366
+
367
+        uut.applyChildOptions(o, Mockito.mock(com.reactnativenavigation.views.Component.class));
368
+        assertThat(uut.getBackgroundComponents().size()).isOne();
369
+    }
370
+
357
     @Test
371
     @Test
358
     public void mergeChildOptions_buttonColorIsResolvedFromAppliedOptions() {
372
     public void mergeChildOptions_buttonColorIsResolvedFromAppliedOptions() {
359
         Options appliedOptions = new Options();
373
         Options appliedOptions = new Options();

+ 0
- 62
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopBarControllerTest.java View File

1
-package com.reactnativenavigation.viewcontrollers;
2
-
3
-import android.app.Activity;
4
-import android.content.Context;
5
-import android.support.annotation.NonNull;
6
-
7
-import com.reactnativenavigation.BaseTest;
8
-import com.reactnativenavigation.viewcontrollers.topbar.TopBarController;
9
-import com.reactnativenavigation.views.StackLayout;
10
-import com.reactnativenavigation.views.titlebar.TitleBar;
11
-import com.reactnativenavigation.views.topbar.TopBar;
12
-
13
-import org.junit.Test;
14
-import org.mockito.Mockito;
15
-
16
-import static org.mockito.Mockito.mock;
17
-import static org.mockito.Mockito.spy;
18
-import static org.mockito.Mockito.times;
19
-import static org.mockito.Mockito.verify;
20
-
21
-public class TopBarControllerTest extends BaseTest {
22
-
23
-    private TopBarController uut;
24
-
25
-    @Override
26
-    public void beforeEach() {
27
-        uut = new TopBarController();
28
-    }
29
-
30
-    @Test
31
-    public void clear() {
32
-        final TitleBar[] titleBar = new TitleBar[1];
33
-        uut = new TopBarController() {
34
-            @NonNull
35
-            @Override
36
-            protected TopBar createTopBar(Context context, StackLayout stackLayout) {
37
-                return new TopBar(context, stackLayout) {
38
-                    @Override
39
-                    protected TitleBar createTitleBar(Context context) {
40
-                        titleBar[0] = spy(super.createTitleBar(context));
41
-                        return titleBar[0];
42
-                    }
43
-                };
44
-            }
45
-        };
46
-        Activity activity = newActivity();
47
-        uut.createView(activity, Mockito.mock(StackLayout.class));
48
-        uut.clear();
49
-        verify(titleBar[0], times(1)).clear();
50
-    }
51
-
52
-    @Test
53
-    public void destroy() {
54
-        uut.createView(newActivity(), mock(StackLayout.class));
55
-        uut.clear();
56
-    }
57
-
58
-    @Test
59
-    public void destroy_canBeCalledBeforeViewIsCreated() {
60
-        uut.clear();
61
-    }
62
-}

+ 0
- 6
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.java View File

1036
         verify(presenter).applyChildOptions(any(), eq(component));
1036
         verify(presenter).applyChildOptions(any(), eq(component));
1037
     }
1037
     }
1038
 
1038
 
1039
-    @Test
1040
-    public void destroy() {
1041
-        uut.destroy();
1042
-        verify(topBarController, times(1)).clear();
1043
-    }
1044
-
1045
     private void assertContainsOnlyId(String... ids) {
1039
     private void assertContainsOnlyId(String... ids) {
1046
         assertThat(uut.size()).isEqualTo(ids.length);
1040
         assertThat(uut.size()).isEqualTo(ids.length);
1047
         assertThat(uut.getChildControllers()).extracting((Extractor<ViewController, String>) ViewController::getId).containsOnly(ids);
1041
         assertThat(uut.getChildControllers()).extracting((Extractor<ViewController, String>) ViewController::getId).containsOnly(ids);