Introduce ReactPackageTurboModuleManagerDelegate

Summary:
## Summary
People use `ReactPackage` instances to create NativeModules. To make the migration from NativeModule to TurboModule easy, I'm introducing a `TurboModuleManagerDelegate` that understands `ReactPackage`s, and uses them to lookup and create the Java TurboModule objects. This way, we don't have to change the way we declare NativeModules for the migration.

## TurboModule registration
Each application should have its own subclass of `ReactPackageTurboModuleManagerDelegate`. This subclass is a hybrid class with a C++ and a Java part. The Java part can (and probably should) do nothing (for now). The C++ part has to implement the `moduleName -> jni::HostObject` and `moduleName, javaInstance -> jni::HostObject` functions for all TurboModules in the application.

**Use Case: Migrating a NativeModule to TurboModule system**
1. Make the Java NativeModule extend `TurboModule`. (The reason why this doesn't happen automatically is probably because we haven't changed the Java codegen yet).
2. Modify the `moduleName -> jni::HostObject` or `moduleName, javaInstance -> jni::HostObject` functions to return the `TurboModule`.

**Use Case: Adding a new TurboModule**
1. Add the TurboModule to a `ReactPackage` in the application.
2. Modify the `moduleName -> jni::HostObject` or `moduleName, javaInstance -> jni::HostObject` functions to return the TurboModule `jsi::HostObject`.

**Note:** It's also possible to declare TurboModules by overriding the `getModule(String moduleName)` function of `ReactPackageTurboModuleManagerDelegate`. It's not a good idea, because it'll make switching between the NativeModule/TurboModule system difficult.

Reviewed By: mdvacca

Differential Revision: D15209129

fbshipit-source-id: 4b0a303595145be9b19d6f4934f956b91990f859
This commit is contained in:
Ramanpreet Nara
2019-05-22 13:11:53 -07:00
committed by Facebook Github Bot
parent 12c0ec85f7
commit 08d87cdacc
4 changed files with 111 additions and 2 deletions
@@ -76,8 +76,7 @@ public abstract class LazyReactPackage implements ReactPackage {
* @param reactContext
* @return
*/
/* package */
Iterable<ModuleHolder> getNativeModuleIterator(final ReactApplicationContext reactContext) {
public Iterable<ModuleHolder> getNativeModuleIterator(final ReactApplicationContext reactContext) {
final Map<String, ReactModuleInfo> reactModuleInfoMap =
getReactModuleInfoProvider().getReactModuleInfos();
final List<ModuleSpec> nativeModules = getNativeModules(reactContext);
@@ -307,6 +307,10 @@ public class ReactInstanceManager {
return mMemoryPressureRouter;
}
public List<ReactPackage> getPackages() {
return new ArrayList<>(mPackages);
}
private static void initializeSoLoaderIfNecessary(Context applicationContext) {
// Call SoLoader.initialize here, this is required for apps that does not use exopackage and
// does not use SoLoader for loading other native code except from the one used by React Native
@@ -21,6 +21,8 @@ rn_android_library(
react_native_target("java/com/facebook/debug/holder:holder"),
react_native_target("java/com/facebook/react/bridge:interfaces"),
react_native_target("java/com/facebook/react/bridge:bridge"),
react_native_target("java/com/facebook/react/config:config"),
react_native_target("java/com/facebook/react:react"),
":jscallinvokerholder",
],
exported_deps = [
@@ -0,0 +1,104 @@
// Copyright 2004-present Facebook. All Rights Reserved.
package com.facebook.react.turbomodule.core;
import com.facebook.react.ReactPackage;
import com.facebook.react.TurboReactPackage;
import com.facebook.react.bridge.CxxModuleWrapper;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
import java.util.List;
public abstract class ReactPackageTurboModuleManagerDelegate extends TurboModuleManagerDelegate {
private final List<TurboReactPackage> mPackages = new ArrayList<>();
private final Map<String, TurboModule> mModules = new HashMap<>();
private final ReactApplicationContext mReactApplicationContext;
public ReactPackageTurboModuleManagerDelegate(ReactApplicationContext reactApplicationContext, List<ReactPackage> packages) {
super(reactApplicationContext);
mReactApplicationContext = reactApplicationContext;
for (ReactPackage reactPackage : packages) {
if (reactPackage instanceof TurboReactPackage) {
mPackages.add((TurboReactPackage)reactPackage);
}
}
}
@Nullable
@Override
public TurboModule getModule(String moduleName) {
TurboModule module = resolveModule(moduleName);
if (module == null) {
return null;
}
if (module instanceof CxxModuleWrapper) {
return null;
}
return module;
}
@Nullable
@Override
public CxxModuleWrapper getLegacyCxxModule(String moduleName) {
TurboModule module = resolveModule(moduleName);
if (module == null) {
return null;
}
if (!(module instanceof CxxModuleWrapper)) {
return null;
}
return (CxxModuleWrapper)module;
}
private TurboModule resolveModule(String moduleName) {
if (mModules.containsKey(moduleName)) {
return mModules.get(moduleName);
}
NativeModule resolvedModule = null;
for (final TurboReactPackage pkg : mPackages) {
try {
NativeModule module = pkg.getModule(moduleName, mReactApplicationContext);
if (resolvedModule == null || module != null && module.canOverrideExistingModule()) {
resolvedModule = module;
}
} catch (IllegalArgumentException ex) {
/**
* TurboReactPackages can throw an IllegalArgumentException when a module
* isn't found. If this happens, it's safe to ignore the exception because
* a later TurboReactPackage could provide the module.
*/
}
}
if (resolvedModule instanceof TurboModule) {
mModules.put(moduleName, (TurboModule)resolvedModule);
} else {
/**
* 1. The list of TurboReactPackages doesn't change.
* 2. TurboReactPackage.getModule is deterministic. Therefore, any two
* invocations of TurboReactPackage.getModule will return the same result
* given that they're provided the same arguments.
*
* Hence, if module lookup fails once, we know it'll fail every time.
* Therefore, we can write null to the mModules Map and avoid doing this
* extra work.
*/
mModules.put(moduleName, null);
}
return mModules.get(moduleName);
}
}