Browse Source

Set stack root refactor (#4460)

This PR adds support for setting stack root with multiple children, which is to be expected as stack can be initialised with multiple children.
Closes #4441
Guy Carmeli 6 years ago
parent
commit
2365e0211b
No account linked to committer's email address

+ 9
- 4
lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java View File

9
 import com.facebook.react.bridge.ReactApplicationContext;
9
 import com.facebook.react.bridge.ReactApplicationContext;
10
 import com.facebook.react.bridge.ReactContextBaseJavaModule;
10
 import com.facebook.react.bridge.ReactContextBaseJavaModule;
11
 import com.facebook.react.bridge.ReactMethod;
11
 import com.facebook.react.bridge.ReactMethod;
12
+import com.facebook.react.bridge.ReadableArray;
12
 import com.facebook.react.bridge.ReadableMap;
13
 import com.facebook.react.bridge.ReadableMap;
13
 import com.facebook.react.bridge.WritableMap;
14
 import com.facebook.react.bridge.WritableMap;
14
 import com.reactnativenavigation.NavigationActivity;
15
 import com.reactnativenavigation.NavigationActivity;
27
 import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalComponentCreator;
28
 import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalComponentCreator;
28
 import com.reactnativenavigation.viewcontrollers.navigator.Navigator;
29
 import com.reactnativenavigation.viewcontrollers.navigator.Navigator;
29
 
30
 
31
+import java.util.ArrayList;
30
 import java.util.Map;
32
 import java.util.Map;
31
 
33
 
32
 public class NavigationModule extends ReactContextBaseJavaModule {
34
 public class NavigationModule extends ReactContextBaseJavaModule {
89
 	}
91
 	}
90
 
92
 
91
     @ReactMethod
93
     @ReactMethod
92
-    public void setStackRoot(String commandId, String onComponentId, ReadableMap rawLayoutTree, Promise promise) {
93
-        final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(rawLayoutTree));
94
+    public void setStackRoot(String commandId, String onComponentId, ReadableArray children, Promise promise) {
94
         handle(() -> {
95
         handle(() -> {
95
-            final ViewController viewController = newLayoutFactory().create(layoutTree);
96
-            navigator().setStackRoot(onComponentId, viewController, new NativeCommandListener(commandId, promise, eventEmitter, now));
96
+            ArrayList<ViewController> _children = new ArrayList();
97
+            for (int i = 0; i < children.size(); i++) {
98
+                final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(children.getMap(i)));
99
+                _children.add(newLayoutFactory().create(layoutTree));
100
+            }
101
+            navigator().setStackRoot(onComponentId, _children, new NativeCommandListener(commandId, promise, eventEmitter, now));
97
         });
102
         });
98
     }
103
     }
99
 
104
 

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

83
         return null;
83
         return null;
84
     }
84
     }
85
 
85
 
86
+    public static <T> T last(@Nullable List<T> items) {
87
+        return CollectionUtils.isNullOrEmpty(items) ? null : items.get(items.size() - 1);
88
+    }
89
+
90
+    public static <T> T removeLast(@NonNull List<T> items) {
91
+        return items.remove(items.size() - 1);
92
+    }
93
+
86
     private static @NonNull <T> Collection<T> get(@Nullable Collection<T> t) {
94
     private static @NonNull <T> Collection<T> get(@Nullable Collection<T> t) {
87
         return t == null ? Collections.EMPTY_LIST : t;
95
         return t == null ? Collections.EMPTY_LIST : t;
88
     }
96
     }

+ 25
- 7
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/IdStack.java View File

4
 
4
 
5
 import com.reactnativenavigation.utils.StringUtils;
5
 import com.reactnativenavigation.utils.StringUtils;
6
 
6
 
7
-import java.util.ArrayDeque;
7
+import java.util.ArrayList;
8
 import java.util.Collection;
8
 import java.util.Collection;
9
 import java.util.HashMap;
9
 import java.util.HashMap;
10
 import java.util.Iterator;
10
 import java.util.Iterator;
11
+import java.util.Map;
12
+
13
+import static com.reactnativenavigation.utils.CollectionUtils.last;
14
+import static com.reactnativenavigation.utils.CollectionUtils.removeLast;
11
 
15
 
