Skip to content

Commit

Permalink
Support Context api (#4427)
Browse files Browse the repository at this point in the history
The following PR introduces improved support for Context api and other api's which wrap the root view.

## Context api
Navigation.registerComponent('navigation.playground.ContextScreen', () => (props) => (
  <TitleContext.Provider value={'Title from Provider'}>
    <ContextScreen {...props} />
  </TitleContext.Provider>
), () => ContextScreen);

## Redux
Navigation.registerComponent('navigation.playground.ReduxScreen', () => (props) => (
  <Provider store={reduxStore}>
    <ReduxScreen {...props} />
  </Provider>
), () => ReduxScreen);

## Plain Component - not changed
Navigation.registerComponent('navigation.playground.MyScreen', () => MyScreen);

This PR also upgrades the TypeScript version to 3.2.0 and RN version used in the playground app to 0.57.7

* New Android build flavor - `reactNative57_7`
* Unit test coverage is disabled, for some reason it broke after upgrading to RN 0.57.7
  • Loading branch information
guyca authored Dec 16, 2018
1 parent 231e912 commit 9d36521
Show file tree
Hide file tree
Showing 33 changed files with 501 additions and 163 deletions.
15 changes: 15 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module.exports = function (api) {
api && api.cache(false);
return {
env: {
test: {
presets: [
"module:metro-react-native-babel-preset"
],
plugins: [
"@babel/plugin-proposal-class-properties"
]
}
}
};
}
1 change: 0 additions & 1 deletion index.ios.js

This file was deleted.

1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('./playground/index');
6 changes: 3 additions & 3 deletions integration/redux/Redux.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ describe('redux support', () => {
render() {
return (
<Provider store={store.reduxStore}>
<MyConnectedComponent />
<MyConnectedComponent/>
</Provider>
);
}
};
Navigation.registerComponent('ComponentName', () => HOC);
Navigation.registerComponent('ComponentName', () => (props) => <HOC {...props} />, Provider, store.reduxStore);

const tree = renderer.create(<HOC />);
expect(tree.toJSON().children).toEqual(['no name']);
Expand All @@ -41,7 +41,7 @@ describe('redux support', () => {
);
}
};
const CompFromNavigation = Navigation.registerComponent('ComponentName', () => HOC)();
const CompFromNavigation = Navigation.registerComponent('ComponentName', () => (props) => <HOC {...props} />)();

const tree = renderer.create(<CompFromNavigation componentId='componentId' renderCountIncrement={renderCountIncrement}/>);
expect(tree.toJSON().children).toEqual(['no name']);
Expand Down
2 changes: 1 addition & 1 deletion lib/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ android {
dimension "RNN.reactNativeVersion"
buildConfigField("int", "REACT_NATVE_VERSION_MINOR", "57")
}
reactNative57WixFork {
reactNative57_5 {
dimension "RNN.reactNativeVersion"
buildConfigField("int", "REACT_NATVE_VERSION_MINOR", "57")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.reactnativenavigation.react;

import com.facebook.react.bridge.NativeDeltaClient;
import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener;

import javax.annotation.Nullable;

public class DevBundleDownloadListenerAdapter implements DevBundleDownloadListener, NavigationDevBundleDownloadListener {
@Override
public void onSuccess(@Nullable NativeDeltaClient nativeDeltaClient) {
onSuccess();
}

@Override
public void onSuccess() {

}

@Override
public void onProgress(@Nullable String status, @Nullable Integer done, @Nullable Integer total) {

}

@Override
public void onFailure(Exception cause) {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.reactnativenavigation.react;

import com.facebook.react.bridge.NativeDeltaClient;
import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener;

import javax.annotation.Nullable;

public class JsDevReloadHandlerFacade implements DevBundleDownloadListener, NavigationDevBundleDownloadListener {
@Override
public void onSuccess(@Nullable NativeDeltaClient nativeDeltaClient) {
onSuccess();
}

@Override
public void onProgress(@Nullable String status, @Nullable Integer done, @Nullable Integer total) {

}

@Override
public void onFailure(Exception cause) {

}

@Override
public void onSuccess() {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.reactnativenavigation.react;

import android.app.Application;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.facebook.infer.annotation.Assertions;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactInstanceManagerBuilder;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener;
import com.facebook.react.shell.MainReactPackage;
import com.reactnativenavigation.NavigationApplication;

import java.util.ArrayList;
import java.util.List;

/**
* Default implementation of {@link ReactNativeHost} that includes {@link NavigationPackage}
* and user-defined additional packages.
*/
public class NavigationReactNativeHost extends ReactNativeHost implements BundleDownloadListenerProvider {

private final boolean isDebug;
private final List<ReactPackage> additionalReactPackages;
private @Nullable NavigationDevBundleDownloadListener bundleListener;
private final DevBundleDownloadListener bundleListenerMediator = new DevBundleDownloadListenerAdapter() {
@Override
public void onSuccess() {
if (bundleListener != null) {
bundleListener.onSuccess();
}
}
};

public NavigationReactNativeHost(NavigationApplication application) {
this(application, application.isDebug(), application.createAdditionalReactPackages());
}

@SuppressWarnings("WeakerAccess")
public NavigationReactNativeHost(Application application, boolean isDebug, final List<ReactPackage> additionalReactPackages) {
super(application);
this.isDebug = isDebug;
this.additionalReactPackages = additionalReactPackages;
}

@Override
public void setBundleLoaderListener(NavigationDevBundleDownloadListener listener) {
bundleListener = listener;
}

@Override
public boolean getUseDeveloperSupport() {
return isDebug;
}

@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new ArrayList<>();
boolean hasMainReactPackage = false;
packages.add(new NavigationPackage(this));
if (additionalReactPackages != null) {
for (ReactPackage p : additionalReactPackages) {
if (!(p instanceof NavigationPackage)) {
packages.add(p);
}
if (p instanceof MainReactPackage) hasMainReactPackage = true;
}
}
if (!hasMainReactPackage) {
packages.add(new MainReactPackage());
}
return packages;
}

protected ReactInstanceManager createReactInstanceManager() {
ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
.setApplication(getApplication())
.setJSMainModulePath(getJSMainModuleName())
.setUseDeveloperSupport(getUseDeveloperSupport())
.setRedBoxHandler(getRedBoxHandler())
.setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
.setUIImplementationProvider(getUIImplementationProvider())
.setInitialLifecycleState(LifecycleState.BEFORE_CREATE)
.setDevBundleDownloadListener(getDevBundleDownloadListener());

for (ReactPackage reactPackage : getPackages()) {
builder.addPackage(reactPackage);
}

String jsBundleFile = getJSBundleFile();
if (jsBundleFile != null) {
builder.setJSBundleFile(jsBundleFile);
} else {
builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
}
return builder.build();
}

@SuppressWarnings("WeakerAccess")
@NonNull
protected DevBundleDownloadListener getDevBundleDownloadListener() {
return bundleListenerMediator;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.reactnativenavigation.react;

import com.facebook.react.bridge.NativeDeltaClient;
import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener;

import javax.annotation.Nullable;

public abstract class ReloadHandlerFacade implements DevBundleDownloadListener {
@Override
public void onSuccess(@Nullable NativeDeltaClient nativeDeltaClient) {

}

@Override
public void onProgress(@Nullable String status, @Nullable Integer done, @Nullable Integer total) {

}

@Override
public void onFailure(Exception cause) {

}

protected abstract void onSuccess();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.reactnativenavigation.react;

import android.support.annotation.Nullable;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.UIImplementation;
import com.facebook.react.uimanager.UIImplementationProvider;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.uimanager.common.MeasureSpecProvider;
import com.facebook.react.uimanager.common.SizeMonitoringFrameLayout;
import com.facebook.react.uimanager.events.EventDispatcher;

import java.util.List;

@SuppressWarnings("WeakerAccess")
public class SyncUiImplementation extends UIImplementation {
private static final Object lock = new Object();

public static class Provider extends UIImplementationProvider {
@Override
public UIImplementation createUIImplementation(ReactApplicationContext reactContext, List<ViewManager> viewManagerList, EventDispatcher eventDispatcher, int minTimeLeftInFrameForNonBatchedOperationMs) {
return new SyncUiImplementation(reactContext, viewManagerList, eventDispatcher, minTimeLeftInFrameForNonBatchedOperationMs);
}

@Override
public UIImplementation createUIImplementation(ReactApplicationContext reactContext, UIManagerModule.ViewManagerResolver viewManagerResolver, EventDispatcher eventDispatcher, int minTimeLeftInFrameForNonBatchedOperationMs) {
return new SyncUiImplementation(reactContext, viewManagerResolver, eventDispatcher, minTimeLeftInFrameForNonBatchedOperationMs);
}
}

public SyncUiImplementation(ReactApplicationContext reactContext, List<ViewManager> viewManagerList, EventDispatcher eventDispatcher, int minTimeLeftInFrameForNonBatchedOperationMs) {
super(reactContext, viewManagerList, eventDispatcher, minTimeLeftInFrameForNonBatchedOperationMs);
}

public SyncUiImplementation(ReactApplicationContext reactContext, UIManagerModule.ViewManagerResolver viewManagerResolver, EventDispatcher eventDispatcher, int minTimeLeftInFrameForNonBatchedOperationMs) {
super(reactContext, viewManagerResolver, eventDispatcher, minTimeLeftInFrameForNonBatchedOperationMs);
}

@Override
public void manageChildren(
int viewTag,
@Nullable ReadableArray moveFrom,
@Nullable ReadableArray moveTo,
@Nullable ReadableArray addChildTags,
@Nullable ReadableArray addAtIndices,
@Nullable ReadableArray removeFrom) {
synchronized (lock) {
super.manageChildren(viewTag, moveFrom, moveTo, addChildTags, addAtIndices, removeFrom);
}
}

@Override
public void setChildren(int viewTag, ReadableArray childrenTags) {
synchronized (lock) {
super.setChildren(viewTag, childrenTags);
}
}

@Override
public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
synchronized (lock) {
super.createView(tag, className, rootViewTag, props);
}
}

@Override
public void removeRootShadowNode(int rootViewTag) {
synchronized (lock) {
super.removeRootShadowNode(rootViewTag);
}
}

@Override
public <T extends SizeMonitoringFrameLayout & MeasureSpecProvider> void registerRootView(T rootView, int tag, ThemedReactContext context) {
synchronized (lock) {
super.registerRootView(rootView, tag, context);
}
}
}
13 changes: 6 additions & 7 deletions lib/src/Navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,17 @@ export class NavigationRoot {
private readonly eventsRegistry: EventsRegistry;
private readonly commandsObserver: CommandsObserver;
private readonly componentEventsObserver: ComponentEventsObserver;
private readonly componentWrapper: typeof ComponentWrapper;
private readonly componentWrapper: ComponentWrapper;

constructor() {
this.Element = Element;
this.TouchablePreview = TouchablePreview;
this.componentWrapper = new ComponentWrapper();
this.store = new Store();
this.componentWrapper = ComponentWrapper;
this.nativeEventsReceiver = new NativeEventsReceiver();
this.uniqueIdProvider = new UniqueIdProvider();
this.componentEventsObserver = new ComponentEventsObserver(this.nativeEventsReceiver);
this.componentRegistry = new ComponentRegistry(this.store, this.componentEventsObserver, this.componentWrapper);
this.componentRegistry = new ComponentRegistry(this.store, this.componentEventsObserver);
this.layoutTreeParser = new LayoutTreeParser();
this.layoutTreeCrawler = new LayoutTreeCrawler(this.uniqueIdProvider, this.store);
this.nativeCommandsSender = new NativeCommandsSender();
Expand All @@ -56,9 +56,8 @@ export class NavigationRoot {
* Every navigation component in your app must be registered with a unique name.
* The component itself is a traditional React component extending React.Component.
*/

public registerComponent(componentName: string | number, getComponentClassFunc: ComponentProvider): ComponentProvider {
return this.componentRegistry.registerComponent(componentName, getComponentClassFunc);
public registerComponent(componentName: string | number, componentProvider: ComponentProvider, concreteComponentProvider?: ComponentProvider): ComponentProvider {
return this.componentRegistry.registerComponent(componentName, componentProvider, this.componentWrapper, concreteComponentProvider);
}

/**
Expand All @@ -71,7 +70,7 @@ export class NavigationRoot {
ReduxProvider: any,
reduxStore: any
): ComponentProvider {
return this.componentRegistry.registerComponent(componentName, getComponentClassFunc, ReduxProvider, reduxStore);
return this.componentRegistry.registerComponent(componentName, getComponentClassFunc, this.componentWrapper, undefined, ReduxProvider, reduxStore);
}

/**
Expand Down
5 changes: 2 additions & 3 deletions lib/src/adapters/Element.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import * as React from 'react';
import * as PropTypes from 'prop-types';
import { View, requireNativeComponent } from 'react-native';
import { requireNativeComponent } from 'react-native';

let RNNElement: React.ComponentType<any>;

export class Element extends React.Component<{ elementId: any; resizeMode?: any; }, any> {
static propTypes = {
elementId: PropTypes.string.isRequired,
resizeMode: PropTypes.string,
...View.propTypes
resizeMode: PropTypes.string
};

static defaultProps = {
Expand Down
Loading

0 comments on commit 9d36521

Please sign in to comment.