Browse Source

layout tree parsing

Daniel Zlotin 7 years ago
parent
commit
304bdcd34b

+ 4
- 41
lib/android/app/src/main/java/com/reactnativenavigation/controllers/CommandsHandler.java View File

@@ -4,9 +4,6 @@ import android.os.Bundle;
4 4
 import android.view.View;
5 5
 
6 6
 import com.facebook.react.ReactRootView;
7
-import com.facebook.react.bridge.ReadableArray;
8
-import com.facebook.react.bridge.ReadableMap;
9
-import com.facebook.react.bridge.ReadableMapKeySetIterator;
10 7
 import com.reactnativenavigation.NavigationActivity;
11 8
 import com.reactnativenavigation.NavigationApplication;
12 9
 import com.reactnativenavigation.layout.LayoutFactory;
@@ -15,14 +12,11 @@ import com.reactnativenavigation.layout.bottomtabs.BottomTabsCreator;
15 12
 import com.reactnativenavigation.layout.parse.LayoutNode;
16 13
 import com.reactnativenavigation.utils.UiThread;
17 14
 
18
-import java.util.ArrayList;
19
-import java.util.HashMap;
20
-import java.util.List;
21 15
 import java.util.Map;
22 16
 
23 17
 public class CommandsHandler {
24 18
 
25
-	public void setRoot(final NavigationActivity activity, final ReadableMap layoutTree) {
19
+	public void setRoot(final NavigationActivity activity, final Map<String, Object> layoutTree) {
26 20
 		LayoutFactory factory =
27 21
 				new LayoutFactory(activity, new LayoutFactory.ReactRootViewCreator() {
28 22
 					@Override
@@ -35,12 +29,12 @@ public class CommandsHandler {
35 29
 					}
36 30
 				}, new BottomTabsCreator());
37 31
 
38
-		final LayoutNode layoutTreeRoot = readableMapToLayoutNode(layoutTree);
32
+		final LayoutNode layoutTreeRoot = LayoutNode.fromTree(layoutTree);
39 33
 		final View rootView = factory.create(layoutTreeRoot);
40 34
 		activity.setContentView(rootView);
41 35
 	}
42 36
 
43
-	public void push(final NavigationActivity activity, String onContainerId, final ReadableMap layout) {
37
+	public void push(final NavigationActivity activity, String onContainerId, final Map<String, Object> layoutTree) {
44 38
 		LayoutFactory factory =
45 39
 				new LayoutFactory(activity, new LayoutFactory.ReactRootViewCreator() {
46 40
 					@Override
@@ -52,7 +46,7 @@ public class CommandsHandler {
52 46
 						return rootView;
53 47
 					}
54 48
 				}, new BottomTabsCreator());
55
-		final LayoutNode layoutNode = readableMapToLayoutNode(layout);
49
+		final LayoutNode layoutNode = LayoutNode.fromTree(layoutTree);
56 50
 		final View rootView = factory.create(layoutNode);
57 51
 		((StackLayout) activity.getContentView()).push(rootView);
58 52
 	}
@@ -65,35 +59,4 @@ public class CommandsHandler {
65 59
 			}
66 60
 		});
67 61
 	}
68
-
69
-	private LayoutNode readableMapToLayoutNode(ReadableMap readableMap) {
70
-		String id = readableMap.getString("id");
71
-		LayoutNode.Type type = LayoutNode.Type.fromString(readableMap.getString("type"));
72
-		Map<String, Object> data = readableMapToJavaMap(readableMap.getMap("data"));
73
-
74
-		ReadableArray childrenNodes = readableMap.getArray("children");
75
-		List<LayoutNode> children = new ArrayList<>(childrenNodes.size());
76
-		for (int i = 0; i < childrenNodes.size(); i++) {
77
-			ReadableMap child = childrenNodes.getMap(i);
78
-			children.add(readableMapToLayoutNode(child));
79
-		}
80
-
81
-		return new LayoutNode(id, type, data, children);
82
-	}
83
-
84
-	private Map<String, Object> readableMapToJavaMap(ReadableMap readableMap) {
85
-		final Map<String, Object> map = new HashMap<>();
86
-		for (ReadableMapKeySetIterator it = readableMap.keySetIterator(); it.hasNextKey(); ) {
87
-			final String key = it.nextKey();
88
-			switch (readableMap.getType(key)) {
89
-				case String:
90
-					map.put(key, readableMap.getString(key));
91
-					break;
92
-				case Map:
93
-					map.put(key, readableMapToJavaMap(readableMap.getMap(key)));
94
-					break;
95
-			}
96
-		}
97
-		return map;
98
-	}
99 62
 }

