Bridging the Gap: Integrating Custom Native Functionality in Expo with TurboModules

React Native Expo Logo

Introduction

React Native’s ability to bridge native code with JavaScript has been a game-changer for mobile development. With the introduction of TurboModules and JSI (JavaScript Interface), this bridge has become even more powerful and efficient. In this comprehensive guide, we’ll explore how to integrate custom native modules into an Expo-managed React Native application, focusing on the modern TurboModules approach.

Table of Contents

  1. Understanding the Expo Workflow
  2. When to Use Native Modules
  3. TurboModules vs Legacy NativeModules
  4. Setting Up Your Development Environment
  5. Creating a Custom Native Module
  6. Performance Considerations
  7. Best Practices and Conclusion

Understanding the Expo Workflow

Expo provides two main workflows for React Native development:

Managed Workflow

  • Pre-built native code
  • Limited access to native modules
  • Easier development experience
  • No direct access to native code

Bare Workflow

  • Full access to native code
  • Custom native module support
  • More complex setup
  • Greater flexibility

When to Use Native Modules

Scenario Approach
Basic app functionality Managed Workflow
Custom native features Bare Workflow
Performance-critical features Bare Workflow
Third-party SDK integration Bare Workflow

TurboModules vs Legacy NativeModules

Performance Comparison

Feature TurboModules Legacy NativeModules
Initialization Lazy loading Eager loading
Memory usage Lower Higher
Type safety Better Limited
Bridge overhead Minimal Significant

Key Benefits of TurboModules

  1. Lazy Loading: Modules are loaded only when needed
  2. Direct Native Calls: JSI enables direct communication
  3. Type Safety: Better TypeScript integration
  4. Reduced Bridge Overhead: More efficient communication

Setting Up Your Development Environment

  1. Install the Expo CLI:
npm install -g expo-cli
  1. Create a new Expo project:
expo init MyNativeApp
cd MyNativeApp
  1. Eject to bare workflow:
expo prebuild

Creating a Custom Native Module

Let’s create a simple native module that performs a CPU-intensive calculation. We’ll implement this in Kotlin for Android.

1. Create the Native Module Interface

// android/app/src/main/java/com/mynativeapp/NativeCalculatorModule.kt

package com.mynativeapp

import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Promise

class NativeCalculatorModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
    override fun getName() = "NativeCalculator"

    @ReactMethod
    fun calculateFibonacci(n: Int, promise: Promise) {
        try {
            val result = calculateFibonacciNative(n)
            promise.resolve(result)
        } catch (e: Exception) {
            promise.reject("CALCULATION_ERROR", e.message)
        }
    }

    private fun calculateFibonacciNative(n: Int): Int {
        if (n <= 1) return n
        var a = 0
        var b = 1
        for (i in 2..n) {
            val temp = a + b
            a = b
            b = temp
        }
        return b
    }
}

2. Create the Package

// android/app/src/main/java/com/mynativeapp/NativeCalculatorPackage.kt

package com.mynativeapp

import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager

class NativeCalculatorPackage : ReactPackage {
    override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
        return listOf(NativeCalculatorModule(reactContext))
    }

    override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
        return emptyList()
    }
}

3. Register the Package

// android/app/src/main/java/com/mynativeapp/MainApplication.kt

// ... existing imports ...

class MainApplication : Application(), ReactApplication {
    private val mReactNativeHost = object : ReactNativeHost(this) {
        override fun getPackages(): List<ReactPackage> {
            val packages = PackageList(this).packages.toMutableList()
            packages.add(NativeCalculatorPackage())
            return packages
        }
        // ... rest of the implementation
    }
}

4. Using the Module in JavaScript

// App.tsx

import { NativeModules } from 'react-native';

const { NativeCalculator } = NativeModules;

const App = () => {
  const calculateFibonacci = async (n: number) => {
    try {
      const result = await NativeCalculator.calculateFibonacci(n);
      console.log(`Fibonacci(${n}) = ${result}`);
    } catch (error) {
      console.error('Calculation failed:', error);
    }
  };

  return (
    // ... your component JSX
  );
};

Performance Considerations

Space Complexity

  • TurboModules: O(1) for module initialization
  • Legacy NativeModules: O(n) where n is the number of modules

Time Complexity

  • TurboModules: O(1) for method calls
  • Legacy NativeModules: O(1) but with higher constant factors due to bridge overhead

Best Practices and Conclusion

Best Practices

  1. Use TurboModules for new native module development
  2. Implement proper error handling
  3. Consider type safety with Codegen
  4. Profile performance before and after native module integration
  5. Document your native module API thoroughly

When to Use This Approach

  • Performance-critical calculations
  • Complex native functionality
  • Third-party SDK integration
  • Hardware-specific features

Future Considerations

For even better type safety and developer experience, consider implementing Codegen for your native modules. This will be covered in a follow-up post, where we’ll explore how to generate type-safe interfaces automatically.

Additional Resources


Note: This blog post assumes basic knowledge of React Native and Android development. For more detailed information about specific topics, please refer to the official documentation.

Post a Comment

Previous Post Next Post