12
 public class IdStack<E> implements Iterable<String> {
16
 public class IdStack<E> implements Iterable<String> {
13
 
17
 
14
-	private final ArrayDeque<String> deque = new ArrayDeque<>();
15
-	private final HashMap<String, E> map = new HashMap<>();
18
+	private final ArrayList<String> deque = new ArrayList();
19
+	private final Map<String, E> map = new HashMap<>();
16
 
20
 
17
 	public void push(String id, E item) {
21
 	public void push(String id, E item) {
18
-		deque.push(id);
22
+		deque.add(id);
19
 		map.put(id, item);
23
 		map.put(id, item);
20
 	}
24
 	}
21
 
25
 
26
+    public void set(String id, E item, int index) {
27
+        deque.add(index, id);
28
+        map.put(id, item);
29
+    }
30
+
22
 	public E peek() {
31
 	public E peek() {
23
 		if (isEmpty()) {
32
 		if (isEmpty()) {
24
 			return null;
33
 			return null;
25
 		}
34
 		}
26
-		return map.get(deque.peek());
35
+		return map.get(last(deque));
27
 	}
36
 	}
28
 
37
 
29
 	public E pop() {
38
 	public E pop() {
30
 		if (isEmpty()) {
39
 		if (isEmpty()) {
31
 			return null;
40
 			return null;
32
 		}
41
 		}
33
-		return map.remove(deque.pop());
42
+		return map.remove(removeLast(deque));
34
 	}
43
 	}
35
 
44
 
36
 	public boolean isEmpty() {
45
 	public boolean isEmpty() {
42
 	}
51
 	}
43
 
52
 
44
 	public String peekId() {
53
 	public String peekId() {
45
-		return deque.peek();
54
+		return last(deque);
46
 	}
55
 	}
47
 
56
 
48
 	public void clear() {
57
 	public void clear() {
54
 		return map.get(id);
63
 		return map.get(id);
55
 	}
64
 	}
56
 
65
 
66
+	public E get(final int index) {
67
+        return map.get(deque.get(index));
68
+    }
69
+
57
 	public boolean containsId(final String id) {
70
 	public boolean containsId(final String id) {
58
 		return deque.contains(id);
71
 		return deque.contains(id);
59
 	}
72
 	}
80
 	public Collection<E> values() {
93
 	public Collection<E> values() {
81
 		return map.values();
94
 		return map.values();
82
 	}
95
 	}
96
+
97
+    public void remove(Iterator<String> iterator, String id) {
98
+        iterator.remove();
99
+        map.remove(id);
100
+    }
83
 }
101
 }

+ 4
- 3
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/navigator/Navigator.java View File

8
 import android.widget.FrameLayout;
8
 import android.widget.FrameLayout;
9
 
9
 
10
 import com.reactnativenavigation.parse.Options;
10
 import com.reactnativenavigation.parse.Options;
11
-import com.reactnativenavigation.presentation.Presenter;
12
 import com.reactnativenavigation.presentation.OverlayManager;
11
 import com.reactnativenavigation.presentation.OverlayManager;
12
+import com.reactnativenavigation.presentation.Presenter;
13
 import com.reactnativenavigation.react.EventEmitter;
13
 import com.reactnativenavigation.react.EventEmitter;
14
 import com.reactnativenavigation.utils.CommandListener;
14
 import com.reactnativenavigation.utils.CommandListener;
15
 import com.reactnativenavigation.utils.CompatUtils;
15
 import com.reactnativenavigation.utils.CompatUtils;
22
 
22
 
23
 import java.util.Collection;
23
 import java.util.Collection;
24
 import java.util.Collections;
24
 import java.util.Collections;
25
+import java.util.List;
25
 
26
 
26
 public class Navigator extends ParentController {
27
 public class Navigator extends ParentController {
27
 
28
 
148
         applyOnStack(id, listener, stack -> stack.push(viewController, listener));
149
         applyOnStack(id, listener, stack -> stack.push(viewController, listener));
149
     }
150
     }
150
 
151
 
151
-    public void setStackRoot(String id, ViewController viewController, CommandListener listener) {
152
-        applyOnStack(id, listener, stack -> stack.setRoot(viewController, listener));
152
+    public void setStackRoot(String id, List<ViewController> children, CommandListener listener) {
153
+        applyOnStack(id, listener, stack -> stack.setRoot(children, listener));
153
     }
154
     }
154
 
155
 
155
     public void pop(String id, Options mergeOptions, CommandListener listener) {
156
     public void pop(String id, Options mergeOptions, CommandListener listener) {

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

14
 import com.reactnativenavigation.presentation.Presenter;
14
 import com.reactnativenavigation.presentation.Presenter;
15
 import com.reactnativenavigation.presentation.StackPresenter;
15
 import com.reactnativenavigation.presentation.StackPresenter;
16
 import com.reactnativenavigation.react.Constants;
16
 import com.reactnativenavigation.react.Constants;
17
+import com.reactnativenavigation.utils.CollectionUtils;
17
 import com.reactnativenavigation.utils.CommandListener;
18
 import com.reactnativenavigation.utils.CommandListener;
18
 import com.reactnativenavigation.utils.CommandListenerAdapter;
19
 import com.reactnativenavigation.utils.CommandListenerAdapter;
19
 import com.reactnativenavigation.viewcontrollers.ChildControllersRegistry;
20
 import com.reactnativenavigation.viewcontrollers.ChildControllersRegistry;
183
         getView().addView(view, getView().getChildCount() - 1);
184
         getView().addView(view, getView().getChildCount() - 1);
184
     }
185
     }
185
 
186
 
186
-    public void setRoot(ViewController child, CommandListener listener) {
187
-        backButtonHelper.clear(child);
188
-        push(child, new CommandListenerAdapter() {
189
-            @Override
190
-            public void onSuccess(String childId) {
191
-                removeChildrenBellowTop();
192
-                listener.onSuccess(childId);
193
-            }
194
-        });
187
+    public void setRoot(List<ViewController> children, CommandListener listener) {
188
+        if (children.size() == 1) {
189
+            backButtonHelper.clear(CollectionUtils.last(children));
190
+            push(CollectionUtils.last(children), new CommandListenerAdapter() {
191
+                @Override
192
+                public void onSuccess(String childId) {
193
+                    removeChildrenBellowTop();
194
+                    listener.onSuccess(childId);
195
+                }
196
+            });
197
+        } else {
198
+            push(CollectionUtils.last(children), new CommandListenerAdapter() {
199
+                @Override
200
+                public void onSuccess(String childId) {
201
+                    removeChildrenBellowTop();
202
+                    for (int i = 0; i < children.size() - 1; i++) {
203
+                        stack.set(children.get(i).getId(), children.get(i), i);
204
+                        children.get(i).setParentController(StackController.this);
205
+                        if (i == 0) {
206
+                            backButtonHelper.clear(children.get(i));
207
+                        } else {
208
+                            backButtonHelper.addToPushedChild(children.get(i));
209
+                        }
210
+                    }
211
+                    listener.onSuccess(childId);
212
+                }
213
+            });
214
+        }
195
     }
215
     }
