diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/options/LayoutFactory.java b/lib/android/app/src/main/java/com/reactnativenavigation/options/LayoutFactory.java index 7e3ff064736..8a667162040 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/options/LayoutFactory.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/options/LayoutFactory.java @@ -1,9 +1,11 @@ package com.reactnativenavigation.options; import android.app.Activity; +import android.content.Context; import com.facebook.react.ReactInstanceManager; import com.facebook.react.bridge.ReactContext; +import com.reactnativenavigation.NavigationApplication; import com.reactnativenavigation.options.parsers.TypefaceLoader; import com.reactnativenavigation.react.events.EventEmitter; import com.reactnativenavigation.utils.Assertions; @@ -45,6 +47,8 @@ import static com.reactnativenavigation.options.Options.parse; import static com.reactnativenavigation.utils.CollectionUtils.*; +import org.json.JSONObject; + public class LayoutFactory { private Activity activity; private ChildControllersRegistry childRegistry; @@ -75,15 +79,15 @@ public ViewController create(final LayoutNode node) { final ReactContext context = reactInstanceManager.getCurrentReactContext(); switch (node.type) { case Component: - return createComponent(context, node); + return createComponent(node); case ExternalComponent: return createExternalComponent(context, node); case Stack: - return createStack(context, node); + return createStack(node); case BottomTabs: - return createBottomTabs(context, node); + return createBottomTabs(node); case SideMenuRoot: - return createSideMenuRoot(context, node); + return createSideMenuRoot(node); case SideMenuCenter: return createSideMenuContent(node); case SideMenuLeft: @@ -91,17 +95,17 @@ public ViewController create(final LayoutNode node) { case SideMenuRight: return createSideMenuRight(node); case TopTabs: - return createTopTabs(context, node); + return createTopTabs(node); default: throw new IllegalArgumentException("Invalid node type: " + node.type); } } - private ViewController createSideMenuRoot(ReactContext context, LayoutNode node) { + private ViewController createSideMenuRoot(LayoutNode node) { SideMenuController sideMenuController = new SideMenuController(activity, childRegistry, node.id, - parse(context, typefaceManager, node.getOptions()), + parseOptions( node.getOptions()), new SideMenuPresenter(), new Presenter(activity, defaultOptions) ); @@ -153,7 +157,7 @@ private ViewController createSideMenuRight(LayoutNode node) { return create(node.children.get(0)); } - private ViewController createComponent(ReactContext context, LayoutNode node) { + private ViewController createComponent(LayoutNode node) { String id = node.id; String name = node.data.optString("name"); return new ComponentViewController(activity, @@ -161,7 +165,7 @@ private ViewController createComponent(ReactContext context, LayoutNode node) id, name, new ComponentViewCreator(reactInstanceManager), - parse(context, typefaceManager, node.getOptions()), + parseOptions(node.getOptions()), new Presenter(activity, defaultOptions), new ComponentPresenter(defaultOptions) ); @@ -178,17 +182,17 @@ private ViewController createExternalComponent(ReactContext context, LayoutNo reactInstanceManager, new EventEmitter(context), new ExternalComponentPresenter(), - parse(context, typefaceManager, node.getOptions()) + parseOptions(node.getOptions()) ); } - private ViewController createStack(ReactContext context, LayoutNode node) { + private ViewController createStack(LayoutNode node) { return new StackControllerBuilder(activity, eventEmitter) .setChildren(createChildren(node.children)) .setChildRegistry(childRegistry) .setTopBarController(new TopBarController()) .setId(node.id) - .setInitialOptions(parse(context, typefaceManager, node.getOptions())) + .setInitialOptions(parseOptions(node.getOptions())) .setStackPresenter(new StackPresenter(activity, new TitleBarReactViewCreator(reactInstanceManager), new TopBarBackgroundViewCreator(reactInstanceManager), @@ -210,7 +214,7 @@ private List> createChildren(List children) { return result; } - private ViewController createBottomTabs(ReactContext context, LayoutNode node) { + private ViewController createBottomTabs(LayoutNode node) { List> tabs = map(node.children, this::create); BottomTabsPresenter bottomTabsPresenter = new BottomTabsPresenter(tabs, defaultOptions, new BottomTabsAnimator()); return new BottomTabsController(activity, @@ -219,24 +223,35 @@ private ViewController createBottomTabs(ReactContext context, LayoutNode node eventEmitter, new ImageLoader(), node.id, - parse(context, typefaceManager, node.getOptions()), + parseOptions( node.getOptions()), new Presenter(activity, defaultOptions), new BottomTabsAttacher(tabs, bottomTabsPresenter, defaultOptions), bottomTabsPresenter, new BottomTabPresenter(activity, tabs, new ImageLoader(), new TypefaceLoader(activity), defaultOptions)); } - private ViewController createTopTabs(ReactContext context, LayoutNode node) { + private ViewController createTopTabs(LayoutNode node) { final List> tabs = new ArrayList<>(); for (int i = 0; i < node.children.size(); i++) { ViewController tabController = create(node.children.get(i)); - Options options = parse(context, typefaceManager, node.children.get(i).getOptions()); + Options options = parseOptions(node.children.get(i).getOptions()); options.setTopTabIndex(i); tabs.add(tabController); } - return new TopTabsController(activity, childRegistry, node.id, tabs, new TopTabsLayoutCreator(activity, tabs), parse(context, typefaceManager, node.getOptions()), new Presenter(activity, defaultOptions)); + return new TopTabsController(activity, childRegistry, node.id, tabs, new TopTabsLayoutCreator(activity, tabs) + , parseOptions(node.getOptions()), new Presenter(activity, defaultOptions)); } + private Options parseOptions(JSONObject jsonOptions) { + Context context = reactInstanceManager.getCurrentReactContext(); + if (context == null) { + context = activity == null ? NavigationApplication.instance : activity; + } + if (typefaceManager == null) { + typefaceManager = new TypefaceLoader(context); + } + return parse(context, typefaceManager, jsonOptions); + } @NonNull @RestrictTo(RestrictTo.Scope.TESTS) public Options getDefaultOptions() { diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/options/Options.java b/lib/android/app/src/main/java/com/reactnativenavigation/options/Options.java index 262086ed3fe..a169c67c0e8 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/options/Options.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/options/Options.java @@ -15,7 +15,7 @@ public class Options { public static final Options EMPTY = new Options(); @NonNull - public static Options parse(Context context, TypefaceLoader typefaceManager, JSONObject json) { + public static Options parse(@NonNull Context context, TypefaceLoader typefaceManager, JSONObject json) { Options result = new Options(); if (json == null) return result; diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/utils/LayoutFactoryTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/utils/LayoutFactoryTest.java index 79fa1378c0a..efa43b037e3 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/utils/LayoutFactoryTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/utils/LayoutFactoryTest.java @@ -15,14 +15,19 @@ import java.util.HashMap; import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.assertj.core.api.Java6Assertions.fail; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class LayoutFactoryTest extends BaseTest { private LayoutFactory uut; + private ReactInstanceManager mockReactInstanceManager; @Override public void beforeEach() { - uut = new LayoutFactory(mock(ReactInstanceManager.class)); + super.beforeEach(); + mockReactInstanceManager = mock(ReactInstanceManager.class); + uut = new LayoutFactory(mockReactInstanceManager); uut.init( newActivity(), Mockito.mock(EventEmitter.class), @@ -36,6 +41,16 @@ public void sanity() throws JSONException { assertThat(uut.create(component())).isNotNull(); } + @Test + public void shouldParseOptionsWhenReactContextIsNull() { + when(mockReactInstanceManager.getCurrentReactContext()).thenReturn(null); + try { + uut.create(component()); + } catch (Exception e) { + fail("Create should not fail! when react instance has null context"); + } + } + @Test public void defaultOptionsAreNotNull() { assertThat(uut.getDefaultOptions()).isNotNull(); @@ -50,6 +65,14 @@ public void defaultOptionsAreNotNull() { } private LayoutNode component() throws JSONException { - return new LayoutNode("Component1", LayoutNode.Type.Component, new JSONObject().put("name", "com.component"), null); + final JSONObject component = new JSONObject(); + final JSONObject layout = new JSONObject(); + final JSONObject backgroundColor = new JSONObject(); + backgroundColor.put("dark",0); + backgroundColor.put("light",1); + layout.put("backgroundColor",backgroundColor ); + component.put("name", "com.component"); + component.put("options",new JSONObject().put("layout", layout)); + return new LayoutNode("Component1", LayoutNode.Type.Component, component, null); } }