Building React Native In-App Modules bypassing the JS bridge.
Ritesh Shukla
Posted on October 29, 2024
Turbo Modules in React Native enhance JavaScript interactions with native code, offering faster, more efficient communication than the traditional bridge model. By leveraging Turbo Modules, developers can seamlessly integrate performance-critical native features directly into their apps, ensuring high speed without relying on external libraries. This guide will show you how to build a native in-app module with Turbo Modules, optimizing your React Native app for a smoother, faster user experience.
In this guide, we will create a basic in-app native module that enables app restarts, utilizing Java for Android and Objective-C++ for iOS.
Step 1: Create spec file
Start by creating folder name specs at the root level. Inside the specs folder create a fileNamed NativeRestartPackage.ts
.
Note that the TurboModules requires the prefix Native to work.
Add the following code
import { TurboModule, TurboModuleRegistry } from "react-native";
export interface Spec extends TurboModule {
restartApp(): void;
}
export default TurboModuleRegistry.get<Spec>("NativeRestartApp") as Spec | null;
Step 2: Codgen Configuration
Add the below configuration to your package.json
"codegenConfig": {
"name": "NativeRestartPackageSpec",
"type": "modules",
"jsSrcsDir": "specs",
"android": {
"javaPackageName": "com.restartApp"
}
}
Step 3: Native Android Code
Create a folder named restartApp at android/app/src/main/java/com
.
Create NativeRestartApp.java
and add the code with all your native logic.
package com.restartApp;
import android.content.Context;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.jakewharton.processphoenix.ProcessPhoenix;
import com.restartApp.NativeRestartPackageSpec;
import android.util.Log;
public class NativeRestartApp extends NativeRestartPackageSpec{
private final Context mContext;
public static final String NAME = "NativeRestartApp";
public NativeRestartApp(ReactApplicationContext reactContext) {
super(reactContext);
this.mContext= reactContext.getApplicationContext();
}
@Override
public String getName() {
return NAME;
}
public void restartApp(){
Log.d("NativeRestartApp", "Restarting app");
ProcessPhoenix.triggerRebirth(mContext);
}
}
Create NativeRestartPackage.java
and add the following code.
package com.restartApp;
import com.facebook.react.TurboReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.react.module.model.ReactModuleInfoProvider;
import java.util.HashMap;
import java.util.Map;
public class NativeRestartPackage extends TurboReactPackage {
@Override
public NativeModule getModule(String name, ReactApplicationContext reactContext){
if (name.equals(NativeRestartApp.NAME)) {
return new NativeRestartApp(reactContext);
} else {
return null;
}
}
@Override
public ReactModuleInfoProvider getReactModuleInfoProvider() {
return new ReactModuleInfoProvider() {
@Override
public Map<String, ReactModuleInfo> getReactModuleInfos() {
Map<String, ReactModuleInfo> moduleInfoMap = new HashMap<>();
moduleInfoMap.put(
NativeRestartApp.NAME,
new ReactModuleInfo(
NativeRestartApp.NAME,
NativeRestartApp.NAME,
false, // canOverrideExistingModule
false, // needsEagerInit
false, // isCxxModule
true // isTurboModule
)
);
return moduleInfoMap;
}
};
}
}
Now its time to add the code into MainApplication
(Since this is an in-app-module and wont be autolinked).
import com.restartApp.NativeRestartPackage
//Rest of code
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
add(NativeRestartPackage())
}
//Rest of code
Step 4: Native iOS Code
Create a new group(using Xcode) in your app named NativeRestartApp.
Create RCTRestartApp.h
#import <Foundation/Foundation.h>
#import <NativeRestartPackageSpec/NativeRestartPackageSpec.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTRootView.h>
#import <React/RCTReloadCommand.h>
NS_ASSUME_NONNULL_BEGIN
@interface RCTRestartApp : NSObject<NativeRestartPackageSpec>
@end
NS_ASSUME_NONNULL_END
Create RCTRestartApp.mm
and add all your native logic here
#import "RCTRestartApp.h"
@implementation RCTRestartApp
RCT_EXPORT_MODULE(NativeRestartApp)
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeRestartPackageSpecJSI>(params);
}
- (void)restartApp{
void (^loadBundle)(void) = ^{
RCTTriggerReloadCommandListeners(@"restart");
};
if ([NSThread isMainThread]) {
loadBundle();
} else {
dispatch_sync(dispatch_get_main_queue(), loadBundle);
}
}
@end
Step 5: Finally the Javascript code
import React from 'react';
import type { PropsWithChildren } from 'react';
import {
Button,
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
useColorScheme,
View,
} from 'react-native';
import {
Colors,
DebugInstructions,
Header,
LearnMoreLinks,
ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen'
import NativeRestartPackage from './specs/NativeRestartPackage'
type SectionProps = PropsWithChildren<{
title: string;
}>;
function Section({ children, title }: SectionProps): React.JSX.Element {
const isDarkMode = useColorScheme() === 'dark';
return (
<View style={styles.sectionContainer}>
</View>
);
}
function App(): React.JSX.Element {
const isDarkMode = useColorScheme() === 'dark';
const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
};
return (
<SafeAreaView style={backgroundStyle}>
<StatusBar
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
backgroundColor={backgroundStyle.backgroundColor}
/>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={backgroundStyle}>
<Header />
<View
style={{
backgroundColor: isDarkMode ? Colors.black : Colors.white,
}}>
<Button title="Click me" onPress={() => {
NativeRestartPackage.restartApp()
}} />
{/* <LearnMoreLinks /> */}
</View>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
},
sectionTitle: {
fontSize: 24,
fontWeight: '600',
},
sectionDescription: {
marginTop: 8,
fontSize: 18,
fontWeight: '400',
},
highlight: {
fontWeight: '700',
},
});
export default App;
Finally Run the app . You should be able to access native functionality bridgeless
Eg Video:-
https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lbbdyisp25wkq3kbpxow.gif
Conclusion
There is no conclusion , it just works like a charm .
Do like(at least for the cover meme XD)
Posted on October 29, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.