196
 
216
 
197
     private void removeChildrenBellowTop() {
217
     private void removeChildrenBellowTop() {
199
         while (stack.size() > 1) {
219
         while (stack.size() > 1) {
200
             ViewController controller = stack.get(iterator.next());
220
             ViewController controller = stack.get(iterator.next());
201
             if (!stack.isTop(controller.getId())) {
221
             if (!stack.isTop(controller.getId())) {
202
-                removeAndDestroyController(controller);
222
+                stack.remove(iterator, controller.getId());
223
+                controller.destroy();
203
             }
224
             }
204
         }
225
         }
205
     }
226
     }
247
             return;
268
             return;
248
         }
269
         }
249
 
270
 
250
-        Iterator<String> iterator = stack.iterator();
251
-        String currentControlId = iterator.next();
252
-        while (!viewController.getId().equals(currentControlId)) {
253
-            if (stack.isTop(currentControlId)) {
254
-                currentControlId = iterator.next();
255
-                continue;
271
+
272
+        String currentControlId;
273
+        for (int i = stack.size() - 2; i >= 0; i--) {
274
+            currentControlId = stack.get(i).getId();
275
+            if (currentControlId.equals(viewController.getId())) {
276
+                break;
256
             }
277
             }
257
-            removeAndDestroyController(stack.get(currentControlId));
258
-            currentControlId = iterator.next();
278
+
279
+            ViewController controller = stack.get(currentControlId);
280
+            stack.remove(controller.getId());
281
+            controller.destroy();
259
         }
282
         }
260
 
283
 
261
         pop(mergeOptions, listener);
284
         pop(mergeOptions, listener);
268
         }
291
         }
269
 
292
 
270
         Iterator<String> iterator = stack.iterator();
293
         Iterator<String> iterator = stack.iterator();
294
+        iterator.next();
271
         while (stack.size() > 2) {
295
         while (stack.size() > 2) {
272
             ViewController controller = stack.get(iterator.next());
296
             ViewController controller = stack.get(iterator.next());
273
             if (!stack.isTop(controller.getId())) {
297
             if (!stack.isTop(controller.getId())) {
274
-                removeAndDestroyController(controller);
298
+                stack.remove(iterator, controller.getId());
299
+                controller.destroy();
275
             }
300
             }
276
         }
301
         }
277
 
302
 

+ 13
- 13
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/IdStackTest.java View File

17
     }
17
     }
18
 
18
 
19
     @Test
19
     @Test
20
-    public void isEmpty() throws Exception {
20
+    public void isEmpty() {
21
         assertThat(uut.isEmpty()).isTrue();
21
         assertThat(uut.isEmpty()).isTrue();
22
         uut.push("123", 123);
22
         uut.push("123", 123);
23
         assertThat(uut.isEmpty()).isFalse();
23
         assertThat(uut.isEmpty()).isFalse();
24
     }
24
     }
25
 
25
 
26
     @Test
26
     @Test
27
-    public void size() throws Exception {
27
+    public void size() {
28
         assertThat(uut.size()).isEqualTo(0);
28
         assertThat(uut.size()).isEqualTo(0);
29
         uut.push("123", 123);
29
         uut.push("123", 123);
30
         assertThat(uut.size()).isEqualTo(1);
30
         assertThat(uut.size()).isEqualTo(1);
31
     }
31
     }
32
 
32
 
33
     @Test
33
     @Test
34
-    public void peek() throws Exception {
34
+    public void peek() {
35
         assertThat(uut.peek()).isNull();
35
         assertThat(uut.peek()).isNull();
36
         uut.push("123", 123);
36
         uut.push("123", 123);
37
         uut.push("456", 456);
37
         uut.push("456", 456);
39
     }
39
     }
40
 
40
 
41
     @Test
41
     @Test
42
-    public void pop() throws Exception {
42
+    public void pop() {
43
         assertThat(uut.pop()).isNull();
43
         assertThat(uut.pop()).isNull();
44
         uut.push("123", 123);
44
         uut.push("123", 123);
45
         uut.push("456", 456);
45
         uut.push("456", 456);
47
     }
47
     }
48
 
48
 
49
     @Test
49
     @Test
50
-    public void peekId() throws Exception {
50
+    public void peekId() {
51
         assertThat(uut.peekId()).isNull();
51
         assertThat(uut.peekId()).isNull();
52
         uut.push("123", 123);
52
         uut.push("123", 123);
53
         assertThat(uut.peekId()).isEqualTo("123");
53
         assertThat(uut.peekId()).isEqualTo("123");
54
     }
54
     }
55
 
55
 
56
     @Test
56
     @Test
