Skip to content

Commit

Permalink
Ensure appLaunched event is emitted only when app is resumed
Browse files Browse the repository at this point in the history
Under certain conditions, app launched event was emitted when the Android Activity isn't in resumed state. In this case, setting root isn't possible since ReactContext doesn't have an instance of the Activity (it can even be destroyed).

This PR ensures the event is emitted only when the Activity is resumed and root can be set.
  • Loading branch information
guyca authored Jul 22, 2019
1 parent a8a97af commit 21584fd
Show file tree
Hide file tree
Showing 10 changed files with 253 additions and 124 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,27 @@

public class LayoutFactory {

private final Activity activity;
private final ChildControllersRegistry childRegistry;
private Activity activity;
private ChildControllersRegistry childRegistry;
private final ReactInstanceManager reactInstanceManager;
private EventEmitter eventEmitter;
private Map<String, ExternalComponentCreator> externalComponentCreators;
private Options defaultOptions;
private final TypefaceLoader typefaceManager;
private TypefaceLoader typefaceManager;

public LayoutFactory(Activity activity, ChildControllersRegistry childRegistry, final ReactInstanceManager reactInstanceManager, EventEmitter eventEmitter, Map<String, ExternalComponentCreator> externalComponentCreators, Options defaultOptions) {
this.activity = activity;
this.childRegistry = childRegistry;
public void setDefaultOptions(Options defaultOptions) {
this.defaultOptions = defaultOptions;
}

public LayoutFactory(final ReactInstanceManager reactInstanceManager) {
this.reactInstanceManager = reactInstanceManager;
}

public void init(Activity activity, EventEmitter eventEmitter, ChildControllersRegistry childRegistry, Map<String, ExternalComponentCreator> externalComponentCreators) {
this.activity = activity;
this.eventEmitter = eventEmitter;
this.childRegistry = childRegistry;
this.externalComponentCreators = externalComponentCreators;
this.defaultOptions = defaultOptions;
typefaceManager = new TypefaceLoader(activity);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import org.json.JSONObject;

public class JSONParser {
public static JSONObject parse(ReadableMap map) {
public JSONObject parse(ReadableMap map) {
try {
ReadableMapKeySetIterator it = map.keySetIterator();
JSONObject result = new JSONObject();
Expand Down Expand Up @@ -41,7 +41,7 @@ public static JSONObject parse(ReadableMap map) {
}
}

public static JSONArray parse(ReadableArray arr) {
public JSONArray parse(ReadableArray arr) {
JSONArray result = new JSONArray();
for (int i = 0; i < arr.size(); i++) {
switch (arr.getType(i)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.reactnativenavigation.react;

import com.facebook.react.bridge.LifecycleEventListener;

public class LifecycleEventListenerAdapter implements LifecycleEventListener {
@Override
public void onHostResume() {

}

@Override
public void onHostPause() {

}

@Override
public void onHostDestroy() {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,161 +25,169 @@
import com.reactnativenavigation.utils.UiThread;
import com.reactnativenavigation.utils.UiUtils;
import com.reactnativenavigation.viewcontrollers.ViewController;
import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalComponentCreator;
import com.reactnativenavigation.viewcontrollers.navigator.Navigator;

import java.util.ArrayList;
import java.util.Map;

public class NavigationModule extends ReactContextBaseJavaModule {
private static final String NAME = "RNNBridgeModule";
private static final String NAME = "RNNBridgeModule";

private final Now now = new Now();
private final ReactInstanceManager reactInstanceManager;
private final ReactInstanceManager reactInstanceManager;
private final JSONParser jsonParser;
private final LayoutFactory layoutFactory;
private EventEmitter eventEmitter;

@SuppressWarnings("WeakerAccess")
public NavigationModule(ReactApplicationContext reactContext, ReactInstanceManager reactInstanceManager) {
super(reactContext);
this.reactInstanceManager = reactInstanceManager;
reactInstanceManager.addReactInstanceEventListener(context -> eventEmitter = new EventEmitter(context));
public NavigationModule(ReactApplicationContext reactContext, ReactInstanceManager reactInstanceManager, LayoutFactory layoutFactory) {
this(reactContext, reactInstanceManager, new JSONParser(), layoutFactory);
}

@Override
public String getName() {
return NAME;
}
public NavigationModule(ReactApplicationContext reactContext, ReactInstanceManager reactInstanceManager, JSONParser jsonParser, LayoutFactory layoutFactory) {
super(reactContext);
this.reactInstanceManager = reactInstanceManager;
this.jsonParser = jsonParser;
this.layoutFactory = layoutFactory;
reactContext.addLifecycleEventListener(new LifecycleEventListenerAdapter() {
@Override
public void onHostResume() {
eventEmitter = new EventEmitter(reactContext);
navigator().setEventEmitter(eventEmitter);
layoutFactory.init(
activity(),
eventEmitter,
navigator().getChildRegistry(),
((NavigationApplication) activity().getApplication()).getExternalComponents()
);
}
});
}

@NonNull
@Override
public String getName() {
return NAME;
}

@ReactMethod
@ReactMethod
public void getConstants(Promise promise) {
ReactApplicationContext ctx = getReactApplicationContext();
WritableMap constants = Arguments.createMap();
constants.putString(Constants.BACK_BUTTON_JS_KEY, Constants.BACK_BUTTON_ID);
constants.putDouble(Constants.BOTTOM_TABS_HEIGHT_KEY, Constants.BOTTOM_TABS_HEIGHT);
constants.putString(Constants.BACK_BUTTON_JS_KEY, Constants.BACK_BUTTON_ID);
constants.putDouble(Constants.BOTTOM_TABS_HEIGHT_KEY, Constants.BOTTOM_TABS_HEIGHT);
constants.putDouble(Constants.STATUS_BAR_HEIGHT_KEY, UiUtils.pxToDp(ctx, UiUtils.getStatusBarHeight(ctx)));
constants.putDouble(Constants.TOP_BAR_HEIGHT_KEY, UiUtils.pxToDp(ctx, UiUtils.getTopBarHeight(ctx)));
constants.putDouble(Constants.TOP_BAR_HEIGHT_KEY, UiUtils.pxToDp(ctx, UiUtils.getTopBarHeight(ctx)));
promise.resolve(constants);
}

@ReactMethod
public void setRoot(String commandId, ReadableMap rawLayoutTree, Promise promise) {
final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(rawLayoutTree).optJSONObject("root"));
handle(() -> {
navigator().setEventEmitter(eventEmitter);
final ViewController viewController = newLayoutFactory().create(layoutTree);
@ReactMethod
public void setRoot(String commandId, ReadableMap rawLayoutTree, Promise promise) {
final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(rawLayoutTree).optJSONObject("root"));
handle(() -> {
final ViewController viewController = layoutFactory.create(layoutTree);
navigator().setRoot(viewController, new NativeCommandListener(commandId, promise, eventEmitter, now), reactInstanceManager);
});
}
}

@ReactMethod
public void setDefaultOptions(ReadableMap options) {
handle(() -> navigator().setDefaultOptions(parse(options)));
@ReactMethod
public void setDefaultOptions(ReadableMap options) {
handle(() -> {
Options defaultOptions = parse(options);
layoutFactory.setDefaultOptions(defaultOptions);
navigator().setDefaultOptions(defaultOptions);
});
}

@ReactMethod
public void mergeOptions(String onComponentId, @Nullable ReadableMap options) {
handle(() -> navigator().mergeOptions(onComponentId, parse(options)));
}
@ReactMethod
public void mergeOptions(String onComponentId, @Nullable ReadableMap options) {
handle(() -> navigator().mergeOptions(onComponentId, parse(options)));
}

@ReactMethod
public void push(String commandId, String onComponentId, ReadableMap rawLayoutTree, Promise promise) {
final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(rawLayoutTree));
handle(() -> {
final ViewController viewController = newLayoutFactory().create(layoutTree);
@ReactMethod
public void push(String commandId, String onComponentId, ReadableMap rawLayoutTree, Promise promise) {
final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(rawLayoutTree));
handle(() -> {
final ViewController viewController = layoutFactory.create(layoutTree);
navigator().push(onComponentId, viewController, new NativeCommandListener(commandId, promise, eventEmitter, now));
});
}
}

@ReactMethod
public void setStackRoot(String commandId, String onComponentId, ReadableArray children, Promise promise) {
handle(() -> {
ArrayList<ViewController> _children = new ArrayList();
for (int i = 0; i < children.size(); i++) {
final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(children.getMap(i)));
_children.add(newLayoutFactory().create(layoutTree));
final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(children.getMap(i)));
_children.add(layoutFactory.create(layoutTree));
}
navigator().setStackRoot(onComponentId, _children, new NativeCommandListener(commandId, promise, eventEmitter, now));
});
}

@ReactMethod
public void pop(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) {
handle(() -> navigator().pop(componentId, parse(mergeOptions), new NativeCommandListener(commandId, promise, eventEmitter, now)));
}
@ReactMethod
public void pop(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) {
handle(() -> navigator().pop(componentId, parse(mergeOptions), new NativeCommandListener(commandId, promise, eventEmitter, now)));
}

@ReactMethod
public void popTo(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) {
handle(() -> navigator().popTo(componentId, parse(mergeOptions), new NativeCommandListener(commandId, promise, eventEmitter, now)));
}
@ReactMethod
public void popTo(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) {
handle(() -> navigator().popTo(componentId, parse(mergeOptions), new NativeCommandListener(commandId, promise, eventEmitter, now)));
}

@ReactMethod
public void popToRoot(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) {
handle(() -> navigator().popToRoot(componentId, parse(mergeOptions), new NativeCommandListener(commandId, promise, eventEmitter, now)));
}
@ReactMethod
public void popToRoot(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) {
handle(() -> navigator().popToRoot(componentId, parse(mergeOptions), new NativeCommandListener(commandId, promise, eventEmitter, now)));
}

@ReactMethod
public void showModal(String commandId, ReadableMap rawLayoutTree, Promise promise) {
final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(rawLayoutTree));
handle(() -> {
final ViewController viewController = newLayoutFactory().create(layoutTree);
@ReactMethod
public void showModal(String commandId, ReadableMap rawLayoutTree, Promise promise) {
final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(rawLayoutTree));
handle(() -> {
final ViewController viewController = layoutFactory.create(layoutTree);
navigator().showModal(viewController, new NativeCommandListener(commandId, promise, eventEmitter, now));
});
}
}

@ReactMethod
public void dismissModal(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) {
handle(() -> {
@ReactMethod
public void dismissModal(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) {
handle(() -> {
navigator().mergeOptions(componentId, parse(mergeOptions));
navigator().dismissModal(componentId, new NativeCommandListener(commandId, promise, eventEmitter, now));
});
}
}

@ReactMethod
public void dismissAllModals(String commandId, @Nullable ReadableMap mergeOptions, Promise promise) {
handle(() -> navigator().dismissAllModals(parse(mergeOptions), new NativeCommandListener(commandId, promise, eventEmitter, now)));
}
public void dismissAllModals(String commandId, @Nullable ReadableMap mergeOptions, Promise promise) {
handle(() -> navigator().dismissAllModals(parse(mergeOptions), new NativeCommandListener(commandId, promise, eventEmitter, now)));
}

@ReactMethod
public void showOverlay(String commandId, ReadableMap rawLayoutTree, Promise promise) {
final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(rawLayoutTree));
@ReactMethod
public void showOverlay(String commandId, ReadableMap rawLayoutTree, Promise promise) {
final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(rawLayoutTree));
handle(() -> {
final ViewController viewController = newLayoutFactory().create(layoutTree);
final ViewController viewController = layoutFactory.create(layoutTree);
navigator().showOverlay(viewController, new NativeCommandListener(commandId, promise, eventEmitter, now));
});
}

@ReactMethod
public void dismissOverlay(String commandId, String componentId, Promise promise) {
handle(() -> navigator().dismissOverlay(componentId, new NativeCommandListener(commandId, promise, eventEmitter, now)));
}

private Navigator navigator() {
return activity().getNavigator();
}
}

@NonNull
private LayoutFactory newLayoutFactory() {
return new LayoutFactory(activity(),
navigator().getChildRegistry(),
reactInstanceManager,
eventEmitter,
externalComponentCreator(),
navigator().getDefaultOptions()
);
}
@ReactMethod
public void dismissOverlay(String commandId, String componentId, Promise promise) {
handle(() -> navigator().dismissOverlay(componentId, new NativeCommandListener(commandId, promise, eventEmitter, now)));
}

private Options parse(@Nullable ReadableMap mergeOptions) {
return mergeOptions == null ? Options.EMPTY : Options.parse(new TypefaceLoader(activity()), JSONParser.parse(mergeOptions));
private Navigator navigator() {
return activity().getNavigator();
}

private Map<String, ExternalComponentCreator> externalComponentCreator() {
return ((NavigationApplication) activity().getApplication()).getExternalComponents();
private Options parse(@Nullable ReadableMap mergeOptions) {
return mergeOptions ==
null ? Options.EMPTY : Options.parse(new TypefaceLoader(activity()), jsonParser.parse(mergeOptions));
}

private void handle(Runnable task) {
if (activity() == null || activity().isFinishing()) return;
UiThread.post(task);
}
private void handle(Runnable task) {
if (activity() == null || activity().isFinishing()) return;
UiThread.post(task);
}

private NavigationActivity activity() {
return (NavigationActivity) getCurrentActivity();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,40 @@
package com.reactnativenavigation.react;

import android.support.annotation.NonNull;

import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import com.reactnativenavigation.parse.LayoutFactory;

import java.util.Collections;
import java.util.List;

public class NavigationPackage implements ReactPackage {

private ReactNativeHost reactNativeHost;
private ReactNativeHost reactNativeHost;

@SuppressWarnings("WeakerAccess")
@SuppressWarnings("WeakerAccess")
public NavigationPackage(final ReactNativeHost reactNativeHost) {
this.reactNativeHost = reactNativeHost;
}
this.reactNativeHost = reactNativeHost;
}

@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.singletonList(new NavigationModule(reactContext, reactNativeHost.getReactInstanceManager()));
}
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
return Collections.singletonList(new NavigationModule(
reactContext,
reactNativeHost.getReactInstanceManager(),
new LayoutFactory(reactNativeHost.getReactInstanceManager())
)
);
}

@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
@NonNull
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.singletonList(new ElementViewManager());
}
}
Loading

0 comments on commit 21584fd

Please sign in to comment.