This paper introduces the concrete implementation of Expo on Native Module and its architecture ideas.


Review of previous article:
Kubernetes monitors the landing of Xiaomi

Lead it

The latest big front is the Flutter, which is racing to catch up with React Native. What they have in common, however, is that Native implementations need to be made to use some Native capabilities, and then provided to the DART/JS VM layer in their own bridging manner. That is to say, in Native invocation, the implementation of Native functions can be consistent and reused except for different bridging modes. Moreover, Expo, as the most complete set of React Native tools, is also adjusting its structure and trying to define a unified development standard for Native Modules. The Native Module based on this implementation will have great versatility, not only in Expo projects, but also in Pure React-Native projects, and even in Flutter.

The use of Expo – permissions

Take expo-Permissions as an example, using version 3.0.0.

Reference project link: https://www.npmjs.com/package/expo-permissions/v/3.0.0

Just a few steps to use in react-Native purity projects:

Yarn Add expo-Permissions

Android /settings.gradle adds a project

include ':expo-permissions'
project(':expo-permissions').projectDir = new File(rootProject.projectDir, 
'.. /node_modules/expo-permissions/android')Copy the code

Add a dependency to Android /app/build.gradle

 api project(':expo-permissions')Copy the code

4, repeat the above steps to add expo-react-native adapter, expo-permissions-interface, expo-image-loader-interface, expo-font-interface (adapter Some modules have been mixed in, and I have to add that the Expo team is still doing module splitting.)

5, instantiation ReactModuleRegistryProvider

import expo.modules.permissions.PermissionsPackage; private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider (Arrays. Package > asList (/ / here you can add other Native Module based on expo - the core to realize new PermissionsPackage ()), Arrays.<SingletonModule>asList());Copy the code

Construct the ModuleRegistryAdapter from the previous step, which is a ReactPackage

7. Add the ModuleRegistryAdapter instance to the getPackages return list of ReactNativeHost to complete the Native export

8. Use the following code on the front end

import * as Permissions from 'expo-permissions'
const { status, expires, permissions } = await Permissions.askAsync(Permissions.LOCATION, Permissions.CONTACTS)Copy the code

Note: Declare the permissions required in androidmanifest.xml to dynamically request success.