57
-    public void clear() throws Exception {
57
+    public void clear() {
58
         uut.push("123", 123);
58
         uut.push("123", 123);
59
         uut.push("456", 456);
59
         uut.push("456", 456);
60
         uut.clear();
60
         uut.clear();
62
     }
62
     }
63
 
63
 
64
     @Test
64
     @Test
65
-    public void getById() throws Exception {
65
+    public void getById() {
66
         assertThat(uut.get("123")).isNull();
66
         assertThat(uut.get("123")).isNull();
67
         uut.push("123", 123);
67
         uut.push("123", 123);
68
         uut.push("456", 456);
68
         uut.push("456", 456);
70
     }
70
     }
71
 
71
 
72
     @Test
72
     @Test
73
-    public void containsId() throws Exception {
73
+    public void containsId() {
74
         assertThat(uut.containsId("123")).isFalse();
74
         assertThat(uut.containsId("123")).isFalse();
75
         uut.push("123", 123);
75
         uut.push("123", 123);
76
         assertThat(uut.containsId("123")).isTrue();
76
         assertThat(uut.containsId("123")).isTrue();
77
     }
77
     }
78
 
78
 
79
     @Test
79
     @Test
80
-    public void remove() throws Exception {
80
+    public void remove() {
81
         assertThat(uut.remove("123")).isNull();
81
         assertThat(uut.remove("123")).isNull();
82
 
82
 
83
         uut.push("123", 123);
83
         uut.push("123", 123);
87
     }
87
     }
88
 
88
 
89
     @Test
89
     @Test
90
-    public void iterableIds() throws Exception {
90
+    public void iterableIds() {
91
         assertThat(uut).isInstanceOf(Iterable.class);
91
         assertThat(uut).isInstanceOf(Iterable.class);
92
         assertThat(uut).isEmpty();
92
         assertThat(uut).isEmpty();
93
         uut.push("123", 123);
93
         uut.push("123", 123);
94
         uut.push("456", 456);
94
         uut.push("456", 456);
95
-        assertThat(uut).containsExactly("456", "123");
95
+        assertThat(uut).containsExactly("123", "456");
96
     }
96
     }
97
 
97
 
98
     @Test
98
     @Test
99
-    public void isTop() throws Exception {
99
+    public void isTop() {
100
         assertThat(uut.isTop("123")).isFalse();
100
         assertThat(uut.isTop("123")).isFalse();
101
         uut.push("123", 123);
101
         uut.push("123", 123);
102
         assertThat(uut.isTop("123")).isTrue();
102
         assertThat(uut.isTop("123")).isTrue();
105
     }
105
     }
106
 
106
 
107
     @Test
107
     @Test
108
-    public void values() throws Exception {
108
+    public void values() {
109
         assertThat(uut.values()).isNotNull().isEmpty();
109
         assertThat(uut.values()).isNotNull().isEmpty();
110
         uut.push("123", 123);
110
         uut.push("123", 123);
111
         uut.push("456", 456);
111
         uut.push("456", 456);

+ 2
- 1
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/navigator/NavigatorTest.java View File

38
 import org.robolectric.annotation.Config;
38
 import org.robolectric.annotation.Config;
39
 
39
 
40
 import java.util.Arrays;
40
 import java.util.Arrays;
41
+import java.util.Collections;
41
 import java.util.List;
42
 import java.util.List;
42
 
43
 
43
 import static org.assertj.core.api.Java6Assertions.assertThat;
44
 import static org.assertj.core.api.Java6Assertions.assertThat;
301
 
302
 
302
         stack.push(child1, new CommandListenerAdapter());
303
         stack.push(child1, new CommandListenerAdapter());
303
         stack.push(child2, new CommandListenerAdapter());
304
         stack.push(child2, new CommandListenerAdapter());
304
-        stack.setRoot(child3, new CommandListenerAdapter());
305
+        stack.setRoot(Collections.singletonList(child3), new CommandListenerAdapter());
305
 
306
 
306
         assertThat(stack.getChildControllers()).containsOnly(child3);
307
         assertThat(stack.getChildControllers()).containsOnly(child3);
307
     }
308
     }

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

100
     public void childrenAreAssignedParent() {
100
     public void childrenAreAssignedParent() {
101
         StackController uut = createStack(Arrays.asList(child1, child2));
101
         StackController uut = createStack(Arrays.asList(child1, child2));
102
         for (ViewController child : uut.getChildControllers()) {
102
         for (ViewController child : uut.getChildControllers()) {
103
-            assertThat(child.getParentController().equals(uut));
103
+            assertThat(child.getParentController().equals(uut)).isTrue();
104
         }
104
         }
105
     }
105
     }
106
 
106
 
204
         assertThat(uut.isEmpty()).isTrue();
204
         assertThat(uut.isEmpty()).isTrue();
205
         uut.push(child1, new CommandListenerAdapter());
205
         uut.push(child1, new CommandListenerAdapter());
206
         uut.push(child2, new CommandListenerAdapter());
206
         uut.push(child2, new CommandListenerAdapter());