+ 23
- 0
lib/android/app/src/main/java/com/reactnativenavigation/layout/parse/LayoutNode.java View File

@@ -36,4 +36,27 @@ public class LayoutNode {
36 36
 		this.data = data;
37 37
 		this.children = children;
38 38
 	}
39
+
40
+	@SuppressWarnings("unchecked")
41
+	public static LayoutNode fromTree(Map<String, Object> layoutTree) {
42
+		String id = (String) layoutTree.get("id");
43
+		LayoutNode.Type type = LayoutNode.Type.fromString((String) layoutTree.get("type"));
44
+
45
+		Map<String, Object> data;
46
+		if (layoutTree.containsKey("data")) {
47
+			data = (Map<String, Object>) layoutTree.get("data");
48
+		} else {
49
+			data = new HashMap<>();
50
+		}
51
+
52
+		List<LayoutNode> children = new ArrayList<>();
53
+		if (layoutTree.containsKey("children")) {
54
+			List<Object> rawChildren = (List<Object>) layoutTree.get("children");
55
+			for (Object rawChild : rawChildren) {
56
+				children.add(LayoutNode.fromTree((Map<String, Object>) rawChild));
57
+			}
58
+		}
59
+
60
+		return new LayoutNode(id, type, data, children);
61
+	}
39 62
 }

+ 82
- 0
lib/android/app/src/main/java/com/reactnativenavigation/react/ArgsParser.java View File

@@ -0,0 +1,82 @@
1
+package com.reactnativenavigation.react;
2
+
3
+import com.facebook.react.bridge.ReadableArray;
4
+import com.facebook.react.bridge.ReadableMap;
5
+import com.facebook.react.bridge.ReadableMapKeySetIterator;
6
+
7
+import java.util.ArrayList;
8
+import java.util.HashMap;
9
+import java.util.List;
10
+import java.util.Map;
11
+
12
+public class ArgsParser {
13
+	public static Map<String, Object> parse(ReadableMap map) {
14
+		Map<String, Object> result = new HashMap<>();
15
+		ReadableMapKeySetIterator it = map.keySetIterator();
16
+		while (it.hasNextKey()) {
17
+			String key = it.nextKey();
18
+			switch (map.getType(key)) {
19
+				case String:
20
+					result.put(key, map.getString(key));
21
+					break;
22
+				case Number:
23
+					result.put(key, parseNumber(map, key));
24
+					break;
25
+				case Boolean:
26
+					result.put(key, map.getBoolean(key));
27
+					break;
28
+				case Array:
29
+					result.put(key, parseArray(map.getArray(key)));
30
+					break;
31
+				case Map:
32
+					result.put(key, parse(map.getMap(key)));
33
+					break;
34
+				default:
35
+					break;
36
+			}
37
+		}
38
+		return result;
39
+	}
40
+
41
+	public static List<Object> parseArray(ReadableArray arr) {
42
+		List<Object> result = new ArrayList<>();
43
+		for (int i = 0; i < arr.size(); i++) {
44
+			switch (arr.getType(i)) {
45
+				case String:
46
+					result.add(arr.getString(i));
47
+					break;
48
+				case Number:
49
+					result.add(parseNumber(arr, i));
50
+					break;
51
+				case Boolean:
52
+					result.add(arr.getBoolean(i));
53
+					break;
54
+				case Array:
55
+					result.add(parseArray(arr.getArray(i)));
56
+					break;
57
+				case Map:
58
+					result.add(parse(arr.getMap(i)));
59
+					break;
60
+				default:
61
+					break;
62
+			}
63
+		}
64
+		return result;
65
+	}
66
+
67
+	private static Object parseNumber(ReadableMap map, String key) {
68
+		try {
69
+			return map.getInt(key);
70
+		} catch (Exception e) {
71
+			return map.getDouble(key);
72
+		}
73
+	}
74
+
75
+	private static Object parseNumber(ReadableArray arr, int index) {
76
+		try {
77
+			return arr.getInt(index);
78
+		} catch (Exception e) {
79
+			return arr.getDouble(index);
80
+		}
81
+	}
82
+}

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