Also try to use the react – native – unimodules (https://github.com/unimodules/react-native-unimodules) provides some common scripts and Package code generation, in order to simplify the way of reference, Of course, this also requires adding dependencies that you may not want.

Let’s take the Android implementation as an example to briefly look at the specific implementation of Expo on Native Module.

Source code environment construction

1, use the version of the Android 2.10.8, download address: https://github.com/expo/expo/releases/tag/android%2F2.10.8

Android SDK/Android Studio/Anroid NDK 17C

3. Run YARN in $SRC_ROOT/tools_public

Open $SRC_ROOT/ Android in Android Studio

5, IDE synchronization is completed, can directly Run

The project source code screenshot is as follows:

Brief Introduction of Main Projects

Expo-core: Expo Native Module definition

Expo-permissions -interface: a standalone module interface definition for permissions related Native capabilities

Expo-permissions: Implementation of expo-Permissions -interface, based on the interface defined by Expo-core, does not rely on React-Native, so it can be used independently

Expo – the react – native – adapter: The Expo Native Module adapted to react – Native, ReactModuleRegistryProvider in the above example, ModuleRegistryAdapter in this implementation

Modules /expo-flutter- Adapter: ADAPTS the EXPO Native Module to flutter

Expoview: each sub-module integration, core implementation

App: Host App main project, app entry definition, dependent on ExpoView

The hierarchy above is clear, and more and more modules will become interfaces, implemented, and then used independently.

Adaption of EXPO-Core and Expo-React-native Adapter

Above for the expo – the core code files, very little code, mainly is the interface definition, can understand the react for ourselves – native native defined ReactPackage NativeModule/ViewManager and defines it again, this is no rely on independent reunification, It is available to the upper adatper, which we describe as expo-React-native Adapter.

InternalModule

InternalModule is mainly used by other internal modules of EXPO. Users only rely on interfaces. This is a typical dependency inversion method:

public interface InternalModule {  
  List<Class> getExportedInterfaces();
}Copy the code

Their implementation instances are assembled into ModuleRegistry and used as follows:

mEventEmitter = moduleRegistry.getModule(EventEmitter.class);Copy the code

ModuleRegistry is the repository for each Module. Use the corresponding get method to retrieve Module instances.

ExportedModule

ExportedModule subclass is a url that implements Native js functions, corresponding to the React-Native NativeModule. It collects ExpoMethod labeled methods and exposes them to the upper layer; In addition, when invokeExportedMethod is invoked, the invokeExportedMethod is exported to the actual ExpoMethod.

But the js to Native entry should be marked as ReactMethod. Where is it?

NativeModulesProxy callMethod in the expo-React-Native-Adapter project

private final static String NAME = "ExpoNativeModuleProxy";    

@ReactMethod 
public void callMethod(String moduleName, Dynamic methodKeyOrName, ReadableArray arguments, final Promise promise) {    
  String methodName;    
  if (methodKeyOrName.getType() == ReadableType.String) {      
    methodName = methodKeyOrName.asString();    
  } else if (methodKeyOrName.getType() == ReadableType.Number) {     
    methodName = mExportedMethodsReverseKeys.get(moduleName).get(methodKeyOrName.asInt()); 
  } else {      
    promise.reject(UNEXPECTED_ERROR, "Method key is neither a String nor an Integer -- don't know how to map it to method name.");      
    return;    
  }​    
  
  try {      
    List<Object> nativeArguments = getNativeArgumentsForMethod(arguments, mModuleRegistry.getExportedModule(moduleName).getExportedMethodInfos().get(methodName));      
    nativeArguments.add(new PromiseWrapper(promise));​     

    mModuleRegistry.getExportedModule(moduleName).invokeExportedMethod(methodName, nativeArguments);    
  } catch (IllegalArgumentException e) {     
    promise.reject(ARGS_TYPES_MISMATCH_ERROR, e.getMessage(), e);    
  } catch (RuntimeException e) {      
    promise.reject(UNEXPECTED_ERROR, "Encountered an exception while calling native method: " + e.getMessage(), e);    
  } catch (NoSuchMethodException e) {      
    promise.reject(              
            UNDEFINED_METHOD_ERROR,              
            "Method " + methodName + " of Java module " + moduleName + " is undefined.", e ); }}Copy the code

The actual export js layers is ExpoNativeModuleProxy callMethod, again through the mModuleRegistry. Switch getExportedModule ExportedModule invokeExportedMethod, This completes the js to ExpoMethod call.

As for the JS layer itself will have some logic, encapsulate ExpoNativeModuleProxy use. The end result is the expo-Permissions usage we showed above.

ViewManager

The ViewManager of Expo corresponds to the React-native ViewManager, i.e., native UI Components, which are exported to js and used in react Component. The Component export property is labeled ExpoProp.

Finally, the actual export is done in an adaptive manner using the SimpleViewManagerAdapter class of Expo-React-Native Adapter. Such as the following code:

 @Nullable
  @Override
  public Map<String, Object> getConstants() {
    return ViewManagerAdapterUtils.getConstants(mViewManager);
  }
  @Override
  public String getName() {
    return ViewManagerAdapterUtils.getViewManagerAdapterName(mViewManager);
  }
  @ReactProp(name = "proxiedProperties")
  public void setProxiedProperties(V view, ReadableMap proxiedProperties) {
    ViewManagerAdapterUtils.setProxiedProperties(getName(), mViewManager, view, proxiedProperties);
  }
  @Nullable
  @Override
  public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
    return ViewManagerAdapterUtils.getExportedCustomDirectEventTypeConstants(mViewManager);
  }Copy the code

It is ViewManagerAdapterUtils that does the unified mapping from the React-native ViewManager to the Expo ViewManager. For proxiedProperties in js layers also needs to do some adaptation, in NativeViewManagerAdapter. TSX, go here.

summary

Expo core ADAPTS to the upper layer through the internal standard module definition. It not only disintegrates all Native functions for independent use, but also provides them for the use of Flutter, which provides much more convenience and enables Native developers to focus on the development of Native functions. In addition, expo users don’t have to start with the expo family and can customize expo to their needs.

At present, the latest module interface project name of expo Master source code has been changed from expo- prefix to UNI -, as shown below:

Exchange interactive, welcome to leave a comment below.

This article was first published on Xiaomi Cloud technology. Click here to read the article.