207
-        uut.setRoot(child3, new CommandListenerAdapter() {
207
+        uut.setRoot(Collections.singletonList(child3), new CommandListenerAdapter() {
208
             @Override
208
             @Override
209
             public void onSuccess(String childId) {
209
             public void onSuccess(String childId) {
210
                 assertContainsOnlyId(child3.getId());
210
                 assertContainsOnlyId(child3.getId());
213
     }
213
     }
214
 
214
 
215
     @Test
215
     @Test
216
-    public void setRoot() {
216
+    public void setRoot_singleChild() {
217
         activity.setContentView(uut.getView());
217
         activity.setContentView(uut.getView());
218
         disablePushAnimation(child1, child2, child3);
218
         disablePushAnimation(child1, child2, child3);
219
 
219
 
221
         uut.push(child1, new CommandListenerAdapter());
221
         uut.push(child1, new CommandListenerAdapter());
222
         uut.push(child2, new CommandListenerAdapter());
222
         uut.push(child2, new CommandListenerAdapter());
223
         assertThat(uut.getTopBar().getTitleBar().getNavigationIcon()).isNotNull();
223
         assertThat(uut.getTopBar().getTitleBar().getNavigationIcon()).isNotNull();
224
-        uut.setRoot(child3, new CommandListenerAdapter() {
224
+        uut.setRoot(Collections.singletonList(child3), new CommandListenerAdapter() {
225
             @Override
225
             @Override
226
             public void onSuccess(String childId) {
226
             public void onSuccess(String childId) {
227
                 assertContainsOnlyId(child3.getId());
227
                 assertContainsOnlyId(child3.getId());
230
         });
230
         });
231
     }
231
     }
232
 
232
 
233
+    @Test
234
+    public void setRoot_multipleChildren() {
235
+        activity.setContentView(uut.getView());
236
+        disablePushAnimation(child1, child2, child3, child4);
237
+        disablePopAnimation(child4);
238
+
239
+        assertThat(uut.isEmpty()).isTrue();
240
+        uut.push(child1, new CommandListenerAdapter());
241
+        uut.push(child2, new CommandListenerAdapter());
242
+        assertThat(uut.getTopBar().getTitleBar().getNavigationIcon()).isNotNull();
243
+        uut.setRoot(Arrays.asList(child3, child4), new CommandListenerAdapter() {
244
+            @Override
245
+            public void onSuccess(String childId) {
246
+                assertContainsOnlyId(child3.getId(), child4.getId());
247
+                assertThat(uut.getTopBar().getTitleBar().getNavigationIcon()).isNotNull();
248
+                assertThat(child4.isViewShown()).isTrue();
249
+                assertThat(child3.isViewShown()).isFalse();
250
+
251
+                assertThat(uut.getCurrentChild()).isEqualTo(child4);
252
+                uut.pop(Options.EMPTY, new CommandListenerAdapter());
253
+                assertThat(uut.getTopBar().getTitleBar().getNavigationIcon()).isNull();
254
+                assertThat(uut.getCurrentChild()).isEqualTo(child3);
255
+            }
256
+        });
257
+    }
258
+
233
     @Test
259
     @Test
234
     public synchronized void pop() {
260
     public synchronized void pop() {
235
         disablePushAnimation(child1, child2);
261
         disablePushAnimation(child1, child2);

+ 2
- 2
lib/ios/RNNBridgeModule.m View File

49
 	} rejection:reject];
49
 	} rejection:reject];
50
 }
50
 }
51
 
51
 
52
-RCT_EXPORT_METHOD(setStackRoot:(NSString*)commandId componentId:(NSString*)componentId layout:(NSDictionary*)layout resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
53
-	[_commandsHandler setStackRoot:componentId layout:layout completion:^{
52
+RCT_EXPORT_METHOD(setStackRoot:(NSString*)commandId componentId:(NSString*)componentId children:(NSArray*)children resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
53
+	[_commandsHandler setStackRoot:componentId children:children completion:^{
54
 		resolve(componentId);
54
 		resolve(componentId);
55
 	} rejection:reject];
55
 	} rejection:reject];
56
 }
56
 }

+ 1
- 1
lib/ios/RNNCommandsHandler.h View File

25
 
25
 
26
 - (void)popToRoot:(NSString*)componentId mergeOptions:(NSDictionary*)options completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection;
26
 - (void)popToRoot:(NSString*)componentId mergeOptions:(NSDictionary*)options completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection;
27
 
27
 
28
-- (void)setStackRoot:(NSString*)componentId layout:(NSDictionary*)layout completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection;
28
+- (void)setStackRoot:(NSString*)componentId children:(NSArray*)children completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection;
29
 
29
 
30
 - (void)showModal:(NSDictionary*)layout completion:(RNNTransitionWithComponentIdCompletionBlock)completion;
30
 - (void)showModal:(NSDictionary*)layout completion:(RNNTransitionWithComponentIdCompletionBlock)completion;
31
 
31
 

+ 4
- 4
lib/ios/RNNCommandsHandler.m View File

150
 	}
150
 	}
151
 }
151
 }
152
 
152
 
