Integrate ReactNative(0.61) to both iOS and Android

1. Introduction

ReactNative has developed rapidly in recent years and is widely used by major companies. The official documents include the following integration methods:

Integration with Existing Apps

However, integration according to this document will not be successful unless you are strong and have the ability to avoid pits.

Recently, I wrote another series of articles about ReactNative integration:

Becoming the Big Front End series

Here alone out of their own pit avoidance process, I hope to help you.

2. Premises (Required)

The assumed three-terminal project name is:

  • iOS: SampleIOS
  • Android: SampleAndroid
  • ReactNative: IntegrationRN

IOS and Android projects have been created or already exist. The paths are:

Your / / path/SampleIOS/ SampleAnroid/Copy the code

The path can be arbitrary, but if it is different from the one in this article, you need to be aware of the difference between the relative path you configure and the one in this article

3. Create a ReactNative project

Create a project for integration, not for live development. Create it according to the official integration documentation. The differences are as follows:

  • The relative paths differ between projects
  • Use NPM instead of YARN

Create a project folder

Create IntegrationRN alongside the iOS and Android projects

SampleIOS/
SampleAnroid/
IntegrationRN/
Copy the code

Create package.json and install dependencies

IntegrationRN/
  package.json  +
Copy the code

Content as follows:

{
  "name": "integration-rn"."version": "0.0.1"."private": true."scripts": {
    "start": "react-native start"}}Copy the code

Install dependencies

cd path/to/IntegrationRN
npm install react-native --save
Copy the code

Now that we’re done, we need to install the specified version of React. See the output of the previous command:

NPM WARN [email protected] requires a peer of [email protected] but none is installed.

NPM install [email protected] - saveCopy the code

Create index. Js

IntegrationRN/
  package.json
  index.js      +
Copy the code

Content is

import React from 'react';
import {AppRegistry, StyleSheet, Text, View} from 'react-native';

class HelloWorld extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.hello}>Hello, World</Text>
      </View>); }}var styles = StyleSheet.create({
  container: {
    flex: 1.justifyContent: 'center',},hello: {
    fontSize: 20.textAlign: 'center'.margin: 10,}}); AppRegistry.registerComponent('IntegrationRN', () => HelloWorld);
Copy the code

Test Completion

npm run start
Copy the code

4. The iOS integration

The existing project structure is as follows:

Introduce RN dependencies using POD

If your project is already managed with POD, skip it

Install CocoaPods. There are many ways to install CocoaPods

sudo gem install cocoapods
Copy the code

Write Podfile

Create a Podfile under SampleIOS, alongside sampleios.xcodeProj

If you follow the Podfile content provided by the official documentation, since it is too old, you will be called into the Endless pit:

  1. The React-native relative path needs to be fixed
  2. Several dependent paths need to be fixed
  3. Running discover crashes requires adding more dependencies, but which ones and where are the paths
  4. NativeComponent needs to be added after the end of front-end development.

Stick with the Podfile I’ve been working on

No Bug Podfile

If your project path differs from the preset one, adjust the react_native_path variable

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'
use_frameworks!

