-
Notifications
You must be signed in to change notification settings - Fork 24.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Summary: LazyReactPackage is an extension of ReactPackage that allows us to lazily construct native modules. It's a separate class to avoid breaking existing packages both internally and in open source. Reviewed By: astreet Differential Revision: D3334258 fbshipit-source-id: e090e146adc4e8e156cae217689e2258ab9837aa
- Loading branch information
1 parent
dba1ce4
commit 1feb462
Showing
6 changed files
with
323 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
82 changes: 82 additions & 0 deletions
82
ReactAndroid/src/main/java/com/facebook/react/CompositeLazyReactPackage.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/** | ||
* Copyright (c) 2015-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. An additional grant | ||
* of patent rights can be found in the PATENTS file in the same directory. | ||
*/ | ||
|
||
package com.facebook.react; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.HashMap; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
|
||
import com.facebook.react.bridge.JavaScriptModule; | ||
import com.facebook.react.bridge.ModuleSpec; | ||
import com.facebook.react.bridge.ReactApplicationContext; | ||
import com.facebook.react.uimanager.ViewManager; | ||
|
||
public class CompositeLazyReactPackage extends LazyReactPackage { | ||
|
||
private final List<LazyReactPackage> mChildReactPackages; | ||
|
||
/** | ||
* The order in which packages are passed matters. It may happen that a NativeModule or | ||
* or a ViewManager exists in two or more ReactPackages. In that case the latter will win | ||
* i.e. the latter will overwrite the former. This re-occurrence is detected by | ||
* comparing a name of a module. | ||
*/ | ||
public CompositeLazyReactPackage(LazyReactPackage... args) { | ||
mChildReactPackages = Arrays.asList(args); | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
@Override | ||
public List<ModuleSpec> getNativeModules(ReactApplicationContext reactContext) { | ||
// TODO: Consider using proper name here instead of class | ||
// This would require us to use ModuleHolder here | ||
final Map<Class<?>, ModuleSpec> moduleMap = new HashMap<>(); | ||
for (LazyReactPackage reactPackage: mChildReactPackages) { | ||
for (ModuleSpec module: reactPackage.getNativeModules(reactContext)) { | ||
moduleMap.put(module.getType(), module); | ||
} | ||
} | ||
return new ArrayList<>(moduleMap.values()); | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
@Override | ||
public List<Class<? extends JavaScriptModule>> createJSModules() { | ||
final Set<Class<? extends JavaScriptModule>> moduleSet = new HashSet<>(); | ||
for (ReactPackage reactPackage: mChildReactPackages) { | ||
for (Class<? extends JavaScriptModule> jsModule: reactPackage.createJSModules()) { | ||
moduleSet.add(jsModule); | ||
} | ||
} | ||
return new ArrayList(moduleSet); | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
@Override | ||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { | ||
final Map<String, ViewManager> viewManagerMap = new HashMap<>(); | ||
for (ReactPackage reactPackage: mChildReactPackages) { | ||
for (ViewManager viewManager: reactPackage.createViewManagers(reactContext)) { | ||
viewManagerMap.put(viewManager.getName(), viewManager); | ||
} | ||
} | ||
return new ArrayList(viewManagerMap.values()); | ||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
ReactAndroid/src/main/java/com/facebook/react/LazyReactPackage.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/** | ||
* Copyright (c) 2015-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. An additional grant | ||
* of patent rights can be found in the PATENTS file in the same directory. | ||
*/ | ||
|
||
package com.facebook.react; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import com.facebook.react.bridge.ModuleSpec; | ||
import com.facebook.react.bridge.NativeModule; | ||
import com.facebook.react.bridge.ReactApplicationContext; | ||
|
||
/** | ||
* React package supporting lazy creation of native modules. | ||
* | ||
* TODO(t11394819): Make this default and deprecate ReactPackage | ||
*/ | ||
public abstract class LazyReactPackage implements ReactPackage { | ||
/** | ||
* @param reactContext react application context that can be used to create modules | ||
* @return list of module specs that can create the native modules | ||
*/ | ||
public abstract List<ModuleSpec> getNativeModules( | ||
ReactApplicationContext reactContext); | ||
|
||
@Override | ||
public final List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { | ||
List<NativeModule> modules = new ArrayList<>(); | ||
for (ModuleSpec holder : getNativeModules(reactContext)) { | ||
modules.add(holder.getProvider().get()); | ||
} | ||
return modules; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
103 changes: 103 additions & 0 deletions
103
ReactAndroid/src/main/java/com/facebook/react/bridge/ModuleSpec.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
/** | ||
* Copyright (c) 2015-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. An additional grant | ||
* of patent rights can be found in the PATENTS file in the same directory. | ||
*/ | ||
|
||
package com.facebook.react.bridge; | ||
|
||
import javax.annotation.Nullable; | ||
import javax.inject.Provider; | ||
|
||
import java.lang.reflect.Constructor; | ||
|
||
import com.facebook.react.common.build.ReactBuildConfig; | ||
|
||
/** | ||
* A specification for a native module. This exists so that we don't have to pay the cost | ||
* for creation until/if the module is used. | ||
* | ||
* If your module either has a default constructor or one taking ReactApplicationContext you can use | ||
* {@link #simple(Class)} or {@link #simple(Class, ReactApplicationContext)}} methods. | ||
*/ | ||
public class ModuleSpec { | ||
private static final Class[] EMPTY_SIGNATURE = {}; | ||
private static final Class[] CONTEXT_SIGNATURE = { ReactApplicationContext.class }; | ||
|
||
private final Class<? extends NativeModule> mType; | ||
private final Provider<? extends NativeModule> mProvider; | ||
|
||
/** | ||
* Simple spec for modules with a default constructor. | ||
*/ | ||
public static ModuleSpec simple(final Class<? extends NativeModule> type) { | ||
return new ModuleSpec(type, new ConstructorProvider(type, EMPTY_SIGNATURE) { | ||
@Override | ||
public NativeModule get() { | ||
try { | ||
return getConstructor(type, EMPTY_SIGNATURE).newInstance(); | ||
} catch (Exception e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* Simple spec for modules with a constructor taking ReactApplicationContext. | ||
*/ | ||
public static ModuleSpec simple( | ||
final Class<? extends NativeModule> type, | ||
final ReactApplicationContext context) { | ||
return new ModuleSpec(type, new ConstructorProvider(type, CONTEXT_SIGNATURE) { | ||
@Override | ||
public NativeModule get() { | ||
try { | ||
return getConstructor(type, CONTEXT_SIGNATURE).newInstance(context); | ||
} catch (Exception e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
public ModuleSpec(Class<? extends NativeModule> type, Provider<? extends NativeModule> provider) { | ||
mType = type; | ||
mProvider = provider; | ||
} | ||
|
||
public Class<? extends NativeModule> getType() { | ||
return mType; | ||
} | ||
|
||
public Provider<? extends NativeModule> getProvider() { | ||
return mProvider; | ||
} | ||
|
||
private static abstract class ConstructorProvider implements Provider<NativeModule> { | ||
protected @Nullable Constructor<? extends NativeModule> mConstructor; | ||
|
||
public ConstructorProvider(Class<? extends NativeModule> type, Class[] signature) { | ||
if (ReactBuildConfig.DEBUG) { | ||
try { | ||
mConstructor = getConstructor(type, signature); | ||
} catch (NoSuchMethodException e) { | ||
throw new IllegalArgumentException("No such constructor", e); | ||
} | ||
} | ||
} | ||
|
||
protected Constructor<? extends NativeModule> getConstructor( | ||
Class<? extends NativeModule> mType, | ||
Class[] signature) throws NoSuchMethodException { | ||
if (mConstructor != null) { | ||
return mConstructor; | ||
} else { | ||
return mType.getConstructor(signature); | ||
} | ||
} | ||
} | ||
} |
96 changes: 96 additions & 0 deletions
96
ReactAndroid/src/test/java/com/facebook/react/bridge/ModuleSpecTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
/** | ||
* Copyright (c) 2015-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. An additional grant | ||
* of patent rights can be found in the PATENTS file in the same directory. | ||
*/ | ||
|
||
package com.facebook.react.bridge; | ||
|
||
import org.junit.Rule; | ||
import org.junit.runner.RunWith; | ||
import org.junit.Test; | ||
import org.powermock.core.classloader.annotations.PowerMockIgnore; | ||
import org.powermock.core.classloader.annotations.PrepareForTest; | ||
import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor; | ||
import org.powermock.modules.junit4.rule.PowerMockRule; | ||
import org.powermock.reflect.Whitebox; | ||
import org.robolectric.RobolectricTestRunner; | ||
|
||
import com.facebook.react.bridge.annotations.ReactModule; | ||
import com.facebook.react.common.build.ReactBuildConfig; | ||
|
||
import static org.fest.assertions.api.Assertions.assertThat; | ||
import static org.mockito.Mockito.mock; | ||
|
||
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) | ||
@SuppressStaticInitializationFor("com.facebook.react.common.build.ReactBuildConfig") | ||
@PrepareForTest({ReactBuildConfig.class}) | ||
@RunWith(RobolectricTestRunner.class) | ||
public class ModuleSpecTest { | ||
@Rule | ||
public PowerMockRule rule = new PowerMockRule(); | ||
|
||
@Test(expected = IllegalArgumentException.class) | ||
public void testSimpleFailFast() { | ||
Whitebox.setInternalState(ReactBuildConfig.class, "DEBUG", true); | ||
ModuleSpec.simple(ComplexModule.class, mock(ReactApplicationContext.class)); | ||
} | ||
|
||
@Test(expected = IllegalArgumentException.class) | ||
public void testSimpleFailFastDefault() { | ||
Whitebox.setInternalState(ReactBuildConfig.class, "DEBUG", true); | ||
ModuleSpec.simple(ComplexModule.class); | ||
} | ||
|
||
@Test | ||
public void testSimpleNoFailFastRelease() { | ||
Whitebox.setInternalState(ReactBuildConfig.class, "DEBUG", false); | ||
ModuleSpec.simple(ComplexModule.class, mock(ReactApplicationContext.class)); | ||
} | ||
|
||
@Test(expected = RuntimeException.class) | ||
public void testSimpleFailLateRelease() { | ||
Whitebox.setInternalState(ReactBuildConfig.class, "DEBUG", false); | ||
ModuleSpec spec = ModuleSpec.simple(ComplexModule.class, mock(ReactApplicationContext.class)); | ||
spec.getProvider().get(); | ||
} | ||
|
||
@Test | ||
public void testSimpleDefaultConstructor() { | ||
Whitebox.setInternalState(ReactBuildConfig.class, "DEBUG", true); | ||
ModuleSpec spec = ModuleSpec.simple(SimpleModule.class); | ||
assertThat(spec.getProvider().get()).isInstanceOf(SimpleModule.class); | ||
} | ||
|
||
@Test | ||
public void testSimpleContextConstructor() { | ||
Whitebox.setInternalState(ReactBuildConfig.class, "DEBUG", true); | ||
ReactApplicationContext context = mock(ReactApplicationContext.class); | ||
ModuleSpec spec = ModuleSpec.simple(SimpleContextModule.class, context); | ||
|
||
NativeModule module = spec.getProvider().get(); | ||
assertThat(module).isInstanceOf(SimpleContextModule.class); | ||
SimpleContextModule contextModule = (SimpleContextModule) module; | ||
assertThat(contextModule.getReactApplicationContext()).isSameAs(context); | ||
} | ||
|
||
@ReactModule(name = "ComplexModule") | ||
public static class ComplexModule extends BaseJavaModule { | ||
public ComplexModule(int a, int b) { | ||
} | ||
} | ||
|
||
@ReactModule(name = "SimpleModule") | ||
public static class SimpleModule extends BaseJavaModule { | ||
} | ||
|
||
@ReactModule(name = "SimpleContextModule") | ||
public static class SimpleContextModule extends ReactContextBaseJavaModule { | ||
public SimpleContextModule(ReactApplicationContext context) { | ||
super(context); | ||
} | ||
} | ||
} |