@@ -29,17 +29,17 @@ public class NavigationModule extends ReactContextBaseJavaModule {
29 29
 		handle(new Runnable() {
30 30
 			@Override
31 31
 			public void run() {
32
-				NavigationApplication.instance.getConfig().commandsHandler.setRoot(activity(), layoutTree);
32
+				NavigationApplication.instance.getConfig().commandsHandler.setRoot(activity(), ArgsParser.parse(layoutTree));
33 33
 			}
34 34
 		});
35 35
 	}
36 36
 
37 37
 	@ReactMethod
38
-	public void push(final String onContainerId, final ReadableMap layout) {
38
+	public void push(final String onContainerId, final ReadableMap layoutTree) {
39 39
 		handle(new Runnable() {
40 40
 			@Override
41 41
 			public void run() {
42
-				NavigationApplication.instance.getConfig().commandsHandler.push(activity(), onContainerId, layout);
42
+				NavigationApplication.instance.getConfig().commandsHandler.push(activity(), onContainerId, ArgsParser.parse(layoutTree));
43 43
 			}
44 44
 		});
45 45
 	}

+ 34
- 0
lib/android/app/src/test/java/com/reactnativenavigation/layout/parse/LayoutNodeTest.java View File

@@ -4,7 +4,13 @@ import com.reactnativenavigation.BaseTest;
4 4
 
5 5
 import org.junit.Test;
6 6
 
7
+import java.util.ArrayList;
8
+import java.util.HashMap;
9
+import java.util.List;
10
+import java.util.Map;
11
+
7 12
 import static org.assertj.core.api.Java6Assertions.assertThat;
13
+import static org.assertj.core.api.Java6Assertions.entry;
8 14
 
9 15
 public class LayoutNodeTest extends BaseTest {
10 16
 	@Test
@@ -25,4 +31,32 @@ public class LayoutNodeTest extends BaseTest {
25 31
 	public void invalidType() throws Exception {
26 32
 		LayoutNode.Type.fromString("unknown");
27 33
 	}
34
+
35
+	@Test
36
+	public void parseFromTree() throws Exception {
37
+		Map<String, Object> tree = new HashMap<>();
38
+		tree.put("id", "node1");
39
+		tree.put("type", "ContainerStack");
40
+		Map<String, Object> rawData = new HashMap<>();
41
+		rawData.put("dataKey", "dataValue");
42
+		tree.put("data", rawData);
43
+		List<Object> rawChildren = new ArrayList<>();
44
+		Map<String, Object> rawChild = new HashMap<>();
45
+		rawChild.put("id", "childId1");
46
+		rawChild.put("type", "Container");
47
+		rawChildren.add(rawChild);
48
+		tree.put("children", rawChildren);
49
+
50
+		LayoutNode result = LayoutNode.fromTree(tree);
51
+
52
+		assertThat(result).isNotNull();
53
+		assertThat(result.id).isEqualTo("node1");
54
+		assertThat(result.type).isEqualTo(LayoutNode.Type.ContainerStack);
55
+		assertThat(result.data).containsOnly(entry("dataKey", "dataValue"));
56
+		assertThat(result.children).hasSize(1);
57
+		assertThat(result.children.get(0).id).isEqualTo("childId1");
58
+		assertThat(result.children.get(0).type).isEqualTo(LayoutNode.Type.Container);
59
+		assertThat(result.children.get(0).data).isEmpty();
60
+		assertThat(result.children.get(0).children).isEmpty();
61
+	}
28 62
 }

+ 53
- 0
lib/android/app/src/test/java/com/reactnativenavigation/react/ArgsParserTest.java View File

@@ -0,0 +1,53 @@
1
+package com.reactnativenavigation.react;
2
+
3
+import com.facebook.react.bridge.JavaOnlyArray;
4
+import com.facebook.react.bridge.JavaOnlyMap;
5
+import com.reactnativenavigation.BaseTest;
6
+
7
+import org.junit.Test;
8
+
9
+import java.util.ArrayList;
10
+import java.util.HashMap;
11
+import java.util.List;
12
+import java.util.Map;
13
+
14
+import static org.assertj.core.api.Java6Assertions.assertThat;
15
+import static org.assertj.core.api.Java6Assertions.entry;
16
+
17
+public class ArgsParserTest extends BaseTest {
18
+	@Test
19
+	public void parsesToMap() throws Exception {
20
+		JavaOnlyMap input = new JavaOnlyMap();
21
+		input.putString("keyString", "stringValue");
22
+		input.putInt("keyInt", 123);
23
+		input.putDouble("keyDouble", 123.456);
24
+		input.putBoolean("keyBoolean", true);
25
+		input.putArray("keyArray", new JavaOnlyArray());
26
+		input.putNull("bla");
27
+
28
+		Map<String, Object> result = ArgsParser.parse(input);
29
+
30
+		assertThat(result).containsOnly(
31
+				entry("keyString", "stringValue"),
32
+				entry("keyInt", 123),
33
+				entry("keyDouble", 123.456),
34
+				entry("keyBoolean", true),
35
+				entry("keyArray", new ArrayList<>())
36
+		);
37
+	}
38
+
39
+	@Test
40
+	public void parsesArrays() throws Exception {
41
+		JavaOnlyArray input = new JavaOnlyArray();
42
+		input.pushString("Hello");
43
+		input.pushInt(123);
44
+		input.pushDouble(123.456);
45
+		input.pushBoolean(true);
46
+		input.pushArray(new JavaOnlyArray());
47
+		input.pushMap(new JavaOnlyMap());
48
+		input.pushNull();
49
+
50
+		List<Object> result = ArgsParser.parseArray(input);
51
+		assertThat(result).containsExactly("Hello", 123, 123.456, true, new ArrayList<>(), new HashMap<>());
52
+	}
53
+}

+ 26
- 2
lib/android/app/src/test/java/com/reactnativenavigation/react/NavigationModuleTest.java View File

@@ -1,28 +1,38 @@
1 1
 package com.reactnativenavigation.react;
2 2
 
3
+import com.facebook.react.bridge.JavaOnlyMap;
3 4
 import com.facebook.react.bridge.ReactApplicationContext;
4 5
 import com.facebook.react.bridge.ReactMethod;
5 6
 import com.reactnativenavigation.BaseTest;
7
+import com.reactnativenavigation.NavigationActivity;
6 8
 import com.reactnativenavigation.NavigationApplication;
7 9
 import com.reactnativenavigation.controllers.CommandsHandler;
8 10
 
9 11
 import org.junit.Before;
10 12
 import org.junit.Test;
13
+import org.mockito.ArgumentCaptor;
14
+import org.robolectric.Robolectric;
11 15
 
12 16
 import java.lang.reflect.Method;
17
+import java.util.HashMap;
13 18
 
14 19
 import static org.assertj.core.api.Java6Assertions.assertThat;
20
+import static org.mockito.ArgumentMatchers.any;
15 21
 import static org.mockito.Mockito.mock;
22
+import static org.mockito.Mockito.times;
23
+import static org.mockito.Mockito.verify;
16 24
 import static org.mockito.Mockito.verifyZeroInteractions;
25
+import static org.mockito.Mockito.when;
17 26
 
18 27
 public class NavigationModuleTest extends BaseTest {
19 28
 
20 29
 	private NavigationModule uut;
30
+	private ReactApplicationContext mockReactApplicationContext;
21 31
 
22 32
 	@Before
23 33
 	public void beforeEach() {
24
-		ReactApplicationContext reactApplicationContext = mock(ReactApplicationContext.class);
25
-		uut = new NavigationModule(reactApplicationContext);
34
+		mockReactApplicationContext = mock(ReactApplicationContext.class);
35
+		uut = new NavigationModule(mockReactApplicationContext);
26 36
 	}
27 37
 
28 38
 	@Test
@@ -44,4 +54,18 @@ public class NavigationModuleTest extends BaseTest {
44 54
 			}
45 55
 		}
46 56
 	}
57
+
58
+	@SuppressWarnings("unchecked")
59
+	@Test
60
+	public void parsesArgumentsIntoMap() throws Exception {
61
+		NavigationApplication.instance.getConfig().commandsHandler = mock(CommandsHandler.class);
62
+		when(mockReactApplicationContext.getCurrentActivity()).thenReturn(Robolectric.buildActivity(NavigationActivity.class).get());
63
+		JavaOnlyMap input = new JavaOnlyMap();
64
+		input.putString("key", "value");
65
+		uut.setRoot(input);
66
+		ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(HashMap.class);
67
+		verify(NavigationApplication.instance.getConfig().commandsHandler, times(1)).setRoot((NavigationActivity) any(), (HashMap<String, Object>) captor.capture());
68
+		assertThat(captor.getAllValues()).hasSize(1);
69
+		assertThat(captor.getValue()).isNotNull();
70
+	}
47 71
 }