target 'SampleIOS' do
    react_native_path = ".. /IntegrationRN/node_modules/react-native"
    pod 'FBLazyVector', :path => "#{react_native_path}/Libraries/FBLazyVector"
    pod 'FBReactNativeSpec', :path => "#{react_native_path}/Libraries/FBReactNativeSpec"
    pod 'RCTRequired', :path => "#{react_native_path}/Libraries/RCTRequired"
    pod 'RCTTypeSafety', :path => "#{react_native_path}/Libraries/TypeSafety"
    pod 'React', :path => "#{react_native_path}"
    pod 'React-Core', :path => "#{react_native_path}"
    pod 'React-Core/DevSupport', :path => "#{react_native_path}"
    pod 'React-CoreModules', :path => "#{react_native_path}/React/CoreModules"
    pod 'React-RCTActionSheet', :path => "#{react_native_path}/Libraries/ActionSheetIOS"
    pod 'React-RCTAnimation', :path => "#{react_native_path}/Libraries/NativeAnimation"
    pod 'React-RCTBlob', :path => "#{react_native_path}/Libraries/Blob"
    pod 'React-RCTImage', :path => "#{react_native_path}/Libraries/Image"
    pod 'React-RCTLinking', :path => "#{react_native_path}/Libraries/LinkingIOS"
    pod 'React-RCTNetwork', :path => "#{react_native_path}/Libraries/Network"
    pod 'React-RCTSettings', :path => "#{react_native_path}/Libraries/Settings"
    pod 'React-RCTText', :path => "#{react_native_path}/Libraries/Text"
    pod 'React-RCTVibration', :path => "#{react_native_path}/Libraries/Vibration"
    pod 'React-Core/RCTWebSocket', :path => "#{react_native_path}"

    pod 'React-cxxreact', :path => "#{react_native_path}/ReactCommon/cxxreact"
    pod 'React-jsi', :path => "#{react_native_path}/ReactCommon/jsi"
    pod 'React-jsiexecutor', :path => "#{react_native_path}/ReactCommon/jsiexecutor"
    pod 'React-jsinspector', :path => "#{react_native_path}/ReactCommon/jsinspector"
    pod 'ReactCommon/jscallinvoker', :path => "#{react_native_path}/ReactCommon"
    pod 'ReactCommon/turbomodule/core', :path => "#{react_native_path}/ReactCommon"

    pod 'Yoga', :path => "#{react_native_path}/ReactCommon/yoga"

    pod 'DoubleConversion', :podspec => "#{react_native_path}/third-party-podspecs/DoubleConversion.podspec"
    pod 'glog', :podspec => "#{react_native_path}/third-party-podspecs/glog.podspec"
    pod 'Folly', :podspec => "#{react_native_path}/third-party-podspecs/Folly.podspec"
end
Copy the code

Then run Pod Install to install the ReactNative dependency

Code integration

Close Xcode and open the project using SampleIOS. Xcworkspace

Next comes easy code integration, modifying the ViewController’s code:

import UIKit
import React

class ViewController: UIViewController {

    override func loadView(a) {
        let jsCodeURL = URL(string: "http://localhost:8081/index.bundle? platform=ios")!
        let rootView = RCTRootView(
            bundleURL: jsCodeURL,
            moduleName: "IntegrationRN",
            initialProperties: [:],
            launchOptions: nil
        )
        self.view = rootView
    }
    
}
Copy the code

Note:

  • If you’re running it on a real machine, turn itlocalhostInstead of youReact Development ServerThe IP
  • ModuleName and our previous js code AppRegistry registerComponent consistent

Finally, the info.plist configuration allows HTTP to be loaded

<key>NSAppTransportSecurity</key>
<dict>
	<key>NSAllowsArbitraryLoads</key>
	<true/>
</dict>
Copy the code

After integration, ReactNative’s development interface is run, showing Hello, World

5. Android integration

Configuration gradle

implementation "com.facebook.react:react-native:+"
Copy the code

Configure maven

allprojects {
    repositories {
        // point to our RN Android source code
        maven { url "$rootDir/.. /IntegrationRN/node_modules/react-native/android"}... }}Copy the code

Code integration

To create an Activity, use the following code. Here I use MainActivity directly:

class MainActivity : Activity(), DefaultHardwareBackBtnHandler {