153
-- (void)setStackRoot:(NSString*)componentId layout:(NSDictionary*)layout completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection {
153
+- (void)setStackRoot:(NSString*)componentId children:(NSArray*)children completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection {
154
 	[self assertReady];
154
 	[self assertReady];
155
 	
155
 	
156
-	UIViewController<RNNParentProtocol> *newVC = [_controllerFactory createLayout:layout saveToStore:_store];
157
-	RNNNavigationOptions* options = [newVC getCurrentChild].resolveOptions;
156
+ 	NSArray<RNNLayoutProtocol> *childViewControllers = [_controllerFactory createChildrenLayout:children saveToStore:_store];
157
+	RNNNavigationOptions* options = [childViewControllers.lastObject getCurrentChild].resolveOptions;
158
 	UIViewController *fromVC = [_store findComponentForId:componentId];
158
 	UIViewController *fromVC = [_store findComponentForId:componentId];
159
 	__weak typeof(RNNEventEmitter*) weakEventEmitter = _eventEmitter;
159
 	__weak typeof(RNNEventEmitter*) weakEventEmitter = _eventEmitter;
160
-	[_stackManager setStackRoot:newVC fromViewController:fromVC animated:options.animations.setStackRoot.enable completion:^{
160
+	[_stackManager setStackChildren:childViewControllers fromViewController:fromVC animated:options.animations.setStackRoot.enable completion:^{
161
 		[weakEventEmitter sendOnNavigationCommandCompletion:setStackRoot params:@{@"componentId": componentId}];
161
 		[weakEventEmitter sendOnNavigationCommandCompletion:setStackRoot params:@{@"componentId": componentId}];
162
 		completion();
162
 		completion();
163
 	} rejection:rejection];
163
 	} rejection:rejection];

+ 2
- 0
lib/ios/RNNControllerFactory.h View File

14
 
14
 
15
 - (UIViewController<RNNParentProtocol> *)createLayout:(NSDictionary*)layout saveToStore:(RNNStore *)store;
15
 - (UIViewController<RNNParentProtocol> *)createLayout:(NSDictionary*)layout saveToStore:(RNNStore *)store;
16
 
16
 
17
+- (NSArray<RNNLayoutProtocol> *)createChildrenLayout:(NSArray*)children saveToStore:(RNNStore *)store;
18
+
17
 @property (nonatomic, strong) RNNEventEmitter *eventEmitter;
19
 @property (nonatomic, strong) RNNEventEmitter *eventEmitter;
18
 
20
 
19
 @property (nonatomic, strong) RNNNavigationOptions* defaultOptions;
21
 @property (nonatomic, strong) RNNNavigationOptions* defaultOptions;

+ 10
- 0
lib/ios/RNNControllerFactory.m View File

44
 	return layoutViewController;
44
 	return layoutViewController;
45
 }
45
 }
46
 
46
 
47
+- (NSArray<RNNLayoutProtocol> *)createChildrenLayout:(NSArray*)children saveToStore:(RNNStore *)store {
48
+	_store = store;
49
+	NSMutableArray<RNNLayoutProtocol>* childViewControllers = [NSMutableArray<RNNLayoutProtocol> new];
50
+	for (NSDictionary* layout in children) {
51
+		[childViewControllers addObject:[self fromTree:layout]];
52
+	}
53
+	_store = nil;
54
+	return childViewControllers;
55
+}
56
+
47
 # pragma mark private
57
 # pragma mark private
48
 
58
 
49
 - (UIViewController<RNNParentProtocol> *)fromTree:(NSDictionary*)json {
59
 - (UIViewController<RNNParentProtocol> *)fromTree:(NSDictionary*)json {

+ 1
- 1
lib/ios/RNNNavigationStackManager.h View File

15
 
15
 
16
 - (void)popToRoot:(UIViewController*)viewController animated:(BOOL)animated completion:(RNNPopCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)rejection;
16
 - (void)popToRoot:(UIViewController*)viewController animated:(BOOL)animated completion:(RNNPopCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)rejection;
17
 
17
 
18
-- (void)setStackRoot:(UIViewController *)newRoot fromViewController:(UIViewController *)fromViewController animated:(BOOL)animated completion:(RNNTransitionCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)rejection;
18
+- (void)setStackChildren:(NSArray<UIViewController *> *)children fromViewController:(UIViewController *)fromViewController animated:(BOOL)animated completion:(RNNTransitionCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)rejection;
19
 
19
 
20
 @end
20
 @end

+ 2
- 2
lib/ios/RNNNavigationStackManager.m View File

62
 	}];
62
 	}];
63
 }
63
 }
64
 
64
 
65
-- (void)setStackRoot:(UIViewController *)newRoot fromViewController:(UIViewController *)fromViewController animated:(BOOL)animated completion:(RNNTransitionCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)rejection {
65
+- (void)setStackChildren:(NSArray<UIViewController *> *)children fromViewController:(UIViewController *)fromViewController animated:(BOOL)animated completion:(RNNTransitionCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)rejection {
66
 	UINavigationController* nvc = fromViewController.navigationController;
66
 	UINavigationController* nvc = fromViewController.navigationController;
67
 	
67
 	
68
 	[self performAnimationBlock:^{
68
 	[self performAnimationBlock:^{
69
-		[nvc setViewControllers:@[newRoot] animated:animated];
69
+		[nvc setViewControllers:children animated:animated];
70
 	} completion:completion];
70
 	} completion:completion];
71
 }
71
 }
72
 
72
 

+ 24
- 0
lib/ios/ReactNativeNavigationTests/RNNCommandsHandlerTest.m View File

310
 	[self.store verify];
310
 	[self.store verify];
311
 }
311
 }
312
 
312
 
