DEV Community

Cover image for Building React Native In-App Modules without the JS bridge.
Ritesh Shukla
Ritesh Shukla

Posted on

Building React Native In-App Modules without the JS bridge.

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;
Enter fullscreen mode Exit fullscreen mode

Step 2: Codgen Configuration

Add the below configuration to your package.json

 "codegenConfig": {
    "name": "NativeRestartPackageSpec",
    "type": "modules",
    "jsSrcsDir": "specs",
    "android": {
      "javaPackageName": "com.restartApp"
    }
  }
Enter fullscreen mode Exit fullscreen mode

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);
    }

}

Enter fullscreen mode Exit fullscreen mode

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;
                }
        };

    }

}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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)

Top comments (0)