    private var mReactRootView: ReactRootView? = null
    private var mReactInstanceManager: ReactInstanceManager? = null

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)

        mReactRootView = ReactRootView(this)
        mReactInstanceManager = ReactInstanceManager.builder()
            .setApplication(application)
            .setCurrentActivity(this)
            .setBundleAssetName("index.android.bundle")
            .setJSMainModulePath("index")
            .addPackage(MainReactPackage())
            .setUseDeveloperSupport(BuildConfig.DEBUG)
            .setInitialLifecycleState(LifecycleState.RESUMED)
            .build()
        // The string here (e.g. "MyReactNativeApp") has to match
        // the string in AppRegistry.registerComponent() in index.jsmReactRootView!! .startReactApplication(mReactInstanceManager,"IntegrationRN".null)

        setContentView(mReactRootView)
    }

    override fun invokeDefaultOnBackPressed(a) {
        super.onBackPressed()
    }

    override fun onPause(a) {
        super.onPause() mReactInstanceManager? .onHostPause(this)}override fun onResume(a) {
        super.onResume() mReactInstanceManager? .onHostResume(this.this)}override fun onDestroy(a) {
        super.onDestroy() mReactInstanceManager? .onHostDestroy(this) mReactRootView? .unmountReactApplication() }override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
        if(keyCode == KEYCODE_MENU && mReactInstanceManager ! =null) { mReactInstanceManager!! .showDevOptionsDialog()return true
        }
        return super.onKeyUp(keyCode, event)
    }

    override fun onBackPressed(a) {
        if(mReactInstanceManager ! =null) { mReactInstanceManager!! .onBackPressed() }else {
            super.onBackPressed()
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int.data: Intent){ mReactInstanceManager? .onActivityResult(this, requestCode, resultCode, data)}}Copy the code

SoLoader. The problem of the init

The run encounters the first problem:

java.lang.RuntimeException: SoLoader.init() not yet called

Add to application.oncreate:

SoLoader.init(this.false)
Copy the code

Libhermes. So problem

The operation will run into a second problem, one that has not been officially explained at all

E/AndroidRuntime: FATAL EXCEPTION: create_react_context
    Process: com.example.sampleandroid, PID: 9677
    java.lang.UnsatisfiedLinkError: couldn't find DSO to load: libhermes.so at com.facebook.soloader.SoLoader.doLoadLibraryBySoName(SoLoader.java:738) at com.facebook.soloader.SoLoader.loadLibraryBySoName(SoLoader.java:591) at com.facebook.soloader.SoLoader.loadLibrary(SoLoader.java:529) at com.facebook.soloader.SoLoader.loadLibrary(SoLoader.java:484) at com.facebook.hermes.reactexecutor.HermesExecutor.
      
       (HermesExecutor.java:20) at com.facebook.hermes.reactexecutor.HermesExecutorFactory.create(HermesExecutorFactory.java:27) at com.facebook.react.ReactInstanceManager$5.run(ReactInstanceManager.java:952) at java.lang.Thread.run(Thread.java:929)
      Copy the code

The key information

couldn't find DSO to load: libhermes.so

ReactNative has been using Hermes instead of JavaScriptCore as a JS engine since a certain release

Solution to add hermes dependency package:

implementation "com.facebook.react:react-native:+"
def hermesPath = "$rootDir/.. /IntegrationRN/node_modules/hermes-engine/android/"
debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
Copy the code

So if I go here and I run it on the emulator, it will load normally and say Hello World

Real machine running problem

The following problems may occur:

The crux of the problem is that ReactNative’s DevSupport module uses localhost loaded JS code, but the React development server is on your computer.

The official document also offers a solution:

adb devices
adb reverse tcp:8081 tcp:8081
Copy the code

But this approach is so bad that different developers have to run this section every time

By looking at the source code of the DevSupport module, we came up with a near-perfect solution:

/ / change debug_http_host
val preferences = PreferenceManager.getDefaultSharedPreferences(applicationContext)
preferences.edit().putString("debug_http_host", WebManager.getWebDevServer()).apply()
Copy the code

Finally, if you are using androidx, you may encounter:

Failed resolution of: Landroidx/swiperefreshlayout/widget/SwipeRefreshLayout;

Just keep adding the dependency

implementation 'androidx. Swiperefreshlayout: swiperefreshlayout: 1.1.0 - alpha02'
Copy the code

This time, finally Hello World

conclusion

Integration process is actually very simple, but want to avoid the pit, consult a lot of information, I hope this can reduce your integration time.

Article source: github.com/zzmingo/Sam…

The last

After the integration, the most important issue is to provide the offline package mechanism, which will be updated in my series of articles. Please follow me.