313
+- (void)testSetStackRoot_resetStackWithSingleComponent {
314
+	OCMStub([self.controllerFactory createChildrenLayout:[OCMArg any] saveToStore:self.store]).andReturn(@[self.vc2]);
315
+	[self.store setReadyToReceiveCommands:true];
316
+	[self.uut setStackRoot:@"vc1" children:nil completion:^{
317
+		
318
+	} rejection:^(NSString *code, NSString *message, NSError *error) {
319
+		
320
+	}];
321
+	XCTAssertEqual(_nvc.viewControllers.firstObject, self.vc2);
322
+	XCTAssertEqual(_nvc.viewControllers.count, 1);
323
+}
324
+
325
+- (void)testSetStackRoot_setMultipleChildren {
326
+	NSArray* newViewControllers = @[_vc1, _vc3];
327
+	OCMStub([self.controllerFactory createChildrenLayout:[OCMArg any] saveToStore:self.store]).andReturn(newViewControllers);
328
+	[self.store setReadyToReceiveCommands:true];
329
+	[self.uut setStackRoot:@"vc1" children:nil completion:^{
330
+		
331
+	} rejection:^(NSString *code, NSString *message, NSError *error) {
332
+		
333
+	}];
334
+	XCTAssertTrue([_nvc.viewControllers isEqual:newViewControllers]);
335
+}
336
+
313
 @end
337
 @end

+ 1
- 1
lib/ios/ReactNativeNavigationTests/RNNNavigationStackManagerTest.m View File

65
 
65
 
