One of the criticisms of React-Native technology is the slow pace of opening up its applications, especially for the most complex ones. Part of this slowness comes from the initialization flow of native bridges at the first opening of the application.
At the opening of the app, the bridge between react native and android code initialize all your packages, keeping users hanging on the splash screen of your application.
During that time, the bridge lists the modules contained in the application in a CompositeReactPackage class, then establishes for each of these modules a list of the constants and methods exposed.
This method does not allow to detect unused packages in the Js code, so all modules, even useless modules, weigh on the initialization of the app.
The MainApplication without turbo modules should be like that :
++pre>++code class="has-line-data" data-line-start="183" data-line-end="218">class MainApplication : ReactApplication {
private val mReactNativeHost = object : ReactNativeHost(this) {
override fun getJSBundleFile(): String? {
return CodePush.getJSBundleFile()
}
override fun getUseDeveloperSupport(): Boolean {
return BuildConfig.DEBUG
}
override fun getPackages(): List<ReactPackage> {
return listOf(
MainReactPackage(),
MyCustomPackage(),
)
}
override fun getJSMainModuleName(): String {
return "index"
}
}
override fun getReactNativeHost(): ReactNativeHost {
return mReactNativeHost
}
override fun onCreate() {
super.onCreate()
}
}
++/code>++/pre>
The turbos modules offer a new architecture for initializing native modules.
At the runtime of the application, the TurboModules open a bridge that will later allow the js code to initialize any module at its first use. The module thus created will have the same structure as any other module.
Here a quick sum-up of the initialization process with TurboModules :
The idea is to initialize only one module when the application is opened: the turbo module. The other modules will only be initialized on their first call in the code flow. Thus, the splash screen remains displayed for a shorter period as the initialization of the app only loads the modules that are immediately required.
This architecture is retro compatible with the traditional way of building modules since each module is created as a singleton. An architecture in turbo modules can therefore easily coexist with more classic architectures.
This is particularly interesting as native modules constructed in exotic ways like using a builder, or multiple parameters in the constructor are not supported yet.
Before to install TurboModules, you can convert your files to Kotlin as it will be easier to maintain your code that way. If you prefer to continue using Java, you'll find the equivalent of code above in this gist.
1. Enable Kotlin in the project: Tools > Kotlin > Configure Kotlin in project
2. Convert each native files to Kotlin: Right Click on the file > Convert Java File to Kotlin file
3. You're done
++pre>++code class="has-line-data" data-line-start="220" data-line-end="271">import com.facebook.react.ReactApplication
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.ReactPackage
import com.facebook.react.TurboReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider
import com.facebook.react.shell.MainReactPackage
class MainApplication : ReactApplication {
private val mReactNativeHost = object : ReactNativeHost(this) {
override fun getJSBundleFile(): String? {
return CodePush.getJSBundleFile()
}
override fun getUseDeveloperSupport(): Boolean {
return BuildConfig.DEBUG
}
override fun getPackages(): List<ReactPackage> {
return listOf(
object : TurboReactPackage() {
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule {
when (name) {
MyCustomPackage.MODULE_NAME -> return MyCustomPackage(reactContext)
else -> throw IllegalArgumentException("Could not find module $name")
}
}
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
return ReactModuleInfoProvider {
val map = HashMap<String, ReactModuleInfo>()
map.put(MyCustomPackage.MODULE_NAME, ReactModuleInfo(MyCustomPackage.MODULE_NAME, "com.mypackagename.MyCustomPackage", false, false, false, false, true))
return map
}
}
},
MainReactPackage(),
)
}
override fun getJSMainModuleName(): String {
return "index"
}
}
override fun getReactNativeHost(): ReactNativeHost {
return mReactNativeHost
}
override fun onCreate() {
super.onCreate()
}
}
++/code>++/pre>
The prototype of the function ReactModuleInfo is :
++pre>++code class="has-line-data" data-line-start="273" data-line-end="282">public ReactModuleInfo(
String name,
String className,
boolean canOverrideExistingModule,
boolean needsEagerInit,
boolean hasConstants,
boolean isCxxModule,
boolean isTurboModule)
++/code>++/pre>
You should always set canOverrideExistingModule to false especially if not all your modules are initialized through Turbo Modules. You don't want to risk overriding a previously initialized package.
NeedsEagerInit has to be set to true if you need the module at runtime. It will behave as if you were declaring your package without TurboModules.
HasConstant is almost always true for most packages. It can be true by default is you don't know whereas you package as or not some constants.
isCxxModule has to be set to true for packages containing only C compiled code.
isTurboModule has to be set to true all the time. This argument is here because ReactModuleInfo is not used only for ReactTurboModules.
Congratulations! You are all set. You can now create React-Native apps that open as fast as natives ones!