소스 검색

Bottom tabs attach mode (#4633)

This pr adds support for changing tab initialisation mode. Currently bottom tabs are attached together and consequently, their corresponding react root views are created and rendered. This adds lots of stress on the js thread and hiders app start time.

To mitigate this issue, this pr adds support for three modes
* `together` (default, current behaviour) - all tabs are loaded together, adding unnecessary load on the js thread as we're loading invisible views, which leads to increased app start time.
* `afterInitialTab` - Initial tab is loaded first. After it is rendered, other tabs are loaded as well. This should shave a few hunderdish ms from app start time. Since other tabs are loaded after the initial tab is visible, the ui might be unresponsive for a few hunderdish ms as multiple root views (which are typically complex) are being created and attached to hierarchy at once.
* `onSwitchToTab` - initial tab is loaded. Other tabs are loaded when switching to them (tab click or programmatically). While this won't stress js thread after initial tab is rendered, there will be a flicker when entering a tab for the first time as it's ui isn't ready.
Guy Carmeli 5 년 전
부모
커밋
740ad3c326
No account linked to committer's email address
29개의 변경된 파일547개의 추가작업 그리고 35개의 파일을 삭제
  1. 4
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/BottomTabsOptions.java
  2. 4
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/LayoutFactory.java
  3. 25
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/TabsAttachMode.java
  4. 1
    1
      lib/android/app/src/main/java/com/reactnativenavigation/presentation/OverlayManager.java
  5. 15
    0
      lib/android/app/src/main/java/com/reactnativenavigation/utils/CollectionUtils.java
  6. 14
    7
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ViewController.java
  7. 36
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/AfterInitialTab.java
  8. 62
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/AttachMode.java
  9. 38
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsAttacher.java
  10. 12
    13
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java
  11. 34
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/OnSwitchToTab.java
  12. 22
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/Together.java
  13. 2
    2
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenter.java
  14. 1
    1
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/navigator/RootPresenter.java
  15. 1
    1
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackController.java
  16. 3
    1
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/topbar/TopBarController.java
  17. 11
    0
      lib/android/app/src/test/java/com/reactnativenavigation/BaseTest.java
  18. 12
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopBarControllerTest.java
  19. 45
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsAttacherTest.java
  20. 21
    3
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsControllerTest.java
  21. 39
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/attachmode/AfterInitialTabTest.java
  22. 80
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/attachmode/AttachModeTest.java
  23. 33
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/attachmode/OnSwitchToTabTest.java
  24. 21
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/attachmode/TogetherTest.java
  25. 1
    1
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenterTest.java
  26. 3
    1
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/navigator/NavigatorTest.java
  27. 1
    1
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/navigator/RootPresenterTest.java
  28. 1
    2
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.java
  29. 5
    0
      lib/src/interfaces/Options.ts

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

@@ -34,6 +34,7 @@ public class BottomTabsOptions {
34 34
         options.elevation = FractionParser.parse(json, "elevation");
35 35
         options.testId = TextParser.parse(json, "testID");
36 36
         options.titleDisplayMode = TitleDisplayMode.fromString(json.optString("titleDisplayMode"));
37
+        options.tabsAttachMode = TabsAttachMode.fromString(json.optString("tabsAttachMode"));
37 38
 
38 39
 		return options;
39 40
 	}
@@ -47,6 +48,7 @@ public class BottomTabsOptions {
47 48
 	public Text currentTabId = new NullText();
48 49
     public Text testId = new NullText();
49 50
     public TitleDisplayMode titleDisplayMode = TitleDisplayMode.UNDEFINED;
51
+    public TabsAttachMode tabsAttachMode = TabsAttachMode.UNDEFINED;
50 52
 
51 53
 	void mergeWith(final BottomTabsOptions other) {
52 54
 		if (other.currentTabId.hasValue()) currentTabId = other.currentTabId;
@@ -58,6 +60,7 @@ public class BottomTabsOptions {
58 60
         if (other.backgroundColor.hasValue()) backgroundColor = other.backgroundColor;
59 61
         if (other.testId.hasValue()) testId = other.testId;
60 62
         if (other.titleDisplayMode.hasValue()) titleDisplayMode = other.titleDisplayMode;
63
+        if (other.tabsAttachMode.hasValue()) tabsAttachMode = other.tabsAttachMode;
61 64
     }
62 65
 
63 66
     void mergeWithDefault(final BottomTabsOptions defaultOptions) {
@@ -69,6 +72,7 @@ public class BottomTabsOptions {
69 72
         if (!elevation.hasValue()) elevation = defaultOptions.elevation;
70 73
         if (!backgroundColor.hasValue()) backgroundColor = defaultOptions.backgroundColor;
71 74
         if (!titleDisplayMode.hasValue()) titleDisplayMode = defaultOptions.titleDisplayMode;
75
+        if (!tabsAttachMode.hasValue()) tabsAttachMode = defaultOptions.tabsAttachMode;
72 76
     }
73 77
 
74 78
     public void clearOneTimeOptions() {

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

@@ -16,6 +16,7 @@ import com.reactnativenavigation.utils.TypefaceLoader;
16 16
 import com.reactnativenavigation.viewcontrollers.ChildControllersRegistry;
17 17
 import com.reactnativenavigation.viewcontrollers.ComponentViewController;
18 18
 import com.reactnativenavigation.viewcontrollers.ViewController;
19
+import com.reactnativenavigation.viewcontrollers.bottomtabs.BottomTabsAttacher;
19 20
 import com.reactnativenavigation.viewcontrollers.bottomtabs.BottomTabsController;
20 21
 import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalComponentCreator;
21 22
 import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalComponentViewController;
@@ -193,6 +194,7 @@ public class LayoutFactory {
193 194
         for (int i = 0; i < node.children.size(); i++) {
194 195
             tabs.add(create(node.children.get(i)));
195 196
         }
197
+        BottomTabsPresenter bottomTabsPresenter = new BottomTabsPresenter(tabs, defaultOptions);
196 198
         return new BottomTabsController(activity,
197 199
                 tabs,
198 200
                 childRegistry,
@@ -201,7 +203,8 @@ public class LayoutFactory {
201 203
                 node.id,
202 204
                 parse(typefaceManager, node.getOptions()),
203 205
                 new Presenter(activity, defaultOptions),
204
-                new BottomTabsPresenter(tabs, defaultOptions),
206
+                new BottomTabsAttacher(tabs, bottomTabsPresenter),
207
+                bottomTabsPresenter,
205 208
                 new BottomTabPresenter(activity, tabs, new ImageLoader(), defaultOptions));
206 209
 	}
207 210
 

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

@@ -0,0 +1,25 @@
1
+package com.reactnativenavigation.parse;
2
+
3
+public enum  TabsAttachMode {
4
+    TOGETHER,
5
+    AFTER_INITIAL_TAB,
6
+    ON_SWITCH_TO_TAB,
7
+    UNDEFINED;
8
+
9
+    public static TabsAttachMode fromString(String mode) {
10
+        switch (mode) {
11
+            case "together":
12
+                return TOGETHER;
13
+            case "afterInitialTab":
14
+                return AFTER_INITIAL_TAB;
15
+            case "onSwitchToTab":
16
+                return ON_SWITCH_TO_TAB;
17
+            default:
18
+                return UNDEFINED;
19
+        }
20
+    }
21
+
22
+    public boolean hasValue() {
23
+        return this != UNDEFINED;
24
+    }
25
+}

+ 1
- 1
lib/android/app/src/main/java/com/reactnativenavigation/presentation/OverlayManager.java 파일 보기

@@ -12,7 +12,7 @@ public class OverlayManager {
12 12
 
13 13
     public void show(ViewGroup overlaysContainer, ViewController overlay, CommandListener listener) {
14 14
         overlayRegistry.put(overlay.getId(), overlay);
15
-        overlay.setOnAppearedListener(() -> listener.onSuccess(overlay.getId()));
15
+        overlay.addOnAppearedListener(() -> listener.onSuccess(overlay.getId()));
16 16
         overlaysContainer.addView(overlay.getView());
17 17
     }
18 18
 

+ 15
- 0
lib/android/app/src/main/java/com/reactnativenavigation/utils/CollectionUtils.java 파일 보기

@@ -69,12 +69,27 @@ public class CollectionUtils {
69 69
     }
70 70
 
71 71
     public static <T> void forEach(@Nullable Collection<T> items, Apply<T> apply) {
72
+        if (items != null) forEach(new ArrayList(items), 0, apply);
73
+    }
74
+
75
+    public static <T> void forEach(@Nullable T[] items, Apply<T> apply) {
72 76
         if (items == null) return;
73 77
         for (T item : items) {
74 78
             apply.on(item);
75 79
         }
76 80
     }
77 81
 
82
+    public static <T> void forEach(@Nullable List<T> items, Apply<T> apply) {
83
+        forEach(items, 0, apply);
84
+    }
85
+
86
+    public static <T> void forEach(@Nullable List<T> items, int startIndex, Apply<T> apply) {
87
+        if (items == null) return;
88
+        for (int i = startIndex; i < items.size(); i++) {
89
+            apply.on(items.get(i));
90
+        }
91
+    }
92
+
78 93
     public static @Nullable <T> T first(@Nullable Collection<T> items, Filter<T> by) {
79 94
         if (isNullOrEmpty(items)) return null;
80 95
         for (T item : items) {

+ 14
- 7
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ViewController.java 파일 보기

@@ -25,12 +25,15 @@ import com.reactnativenavigation.views.Component;
25 25
 import com.reactnativenavigation.views.Renderable;
26 26
 import com.reactnativenavigation.views.element.Element;
27 27
 
28
+import java.util.ArrayList;
28 29
 import java.util.Collections;
29 30
 import java.util.List;
30 31
 
32
+import static com.reactnativenavigation.utils.CollectionUtils.forEach;
33
+
31 34
 public abstract class ViewController<T extends ViewGroup> implements ViewTreeObserver.OnGlobalLayoutListener, ViewGroup.OnHierarchyChangeListener {
32 35
 
33
-    private Runnable onAppearedListener;
36
+    private final List<Runnable> onAppearedListeners = new ArrayList();
34 37
     private boolean appearEventPosted;
35 38
     private boolean isFirstLayout = true;
36 39
     private Bool waitForRender = new NullBool();
@@ -47,7 +50,7 @@ public abstract class ViewController<T extends ViewGroup> implements ViewTreeObs
47 50
         boolean onViewDisappear(View view);
48 51
     }
49 52
 
50
-    protected Options initialOptions;
53
+    public Options initialOptions;
51 54
     public Options options;
52 55
 
53 56
     private final Activity activity;
@@ -77,8 +80,12 @@ public abstract class ViewController<T extends ViewGroup> implements ViewTreeObs
77 80
         this.waitForRender = waitForRender;
78 81
     }
79 82
 
80
-    public void setOnAppearedListener(Runnable onAppearedListener) {
81
-        this.onAppearedListener = onAppearedListener;
83
+    public void addOnAppearedListener(Runnable onAppearedListener) {
84
+        onAppearedListeners.add(onAppearedListener);
85
+    }
86
+
87
+    public void removeOnAppearedListener(Runnable onAppearedListener) {
88
+        onAppearedListeners.remove(onAppearedListener);
82 89
     }
83 90
 
84 91
     protected abstract T createView();
@@ -199,11 +206,11 @@ public abstract class ViewController<T extends ViewGroup> implements ViewTreeObs
199 206
             parentController.clearOptions();
200 207
             if (getView() instanceof Component) parentController.applyChildOptions(options, (Component) getView());
201 208
         });
202
-        if (onAppearedListener != null && !appearEventPosted) {
209
+        if (!onAppearedListeners.isEmpty() && !appearEventPosted) {
203 210
             appearEventPosted = true;
204 211
             UiThread.post(() -> {
205
-                onAppearedListener.run();
206
-                onAppearedListener = null;
212
+                forEach(onAppearedListeners, Runnable::run);
213
+                onAppearedListeners.clear();
207 214
             });
208 215
         }
209 216
     }

+ 36
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/AfterInitialTab.java 파일 보기

@@ -0,0 +1,36 @@
1
+package com.reactnativenavigation.viewcontrollers.bottomtabs;
2
+
3
+import android.view.*;
4
+
5
+import com.reactnativenavigation.parse.*;
6
+import com.reactnativenavigation.presentation.*;
7
+import com.reactnativenavigation.viewcontrollers.*;
8
+
9
+import java.util.*;
10
+
11
+import static com.reactnativenavigation.utils.CollectionUtils.filter;
12
+import static com.reactnativenavigation.utils.CollectionUtils.forEach;
13
+
14
+public class AfterInitialTab extends AttachMode {
15
+    private final Runnable attachOtherTabs;
16
+
17
+    public AfterInitialTab(ViewGroup parent, List<ViewController> tabs, BottomTabsPresenter presenter, Options resolved) {
18
+        super(parent, tabs, presenter, resolved);
19
+        attachOtherTabs = () -> forEach(otherTabs(), this::attach);
20
+    }
21
+
22
+    @Override
23
+    public void attach() {
24
+        initialTab.addOnAppearedListener(attachOtherTabs);
25
+        attach(initialTab);
26
+    }
27
+
28
+    @Override
29
+    public void destroy() {
30
+        initialTab.removeOnAppearedListener(attachOtherTabs);
31
+    }
32
+
33
+    private List<ViewController> otherTabs() {
34
+        return filter(tabs, t -> t != initialTab);
35
+    }
36
+}

+ 62
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/AttachMode.java 파일 보기

@@ -0,0 +1,62 @@
1
+package com.reactnativenavigation.viewcontrollers.bottomtabs;
2
+
3
+import android.support.annotation.*;
4
+import android.view.*;
5
+import android.widget.*;
6
+
7
+import com.reactnativenavigation.parse.*;
8
+import com.reactnativenavigation.presentation.*;
9
+import com.reactnativenavigation.viewcontrollers.*;
10
+
11
+import java.util.*;
12
+
13
+import static android.view.ViewGroup.LayoutParams.*;
14
+
15
+public abstract class AttachMode {
16
+    protected final ViewGroup parent;
17
+    protected final BottomTabsPresenter presenter;
18
+    protected final List<ViewController> tabs;
19
+    final ViewController initialTab;
20
+    private final Options resolved;
21
+
22
+
23
+    public static AttachMode get(ViewGroup parent, List<ViewController> tabs, BottomTabsPresenter presenter, Options resolved) {
24
+        switch (resolved.bottomTabsOptions.tabsAttachMode) {
25
+            case AFTER_INITIAL_TAB:
26
+                return new AfterInitialTab(parent, tabs, presenter, resolved);
27
+            case ON_SWITCH_TO_TAB:
28
+                return new OnSwitchToTab(parent, tabs, presenter, resolved);
29
+            case UNDEFINED:
30
+            case TOGETHER:
31
+            default:
32
+                return new Together(parent, tabs, presenter, resolved);
33
+        }
34
+    }
35
+
36
+    AttachMode(ViewGroup parent, List<ViewController> tabs, BottomTabsPresenter presenter, Options resolved) {
37
+        this.parent = parent;
38
+        this.tabs = tabs;
39
+        this.presenter = presenter;
40
+        this.resolved = resolved;
41
+        initialTab = tabs.get(resolved.bottomTabsOptions.currentTabIndex.get(0));
42
+    }
43
+
44
+    public abstract void attach();
45
+
46
+    public void destroy() {
47
+
48
+    }
49
+
50
+    public void onTabSelected(ViewController tab) {
51
+
52
+    }
53
+
54
+    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
55
+    public void attach(ViewController tab) {
56
+        ViewGroup view = tab.getView();
57
+        view.setLayoutParams(new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
58
+        presenter.applyLayoutParamsOptions(resolved, tabs.indexOf(tab));
59
+        view.setVisibility(tab == initialTab ? View.VISIBLE : View.INVISIBLE);
60
+        parent.addView(view);
61
+    }
62
+}

+ 38
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsAttacher.java 파일 보기

@@ -0,0 +1,38 @@
1
+package com.reactnativenavigation.viewcontrollers.bottomtabs;
2
+
3
+import android.support.annotation.VisibleForTesting;
4
+import android.view.ViewGroup;
5
+
6
+import com.reactnativenavigation.parse.Options;
7
+import com.reactnativenavigation.presentation.BottomTabsPresenter;
8
+import com.reactnativenavigation.viewcontrollers.ViewController;
9
+
10
+import java.util.List;
11
+
12
+public class BottomTabsAttacher {
13
+    private final List<ViewController> tabs;
14
+    private final BottomTabsPresenter presenter;
15
+    @VisibleForTesting
16
+    AttachMode attachStrategy;
17
+
18
+    public BottomTabsAttacher(List<ViewController> tabs, BottomTabsPresenter presenter) {
19
+        this.tabs = tabs;
20
+        this.presenter = presenter;
21
+    }
22
+
23
+    void init(ViewGroup parent, Options resolved) {
24
+        attachStrategy = AttachMode.get(parent, tabs, presenter, resolved);
25
+    }
26
+
27
+    void attach() {
28
+        attachStrategy.attach();
29
+    }
30
+
31
+    public void destroy() {
32
+        attachStrategy.destroy();
33
+    }
34
+
35
+    void onTabSelected(ViewController tab) {
36
+        attachStrategy.onTabSelected(tab);
37
+    }
38
+}

+ 12
- 13
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java 파일 보기

@@ -38,14 +38,16 @@ public class BottomTabsController extends ParentController implements AHBottomNa
38 38
 	private List<ViewController> tabs;
39 39
     private EventEmitter eventEmitter;
40 40
     private ImageLoader imageLoader;
41
+    private final BottomTabsAttacher tabsAttacher;
41 42
     private BottomTabsPresenter presenter;
42 43
     private BottomTabPresenter tabPresenter;
43 44
 
44
-    public BottomTabsController(Activity activity, List<ViewController> tabs, ChildControllersRegistry childRegistry, EventEmitter eventEmitter, ImageLoader imageLoader, String id, Options initialOptions, Presenter presenter, BottomTabsPresenter bottomTabsPresenter, BottomTabPresenter bottomTabPresenter) {
45
+    public BottomTabsController(Activity activity, List<ViewController> tabs, ChildControllersRegistry childRegistry, EventEmitter eventEmitter, ImageLoader imageLoader, String id, Options initialOptions, Presenter presenter, BottomTabsAttacher tabsAttacher, BottomTabsPresenter bottomTabsPresenter, BottomTabPresenter bottomTabPresenter) {
45 46
 		super(activity, childRegistry, id, presenter, initialOptions);
46 47
         this.tabs = tabs;
47 48
         this.eventEmitter = eventEmitter;
48 49
         this.imageLoader = imageLoader;
50
+        this.tabsAttacher = tabsAttacher;
49 51
         this.presenter = bottomTabsPresenter;
50 52
         this.tabPresenter = bottomTabPresenter;
51 53
         forEach(tabs, (tab) -> tab.setParentController(this));
@@ -63,6 +65,7 @@ public class BottomTabsController extends ParentController implements AHBottomNa
63 65
 	protected ViewGroup createView() {
64 66
 		RelativeLayout root = new RelativeLayout(getActivity());
65 67
 		bottomTabs = createBottomTabs();
68
+        tabsAttacher.init(root, resolveCurrentOptions());
66 69
         presenter.bindView(bottomTabs, this);
67 70
         tabPresenter.bindView(bottomTabs);
68 71
         bottomTabs.setOnTabSelectedListener(this);
@@ -70,7 +73,7 @@ public class BottomTabsController extends ParentController implements AHBottomNa
70 73
 		lp.addRule(ALIGN_PARENT_BOTTOM);
71 74
 		root.addView(bottomTabs, lp);
72 75
 		bottomTabs.addItems(createTabs());
73
-        attachTabs(root);
76
+        tabsAttacher.attach();
74 77
         return root;
75 78
 	}
76 79
 
@@ -155,17 +158,6 @@ public class BottomTabsController extends ParentController implements AHBottomNa
155 158
         });
156 159
 	}
157 160
 
158
-    private void attachTabs(RelativeLayout root) {
159
-        for (int i = 0; i < tabs.size(); i++) {
160
-            ViewGroup tab = tabs.get(i).getView();
161
-            tab.setLayoutParams(new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
162
-            Options options = resolveCurrentOptions();
163
-            presenter.applyLayoutParamsOptions(options, i);
164
-            if (i != 0) tab.setVisibility(View.INVISIBLE);
165
-            root.addView(tab);
166
-        }
167
-    }
168
-
169 161
     public int getSelectedIndex() {
170 162
 		return bottomTabs.getCurrentItem();
171 163
 	}
@@ -176,8 +168,15 @@ public class BottomTabsController extends ParentController implements AHBottomNa
176 168
 		return tabs;
177 169
 	}
178 170
 
171
+    @Override
172
+    public void destroy() {
173
+        tabsAttacher.destroy();
174
+        super.destroy();
175
+    }
176
+
179 177
     @Override
180 178
     public void selectTab(final int newIndex) {
179
+        tabsAttacher.onTabSelected(tabs.get(newIndex));
181 180
         getCurrentView().setVisibility(View.INVISIBLE);
182 181
         bottomTabs.setCurrentItem(newIndex, false);
183 182
         getCurrentView().setVisibility(View.VISIBLE);

+ 34
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/OnSwitchToTab.java 파일 보기

@@ -0,0 +1,34 @@
1
+package com.reactnativenavigation.viewcontrollers.bottomtabs;
2
+
3
+import android.view.*;
4
+
5
+import com.reactnativenavigation.parse.*;
6
+import com.reactnativenavigation.presentation.*;
7
+import com.reactnativenavigation.viewcontrollers.*;
8
+
9
+import java.util.*;
10
+
11
+public class OnSwitchToTab extends AttachMode {
12
+    private final ViewController initialTab;
13
+
14
+    public OnSwitchToTab(ViewGroup parent, List<ViewController> tabs, BottomTabsPresenter presenter, Options resolved) {
15
+        super(parent, tabs, presenter, resolved);
16
+        this.initialTab = tabs.get(resolved.bottomTabsOptions.currentTabIndex.get(0));
17
+    }
18
+
19
+    @Override
20
+    public void attach() {
21
+        attach(initialTab);
22
+    }
23
+
24
+    @Override
25
+    public void onTabSelected(ViewController tab) {
26
+        if (tab != initialTab && isNotAttached(tab)) {
27
+            attach(tab);
28
+        }
29
+    }
30
+
31
+    private boolean isNotAttached(ViewController tab) {
32
+        return tab.getView().getParent() == null;
33
+    }
34
+}

+ 22
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/Together.java 파일 보기

@@ -0,0 +1,22 @@
1
+package com.reactnativenavigation.viewcontrollers.bottomtabs;
2
+
3
+import android.view.*;
4
+
5
+import com.reactnativenavigation.parse.*;
6
+import com.reactnativenavigation.presentation.*;
7
+import com.reactnativenavigation.viewcontrollers.*;
8
+
9
+import java.util.*;
10
+
11
+import static com.reactnativenavigation.utils.CollectionUtils.*;
12
+
13
+public class Together extends AttachMode {
14
+    public Together(ViewGroup parent, List<ViewController> tabs, BottomTabsPresenter presenter, Options resolved) {
15
+        super(parent, tabs, presenter, resolved);
16
+    }
17
+
18
+    @Override
19
+    public void attach() {
20
+        forEach(tabs, this::attach);
21
+    }
22
+}

+ 2
- 2
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenter.java 파일 보기

@@ -44,13 +44,13 @@ public class ModalPresenter {
44 44
         modalsLayout.addView(toAdd.getView());
45 45
         if (options.animations.showModal.enabled.isTrueOrUndefined()) {
46 46
             if (options.animations.showModal.waitForRender.isTrue()) {
47
-                toAdd.setOnAppearedListener(() -> animateShow(toAdd, toRemove, listener, options));
47
+                toAdd.addOnAppearedListener(() -> animateShow(toAdd, toRemove, listener, options));
48 48
             } else {
49 49
                 animateShow(toAdd, toRemove, listener, options);
50 50
             }
51 51
         } else {
52 52
             if (options.animations.showModal.waitForRender.isTrue()) {
53
-                toAdd.setOnAppearedListener(() -> onShowModalEnd(toAdd, toRemove, listener));
53
+                toAdd.addOnAppearedListener(() -> onShowModalEnd(toAdd, toRemove, listener));
54 54
             } else {
55 55
                 onShowModalEnd(toAdd, toRemove, listener);
56 56
             }

+ 1
- 1
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/navigator/RootPresenter.java 파일 보기

@@ -31,7 +31,7 @@ public class RootPresenter {
31 31
         root.setWaitForRender(options.animations.setRoot.waitForRender);
32 32
         if (options.animations.setRoot.waitForRender.isTrue()) {
33 33
             root.getView().setAlpha(0);
34
-            root.setOnAppearedListener(() -> {
34
+            root.addOnAppearedListener(() -> {
35 35
                 root.getView().setAlpha(1);
36 36
                 animateSetRootAndReportSuccess(root, listener, options);
37 37
             });

+ 1
- 1
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackController.java 파일 보기

@@ -162,7 +162,7 @@ public class StackController extends ParentController<StackLayout> {
162 162
             if (resolvedOptions.animations.push.enabled.isTrueOrUndefined()) {
163 163
                 if (resolvedOptions.animations.push.waitForRender.isTrue()) {
164 164
                     child.getView().setAlpha(0);
165
-                    child.setOnAppearedListener(() -> animator.push(child.getView(), resolvedOptions.animations.push, resolvedOptions.transitions, toRemove.getElements(), child.getElements(), () -> {
165
+                    child.addOnAppearedListener(() -> animator.push(child.getView(), resolvedOptions.animations.push, resolvedOptions.transitions, toRemove.getElements(), child.getElements(), () -> {
166 166
                         getView().removeView(toRemove.getView());
167 167
                         listener.onSuccess(child.getId());
168 168
                     }));

+ 3
- 1
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/topbar/TopBarController.java 파일 보기

@@ -25,7 +25,9 @@ public class TopBarController {
25 25
     }
26 26
 
27 27
     public void clear() {
28
-        topBar.clear();
28
+        if (topBar != null) {
29
+            topBar.clear();
30
+        }
29 31
     }
30 32
 
31 33
     public TopBar getView() {

+ 11
- 0
lib/android/app/src/test/java/com/reactnativenavigation/BaseTest.java 파일 보기

@@ -19,6 +19,9 @@ import org.robolectric.RobolectricTestRunner;
19 19
 import org.robolectric.android.controller.ActivityController;
20 20
 import org.robolectric.annotation.Config;
21 21
 
22
+import java.util.Arrays;
23
+
24
+import static com.reactnativenavigation.utils.CollectionUtils.forEach;
22 25
 import static org.assertj.core.api.Java6Assertions.assertThat;
23 26
 
24 27
 @RunWith(RobolectricTestRunner.class)
@@ -42,12 +45,20 @@ public abstract class BaseTest {
42 45
         return Robolectric.buildActivity(clazz);
43 46
     }
44 47
 
48
+    public void assertIsChild(ViewGroup parent, ViewController... children) {
49
+        forEach(Arrays.asList(children),c -> assertIsChild(parent, c.getView()));
50
+    }
51
+
45 52
     public void assertIsChild(ViewGroup parent, View child) {
46 53
         assertThat(parent).isNotNull();
47 54
         assertThat(child).isNotNull();
48 55
         assertThat(ViewUtils.isChildOf(parent, child)).isTrue();
49 56
     }
50 57
 
58
+    public void assertNotChildOf(ViewGroup parent, ViewController... children) {
59
+        forEach(Arrays.asList(children), c -> assertNotChildOf(parent, c.getView()));
60
+    }
61
+
51 62
     public void assertNotChildOf(ViewGroup parent, View child) {
52 63
         assertThat(parent).isNotNull();
53 64
         assertThat(child).isNotNull();

+ 12
- 0
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopBarControllerTest.java 파일 보기

@@ -13,6 +13,7 @@ import com.reactnativenavigation.views.topbar.TopBar;
13 13
 import org.junit.Test;
14 14
 import org.mockito.Mockito;
15 15
 
16
+import static org.mockito.Mockito.mock;
16 17
 import static org.mockito.Mockito.spy;
17 18
 import static org.mockito.Mockito.times;
18 19
 import static org.mockito.Mockito.verify;
@@ -47,4 +48,15 @@ public class TopBarControllerTest extends BaseTest {
47 48
         uut.clear();
48 49
         verify(titleBar[0], times(1)).clear();
49 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
+    }
50 62
 }

+ 45
- 0
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsAttacherTest.java 파일 보기

@@ -0,0 +1,45 @@
1
+package com.reactnativenavigation.viewcontrollers.bottomtabs;
2
+
3
+import com.reactnativenavigation.BaseTest;
4
+import com.reactnativenavigation.presentation.BottomTabsPresenter;
5
+import com.reactnativenavigation.viewcontrollers.*;
6
+
7
+import org.junit.Test;
8
+import org.mockito.Mockito;
9
+
10
+import java.util.Collections;
11
+
12
+import static org.mockito.Mockito.mock;
13
+import static org.mockito.Mockito.verify;
14
+
15
+public class BottomTabsAttacherTest extends BaseTest {
16
+
17
+    private BottomTabsAttacher uut;
18
+    private AttachMode mode;
19
+
20
+    @Override
21
+    public void beforeEach() {
22
+        mode = Mockito.mock(AttachMode.class);
23
+        uut = new BottomTabsAttacher(Collections.EMPTY_LIST, Mockito.mock(BottomTabsPresenter.class));
24
+        uut.attachStrategy = mode;
25
+    }
26
+
27
+    @Test
28
+    public void attach_delegatesToStrategy() {
29
+        uut.attach();
30
+        verify(mode).attach();
31
+    }
32
+
33
+    @Test
34
+    public void onTabSelected() {
35
+        ViewController tab = mock(ViewController.class);
36
+        uut.onTabSelected(tab);
37
+        verify(mode).onTabSelected(tab);
38
+    }
39
+
40
+    @Test
41
+    public void destroy_delegatesToStrategy() {
42
+        uut.destroy();
43
+        verify(mode).destroy();
44
+    }
45
+}

lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/BottomTabsControllerTest.java → lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsControllerTest.java 파일 보기

@@ -1,4 +1,4 @@
1
-package com.reactnativenavigation.viewcontrollers;
1
+package com.reactnativenavigation.viewcontrollers.bottomtabs;
2 2
 
3 3
 import android.app.Activity;
4 4
 import android.graphics.Color;
@@ -24,7 +24,8 @@ import com.reactnativenavigation.utils.CommandListenerAdapter;
24 24
 import com.reactnativenavigation.utils.ImageLoader;
25 25
 import com.reactnativenavigation.utils.OptionHelper;
26 26
 import com.reactnativenavigation.utils.ViewUtils;
27
-import com.reactnativenavigation.viewcontrollers.bottomtabs.BottomTabsController;
27
+import com.reactnativenavigation.viewcontrollers.ChildControllersRegistry;
28
+import com.reactnativenavigation.viewcontrollers.ViewController;
28 29
 import com.reactnativenavigation.viewcontrollers.stack.StackController;
29 30
 import com.reactnativenavigation.views.BottomTabs;
30 31
 import com.reactnativenavigation.views.ReactComponent;
@@ -66,6 +67,7 @@ public class BottomTabsControllerTest extends BaseTest {
66 67
     private ChildControllersRegistry childRegistry;
67 68
     private List<ViewController> tabs;
68 69
     private BottomTabsPresenter presenter;
70
+    private BottomTabsAttacher tabsAttacher;
69 71
 
70 72
     @Override
71 73
     public void beforeEach() {
@@ -88,6 +90,7 @@ public class BottomTabsControllerTest extends BaseTest {
88 90
         when(child5.handleBack(any())).thenReturn(true);
89 91
         tabs = createTabs();
90 92
         presenter = spy(new BottomTabsPresenter(tabs, new Options()));
93
+        tabsAttacher = spy(new BottomTabsAttacher(tabs, presenter));
91 94
         uut = createBottomTabs();
92 95
         activity.setContentView(uut.getView());
93 96
     }
@@ -241,8 +244,9 @@ public class BottomTabsControllerTest extends BaseTest {
241 244
         child4 = createStack(pushedScreen);
242 245
 
243 246
         tabs = new ArrayList<>(Collections.singletonList(child4));
247
+        tabsAttacher = new BottomTabsAttacher(tabs, presenter);
244 248
 
245
-        initialOptions.bottomTabsOptions.currentTabIndex = new Number(3);
249
+        initialOptions.bottomTabsOptions.currentTabIndex = new Number(0);
246 250
         Options resolvedOptions = new Options();
247 251
         uut = new BottomTabsController(activity,
248 252
                 tabs,
@@ -252,6 +256,7 @@ public class BottomTabsControllerTest extends BaseTest {
252 256
                 "uut",
253 257
                 initialOptions,
254 258
                 new Presenter(activity, new Options()),
259
+                tabsAttacher,
255 260
                 presenter,
256 261
                 new BottomTabPresenter(activity, tabs, ImageLoaderMock.mock(), new Options())) {
257 262
             @Override
@@ -340,6 +345,18 @@ public class BottomTabsControllerTest extends BaseTest {
340 345
         assertThat(uut.initialOptions.bottomTabsOptions.currentTabIndex.hasValue()).isFalse();
341 346
     }
342 347
 
348
+    @Test
349
+    public void selectTab() {
350
+        uut.selectTab(1);
351
+        verify(tabsAttacher).onTabSelected(tabs.get(1));
352
+    }
353
+
354
+    @Test
355
+    public void destroy() {
356
+        uut.destroy();
357
+        verify(tabsAttacher).destroy();
358
+    }
359
+
343 360
     @NonNull
344 361
     private List<ViewController> createTabs() {
345 362
         return Arrays.asList(child1, child2, child3, child4, child5);
@@ -372,6 +389,7 @@ public class BottomTabsControllerTest extends BaseTest {
372 389
                 "uut",
373 390
                 initialOptions,
374 391
                 new Presenter(activity, new Options()),
392
+                tabsAttacher,
375 393
                 presenter,
376 394
                 new BottomTabPresenter(activity, tabs, ImageLoaderMock.mock(), new Options())) {
377 395
             @Override

+ 39
- 0
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/attachmode/AfterInitialTabTest.java 파일 보기

@@ -0,0 +1,39 @@
1
+package com.reactnativenavigation.viewcontrollers.bottomtabs.attachmode;
2
+
3
+
4
+import com.reactnativenavigation.viewcontrollers.bottomtabs.*;
5
+
6
+import org.junit.*;
7
+
8
+import static org.mockito.ArgumentMatchers.any;
9
+import static org.mockito.Mockito.*;
10
+
11
+public class AfterInitialTabTest extends AttachModeTest {
12
+
13
+    @Override
14
+    public void beforeEach() {
15
+        super.beforeEach();
16
+        uut = new AfterInitialTab(parent, tabs, presenter, options);
17
+    }
18
+
19
+    @Test
20
+    public void attach_initialTabIsAttached() {
21
+        uut.attach();
22
+        assertIsChild(parent, tab2);
23
+    }
24
+
25
+    @Test
26
+    public void attach_otherTabsAreAttachedAfterInitialTab() {
27
+        uut.attach();
28
+        assertNotChildOf(parent, otherTabs());
29
+
30
+        initialTab().onViewAppeared();
31
+        assertIsChild(parent, otherTabs());
32
+    }
33
+
34
+    @Test
35
+    public void destroy() {
36
+        uut.destroy();
37
+        verify(initialTab()).removeOnAppearedListener(any());
38
+    }
39
+}

+ 80
- 0
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/attachmode/AttachModeTest.java 파일 보기

@@ -0,0 +1,80 @@
1
+package com.reactnativenavigation.viewcontrollers.bottomtabs.attachmode;
2
+
3
+import android.app.*;
4
+import android.view.*;
5
+import android.widget.*;
6
+
7
+import com.reactnativenavigation.*;
8
+import com.reactnativenavigation.mocks.*;
9
+import com.reactnativenavigation.parse.*;
10
+import com.reactnativenavigation.parse.params.Number;
11
+import com.reactnativenavigation.presentation.*;
12
+import com.reactnativenavigation.viewcontrollers.*;
13
+import com.reactnativenavigation.viewcontrollers.bottomtabs.*;
14
+
15
+import org.junit.*;
16
+import org.mockito.*;
17
+
18
+import java.util.*;
19
+
20
+import static com.reactnativenavigation.utils.CollectionUtils.*;
21
+import static org.assertj.core.api.Java6Assertions.assertThat;
22
+import static org.mockito.Mockito.*;
23
+
24
+public abstract class AttachModeTest extends BaseTest {
25
+    private static final int INITIAL_TAB = 1;
26
+
27
+    private Activity activity;
28
+    private ChildControllersRegistry childRegistry;
29
+    protected ViewGroup parent;
30
+    ViewController tab1;
31
+    ViewController tab2;
32
+    List<ViewController> tabs;
33
+    protected Options options;
34
+    protected BottomTabsPresenter presenter;
35
+    protected AttachMode uut;
36
+
37
+    @Override
38
+    public void beforeEach() {
39
+        activity = newActivity();
40
+        childRegistry = new ChildControllersRegistry();
41
+        parent = new FrameLayout(activity);
42
+        tabs = createTabs();
43
+        options = new Options();
44
+        options.bottomTabsOptions.currentTabIndex = new Number(INITIAL_TAB);
45
+        presenter = Mockito.mock(BottomTabsPresenter.class);
46
+    }
47
+
48
+    @Test
49
+    public void attach_layoutOptionsAreApplied() {
50
+        uut.attach(tab1);
51
+        verify(presenter).applyLayoutParamsOptions(options, tabs.indexOf(tab1));
52
+    }
53
+
54
+    @Test
55
+    public void attach_initialTabIsVisible() {
56
+        uut.attach(initialTab());
57
+        assertThat(initialTab().getView().getVisibility()).isEqualTo(View.VISIBLE);
58
+    }
59
+
60
+    @Test
61
+    public void attach_otherTabsAreInvisibleWhenAttached() {
62
+        forEach(otherTabs(), t -> uut.attach(t));
63
+        forEach(otherTabs(), t -> assertThat(t.getView().getVisibility()).isEqualTo(View.INVISIBLE));
64
+    }
65
+
66
+    ViewController[] otherTabs() {
67
+        return filter(tabs, t -> t != initialTab()).toArray(new ViewController[0]);
68
+    }
69
+
70
+    ViewController initialTab() {
71
+        return tabs.get(INITIAL_TAB);
72
+    }
73
+
74
+    private List<ViewController> createTabs() {
75
+        tab1 = new SimpleViewController(activity, childRegistry, "child1", new Options());
76
+        tab2 = spy(new SimpleViewController(activity, childRegistry, "child2", new Options()));
77
+        ViewController tab3 = new SimpleViewController(activity, childRegistry, "child3", new Options());
78
+        return Arrays.asList(tab1, tab2, tab3);
79
+    }
80
+}

+ 33
- 0
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/attachmode/OnSwitchToTabTest.java 파일 보기

@@ -0,0 +1,33 @@
1
+package com.reactnativenavigation.viewcontrollers.bottomtabs.attachmode;
2
+
3
+import com.reactnativenavigation.viewcontrollers.bottomtabs.*;
4
+
5
+import org.junit.*;
6
+
7
+public class OnSwitchToTabTest extends AttachModeTest {
8
+
9
+    @Override
10
+    public void beforeEach() {
11
+        super.beforeEach();
12
+        uut = new OnSwitchToTab(parent, tabs, presenter, options);
13
+    }
14
+
15
+    @Test
16
+    public void attach_onlyInitialTabIsAttached() {
17
+        uut.attach();
18
+        assertIsChild(parent, initialTab());
19
+        assertNotChildOf(parent, otherTabs());
20
+    }
21
+
22
+    @Test
23
+    public void onTabSelected_initialTabIsNotHandled() {
24
+        uut.onTabSelected(initialTab());
25
+        assertNotChildOf(parent, initialTab());
26
+    }
27
+
28
+    @Test
29
+    public void onTabSelected_otherTabIsAttached() {
30
+        uut.onTabSelected(tab1);
31
+        assertIsChild(parent, tab1);
32
+    }
33
+}

+ 21
- 0
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/attachmode/TogetherTest.java 파일 보기

@@ -0,0 +1,21 @@
1
+package com.reactnativenavigation.viewcontrollers.bottomtabs.attachmode;
2
+
3
+import com.reactnativenavigation.viewcontrollers.*;
4
+import com.reactnativenavigation.viewcontrollers.bottomtabs.*;
5
+
6
+import org.junit.*;
7
+
8
+public class TogetherTest extends AttachModeTest {
9
+
10
+    @Override
11
+    public void beforeEach() {
12
+        super.beforeEach();
13
+        uut = new Together(parent, tabs, presenter, options);
14
+    }
15
+
16
+    @Test
17
+    public void attach_allTabsAreAttached() {
18
+        uut.attach();
19
+        assertIsChild(parent, tabs.toArray(new ViewController[0]));
20
+    }
21
+}

+ 1
- 1
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenterTest.java 파일 보기

@@ -138,7 +138,7 @@ public class ModalPresenterTest extends BaseTest {
138 138
     public void showModal_waitForRender() {
139 139
         modal1.options.animations.showModal.waitForRender = new Bool(true);
140 140
         uut.showModal(modal1, root, new CommandListenerAdapter());
141
-        verify(modal1).setOnAppearedListener(any());
141
+        verify(modal1).addOnAppearedListener(any());
142 142
         verifyZeroInteractions(animator);
143 143
     }
144 144
 

+ 3
- 1
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/navigator/NavigatorTest.java 파일 보기

@@ -28,6 +28,7 @@ import com.reactnativenavigation.utils.ViewUtils;
28 28
 import com.reactnativenavigation.viewcontrollers.ChildControllersRegistry;
29 29
 import com.reactnativenavigation.viewcontrollers.ComponentViewController;
30 30
 import com.reactnativenavigation.viewcontrollers.ViewController;
31
+import com.reactnativenavigation.viewcontrollers.bottomtabs.BottomTabsAttacher;
31 32
 import com.reactnativenavigation.viewcontrollers.bottomtabs.BottomTabsController;
32 33
 import com.reactnativenavigation.viewcontrollers.modal.ModalStack;
33 34
 import com.reactnativenavigation.viewcontrollers.stack.StackController;
@@ -350,7 +351,8 @@ public class NavigatorTest extends BaseTest {
350 351
 
351 352
     @NonNull
352 353
     private BottomTabsController newTabs(List<ViewController> tabs) {
353
-        return new BottomTabsController(activity, tabs, childRegistry, eventEmitter, imageLoaderMock, "tabsController", new Options(), new Presenter(activity, new Options()), new BottomTabsPresenter(tabs, new Options()), new BottomTabPresenter(activity, tabs, ImageLoaderMock.mock(), new Options())) {
354
+        BottomTabsPresenter bottomTabsPresenter = new BottomTabsPresenter(tabs, new Options());
355
+        return new BottomTabsController(activity, tabs, childRegistry, eventEmitter, imageLoaderMock, "tabsController", new Options(), new Presenter(activity, new Options()), new BottomTabsAttacher(tabs, bottomTabsPresenter), bottomTabsPresenter, new BottomTabPresenter(activity, tabs, ImageLoaderMock.mock(), new Options())) {
354 356
             @NonNull
355 357
             @Override
356 358
             protected BottomTabs createBottomTabs() {

+ 1
- 1
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/navigator/RootPresenterTest.java 파일 보기

@@ -106,7 +106,7 @@ public class RootPresenterTest extends BaseTest {
106 106
         ViewController spy = spy(root);
107 107
         CommandListenerAdapter listener = spy(new CommandListenerAdapter());
108 108
         uut.setRoot(spy, defaultOptions, listener);
109
-        verify(spy).setOnAppearedListener(any());
109
+        verify(spy).addOnAppearedListener(any());
110 110
         assertThat(spy.getView().getAlpha()).isZero();
111 111
         verifyZeroInteractions(listener);
112 112
 

+ 1
- 2
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.java 파일 보기

@@ -203,7 +203,7 @@ public class StackControllerTest extends BaseTest {
203 203
 
204 204
         child2.options.animations.push.waitForRender = new Bool(true);
205 205
         uut.push(child2, new CommandListenerAdapter());
206
-        verify(child2).setOnAppearedListener(any());
206
+        verify(child2).addOnAppearedListener(any());
207 207
         verify(animator, times(0)).push(eq(child1.getView()), eq(child1.options.animations.push), any());
208 208
     }
209 209
 
@@ -989,7 +989,6 @@ public class StackControllerTest extends BaseTest {
989 989
 
990 990
     @Test
991 991
     public void destroy() {
992
-        uut.ensureViewIsCreated();
993 992
         uut.destroy();
994 993
         verify(topBarController, times(1)).clear();
995 994
     }

+ 5
- 0
lib/src/interfaces/Options.ts 파일 보기

@@ -445,6 +445,11 @@ export interface OptionsBottomTabs {
445 445
    * Set a background color for the bottom tabs
446 446
    */
447 447
   backgroundColor?: Color;
448
+  /**
449
+   * Set when tabs are attached to hierarchy consequently when the
450
+   * RootView's constructor is called.
451
+   */
452
+  tabsAttachMode?: 'together' | 'afterInitialTab' | 'onSwitchToTab';
448 453
   /**
449 454
    * Control the Bottom Tabs blur style
450 455
    * #### (iOS specific)