66
 - (void)testStackRoot_shouldUpdateNavigationControllerChildrenViewControllers {
66
 - (void)testStackRoot_shouldUpdateNavigationControllerChildrenViewControllers {
67
 	XCTestExpectation *expectation = [self expectationWithDescription:@"Testing Async Method"];
67
 	XCTestExpectation *expectation = [self expectationWithDescription:@"Testing Async Method"];
68
-	[_stackManager setStackRoot:self.vc2 fromViewController:self.vc1 animated:NO completion:^{
68
+	[_stackManager setStackChildren:@[self.vc2] fromViewController:self.vc1 animated:NO completion:^{
69
 		XCTAssertTrue(self.nvc.childViewControllers.count == 1);
69
 		XCTAssertTrue(self.nvc.childViewControllers.count == 1);
70
 		XCTAssertTrue([self.nvc.topViewController isEqual:self.vc2]);
70
 		XCTAssertTrue([self.nvc.topViewController isEqual:self.vc2]);
71
 		[expectation fulfill];
71
 		[expectation fulfill];

+ 4
- 2
lib/src/Navigation.ts View File

1
+import { isArray } from 'lodash';
1
 import { NativeCommandsSender } from './adapters/NativeCommandsSender';
2
 import { NativeCommandsSender } from './adapters/NativeCommandsSender';
2
 import { NativeEventsReceiver } from './adapters/NativeEventsReceiver';
3
 import { NativeEventsReceiver } from './adapters/NativeEventsReceiver';
3
 import { UniqueIdProvider } from './adapters/UniqueIdProvider';
4
 import { UniqueIdProvider } from './adapters/UniqueIdProvider';
146
   /**
147
   /**
147
    * Sets new root component to stack.
148
    * Sets new root component to stack.
148
    */
149
    */
149
-  public setStackRoot(componentId: string, layout: Layout): Promise<any> {
150
-    return this.commands.setStackRoot(componentId, layout);
150
+  public setStackRoot(componentId: string, layout: Layout | Layout[]): Promise<any> {
151
+    const children: Layout[] = isArray(layout) ? layout : [layout];
152
+    return this.commands.setStackRoot(componentId, children);
151
   }
153
   }
152
 
154
 
153
   /**
155
   /**

+ 15
- 13
lib/src/commands/Commands.test.ts View File

289
 
289
 
290
   describe('setStackRoot', () => {
290
   describe('setStackRoot', () => {
291
     it('parses into correct layout node and sends to native', () => {
291
     it('parses into correct layout node and sends to native', () => {
292
-      uut.setStackRoot('theComponentId', { component: { name: 'com.example.MyScreen' } });
293
-      verify(mockedNativeCommandsSender.setStackRoot('setStackRoot+UNIQUE_ID', 'theComponentId', deepEqual({
294
-        type: 'Component',
295
-        id: 'Component+UNIQUE_ID',
296
-        data: {
297
-          name: 'com.example.MyScreen',
298
-          options: {},
299
-          passProps: undefined
300
-        },
301
-        children: []
302
-      }))).called();
292
+      uut.setStackRoot('theComponentId', [{ component: { name: 'com.example.MyScreen' } }]);
293
+      verify(mockedNativeCommandsSender.setStackRoot('setStackRoot+UNIQUE_ID', 'theComponentId', deepEqual([
294
+        {
295
+          type: 'Component',
296
+          id: 'Component+UNIQUE_ID',
297
+          data: {
298
+            name: 'com.example.MyScreen',
299
+            options: {},
300
+            passProps: undefined
301
+          },
302
+          children: []
303
+        }
304
+      ]))).called();
303
     });
305
     });
304
   });
306
   });
305
 
307
 
396
         pop: ['id', {}],
398
         pop: ['id', {}],
397
         popTo: ['id', {}],
399
         popTo: ['id', {}],
398
         popToRoot: ['id', {}],
400
         popToRoot: ['id', {}],
399
-        setStackRoot: ['id', {}],
401
+        setStackRoot: ['id', [{}]],
400
         showOverlay: [{}],
402
         showOverlay: [{}],
401
         dismissOverlay: ['id'],
403
         dismissOverlay: ['id'],
402
         getLaunchArgs: ['id']
404
         getLaunchArgs: ['id']
412
         pop: { commandId: 'pop+UNIQUE_ID', componentId: 'id', mergeOptions: {} },
414
         pop: { commandId: 'pop+UNIQUE_ID', componentId: 'id', mergeOptions: {} },
413
         popTo: { commandId: 'popTo+UNIQUE_ID', componentId: 'id', mergeOptions: {} },
415
         popTo: { commandId: 'popTo+UNIQUE_ID', componentId: 'id', mergeOptions: {} },
414
         popToRoot: { commandId: 'popToRoot+UNIQUE_ID', componentId: 'id', mergeOptions: {} },
416
         popToRoot: { commandId: 'popToRoot+UNIQUE_ID', componentId: 'id', mergeOptions: {} },
415
-        setStackRoot: { commandId: 'setStackRoot+UNIQUE_ID', componentId: 'id', layout: 'parsed' },
417
+        setStackRoot: { commandId: 'setStackRoot+UNIQUE_ID', componentId: 'id', layout: ['parsed'] },
416
         showOverlay: { commandId: 'showOverlay+UNIQUE_ID', layout: 'parsed' },
418
         showOverlay: { commandId: 'showOverlay+UNIQUE_ID', layout: 'parsed' },
417
         dismissOverlay: { commandId: 'dismissOverlay+UNIQUE_ID', componentId: 'id' },
419
         dismissOverlay: { commandId: 'dismissOverlay+UNIQUE_ID', componentId: 'id' },
418
         getLaunchArgs: { commandId: 'getLaunchArgs+UNIQUE_ID' },
420
         getLaunchArgs: { commandId: 'getLaunchArgs+UNIQUE_ID' },

+ 8
- 8
lib/src/commands/Commands.ts View File

113
     return result;
113
     return result;
114
   }
114
   }
115
 
115
 
116
-  public setStackRoot(componentId: string, simpleApi: Layout) {
117
-    const input = _.cloneDeep(simpleApi);
118
-
119
-    const layout = this.layoutTreeParser.parse(input);
120
-    this.layoutTreeCrawler.crawl(layout);
121
-
116
+  public setStackRoot(componentId: string, children: Layout[]) {
117
+    const input = _.map(_.cloneDeep(children), (simpleApi) => {
118
+      const layout = this.layoutTreeParser.parse(simpleApi);
119
+      this.layoutTreeCrawler.crawl(layout);
120
+      return layout;
121
+    });
122
     const commandId = this.uniqueIdProvider.generate('setStackRoot');
122
     const commandId = this.uniqueIdProvider.generate('setStackRoot');
123
-    const result = this.nativeCommandsSender.setStackRoot(commandId, componentId, layout);
124
-    this.commandsObserver.notify('setStackRoot', { commandId, componentId, layout });
123
+    const result = this.nativeCommandsSender.setStackRoot(commandId, componentId, input);
124
+    this.commandsObserver.notify('setStackRoot', { commandId, componentId, layout: input });
125
     return result;
125
     return result;
126
   }
126
   }
127
 
127
 

+ 38
- 15
playground/src/screens/PushedScreen.js View File

168
   }
168
   }
169
 
169
 
170
   async onClickSetStackRoot() {
170
   async onClickSetStackRoot() {
171
-    await Navigation.setStackRoot(this.props.componentId, {
172
-      component: {
173
-        name: 'navigation.playground.PushedScreen',
174
-        passProps: {
175
-          stackPosition: this.getStackPosition() + 1,
176
-          previousScreenIds: _.concat([], this.props.previousScreenIds || [], this.props.componentId)
177
-        },
178
-        options: {
179
-          animations: {
180
-            setStackRoot: {
181
-              enabled: false
171
+    await Navigation.setStackRoot(this.props.componentId, [
172
+      {
173
+        component: {
174
+          name: 'navigation.playground.PushedScreen',
175
+          passProps: {
176
+            stackPosition: this.getStackPosition() + 1,
177
+            previousScreenIds: _.concat([], this.props.previousScreenIds || [], this.props.componentId)
178
+          },
179
+          options: {
180
+            animations: {
181
+              setStackRoot: {
182
+                enabled: false
183
+              }
184
+            },
185
+            topBar: {
186
+              title: {
187
+                text: `Pushed ${this.getStackPosition() + 1} a`
188
+              }
182
             }
189
             }
190
+          }
191
+        }
192
+      },
193
+      {
194
+        component: {
195
+          name: 'navigation.playground.PushedScreen',
196
+          passProps: {
197
+            stackPosition: this.getStackPosition() + 1,
198
+            previousScreenIds: _.concat([], this.props.previousScreenIds || [], this.props.componentId)
183
           },
199
           },
184
-          topBar: {
185
-            title: {
186
-              text: `Pushed ${this.getStackPosition() + 1}`
200
+          options: {
201
+            animations: {
202
+              setStackRoot: {
203
+                enabled: false
204
+              }
205
+            },
206
+            topBar: {
207
+              title: {
208
+                text: `Pushed ${this.getStackPosition() + 1} b`
209
+              }
187
             }
210
             }
188
           }
211
           }
189
         }
212
         }
190
       }
213
       }
191
-    });
214
+    ]);
192
   }
215
   }
193
 
216
 
194
   onClickPushWaitForRender = async () => {
217
   onClickPushWaitForRender = async () => {