Skip to main content

Working with Custom Native Code

1. Creating Config Plugins

Config plugins in Expo allow you to modify the native project configuration (e.g., AndroidManifest.xml, Info.plist) or inject code during the build process. They are JavaScript functions that receive a config object and return an updated config object.

Property Description Values
config The Expo config object (app.json or app.config.js) Object
platform The target platform (android or ios) String
projectRoot Path to the project root directory String
configPath Path to the config file (app.json or app.config.js) String

Example: Basic Config Plugin

export default function configPlugin(config) {
  // Modify the config object
  config.android.permissions = ['CAMERA', 'RECORD_AUDIO'];
  config.ios.infoPlist.NSCameraUsageDescription = 'This app uses the camera to take photos.';
  return config;
}

Example: Conditional Plugin

export default function conditionalPlugin(config, { platform }) {
  if (platform === 'android') {
    config.android.permissions.push('ACCESS_FINE_LOCATION');
  }
  return config;
}
Note: Config plugins are processed during the expo prebuild step and must be compatible with the Expo SDK version in use.

Plugin Types

  • Static Plugins: Modify the config object directly.
  • Async Plugins: Can perform asynchronous operations before returning the modified config.
  • Platform-Specific Plugins: Use the platform parameter to apply changes only for a specific platform.

2. Using Expo Modules API

The Expo Modules API provides a way to access native functionality from JavaScript in a cross-platform manner. It allows developers to create custom modules that can be used across iOS, Android, and web platforms.

Feature Description Usage
expo-modules-core Core module for creating custom Expo modules import { requireNativeModule } from 'expo-modules-core';
Native Module Registration Registering a native module with Expo registerNativeModule('MyModule', MyModule);
Expo SDK Version Minimum Expo SDK version required for the module SDK_50

Example: Creating a Custom Module

import { requireNativeModule } from 'expo-modules-core';

const MyModule = requireNativeModule('MyModule');

export function getDeviceName() {
  return MyModule.getDeviceName();
}
Note: Custom modules must be registered in the Expo config to be available at runtime.
Warning: Native modules are not supported in the Expo Go app. They require a development build.

Key Points

  • Use expo-modules-core to access native modules
  • Modules must be registered in the Expo config
  • Custom modules are not available in Expo Go
  • Requires a development build to use

3. Running Prebuild

Prebuild is a feature in Expo that allows you to generate native code for your project. It's particularly useful when you need to integrate with native modules or customize the native code.

Command Description Notes
expo prebuild Generates native code for iOS and Android platforms. Runs pod install on iOS after generating code.
expo prebuild --platform ios Generates native code only for iOS. Useful when you're working on iOS-specific features.
expo prebuild --platform android Generates native code only for Android. Useful when you're working on Android-specific features.
expo prebuild --clean Deletes the native folders and regenerates them. Useful when you want to ensure a clean build environment.

Example: Running Prebuild

expo prebuild
Note: Prebuild requires that you have the native dependencies installed in your project. If you're using Expo SDK 49 or later, prebuild is enabled by default.

4. Modifying Native Files

When working with Expo, modifying native files requires careful handling to ensure compatibility and proper builds. Below are the key considerations and steps for modifying native files in an Expo project.

File Type Description Location Notes
AndroidManifest.xml Configuration file for Android app android/app/src/main/AndroidManifest.xml Used to declare permissions, activities, and services
Info.plist Configuration file for iOS app ios/<ProjectName>/Info.plist Used to configure app settings, permissions, and capabilities
build.gradle Gradle build configuration for Android android/app/build.gradle Used to manage dependencies and build configurations
Podfile CocoaPods configuration for iOS ios/Podfile Used to manage iOS dependencies and build settings

Example: Adding a Permission to AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.CAMERA" />
</manifest>

Example: Adding a Capability to Info.plist

<key>NSCameraUsageDescription</key>
<string>This app uses the camera to scan QR codes.</string>
Note: Modifications to native files in an Expo project require the use of expo-dev-client or a custom development build to ensure that changes are properly applied and tested.
Warning: Direct modifications to native files may break the Expo workflow or cause issues during the build process. Always ensure that changes are compatible with the Expo SDK version in use.

5. Creating Native Modules

Creating native modules in Expo allows you to extend the functionality of your app by integrating with platform-specific code. Native modules can be written in Swift (iOS) or Kotlin/Java (Android).

Step Description Details
1. Setup Initialize the native module project Create a new directory for your native module and set up the necessary files for iOS and Android.
2. iOS Implementation Write Swift code for iOS Implement the native logic in Swift, using Expo's headers and bridging.
3. Android Implementation Write Kotlin/Java code for Android Implement the native logic in Kotlin or Java, using Expo's Android SDK.
4. Linking Link the module to Expo Update the Expo configuration files to include your native module.

Example: Basic iOS Native Module

import ExpoModulesCore

public struct MyModule: Module {
  public func definition() -> ModuleDefinition {
    Name("MyModule")
    
    Function("getValue") { () -> String in
      return "Hello from native module!"
    }
  }
}

Example: Basic Android Native Module

package com.example.mymodule

import expo.modules.core.ModuleDefinition
import expo.modules.core.functions.Promise

class MyModule : Module() {
  fun definition() = ModuleDefinition {
    Name("MyModule")
    
    Function("getValue") { promise: Promise ->
      promise.resolve("Hello from native module!")
    }
  }
}
Note: Native modules must be registered in the Expo configuration files to be accessible from JavaScript.
Warning: Changes to native modules require rebuilding the app and may not be supported in managed workflow.

6. Using Expo Module Config

Property Value Description
name "expo-module-config" Module name for the Expo module
version "1.0.0" Version of the module
platforms ["ios", "android"] Supported platforms for the module
config {} Configuration object for the module

Example: Basic Expo Module Config

{
  "name": "expo-module-config",
  "version": "1.0.0",
  "platforms": [
    "ios",
    "android"
  ],
  "config": {}
}
Note: The expo-module-config is used to define the configuration for custom native modules in Expo projects.

7. Implementing Native Views

Implementing native views in Expo allows you to integrate custom native functionality into your React Native application. This is particularly useful when you need to access platform-specific features not available through Expo's managed APIs.

Step Description Details
1. Create Native Module Set up a new native module for iOS and Android Use Expo's CLI to generate the necessary files. For iOS, create a .m file and for Android, create a .java or .kt file.
2. Register Module Register the native module with Expo In your app.json, add the native module under expo.modules. This allows Expo to recognize and load your custom module.
3. Implement View Logic Implement the logic for your native view Write the code to handle interactions and data flow between React Native and your native view. This includes setting up event listeners and managing state.
4. Test on Device Test the native view on a physical device Use Expo Go or a development build to test the native view. Ensure that it behaves as expected and handles all edge cases.

Example: Native View Implementation

// Example of a simple native view implementation in React Native
import { requireNativeComponent } from 'react-native';

const NativeView = requireNativeComponent('NativeView');

export default function App() {
  return (
    <NativeView style={{ flex: 1 }} />
  );
}
Note: Native views must be implemented in the native code of each platform (iOS and Android) to ensure compatibility.
Warning: Custom native code may not be compatible with all Expo features. Ensure that your implementation does not conflict with Expo's managed workflow.

8. Bridging Native Methods

Method Description Return Type Notes
ExpoNativeModule.methodName() Invokes a native method from JavaScript Promise<any> Must be registered in the native module
ExpoNativeModule.syncMethod() Sync version of a native method any Blocks JavaScript thread, use sparingly

Example: Bridging a Native Method

import { NativeModules } from 'react-native';<br><br>const { MyNativeModule } = NativeModules;<br><br>async function callNativeMethod() {<br>&nbsp;&nbsp;const result = await MyNativeModule.fetchData();<br>&nbsp;&nbsp;console.log(result);<br>}
Note: Ensure the native module is properly registered in the Expo config to avoid runtime errors.
Warning: Synchronous methods can block the JavaScript thread and should be avoided in performance-critical paths.

9. Using Continuous Native Generation

Continuous Native Generation (CNG) in Expo allows you to automatically regenerate native code when changes are made to your project's configuration or dependencies. This feature streamlines the development workflow by reducing manual steps required to update native code.

Feature Description Status
expo build --platform native Triggers continuous generation of native code for specified platform NEW
expo config Displays current configuration including native settings DEPRECATED

Example: Enabling Continuous Native Generation

expo build --platform native --watch
Note: CNG requires the latest version of Expo CLI and supports iOS and Android platforms.
Warning: Continuous generation may increase build times during active development.

Key Benefits of CNG

  • Automatically updates native code on configuration changes
  • Reduces manual intervention in the build process
  • Improves developer experience by minimizing rebuilds

10. Managing Native Dependencies

Dependency Type Description Installation
expo-camera Native Module Access camera functionality npx expo install expo-camera
expo-filesystem Native Module Read and write files npx expo install expo-filesystem
react-native-reanimated Native Module High-performance animations npx expo install react-native-reanimated

Example: Using expo-camera

import { Camera } from 'expo-camera';

export default function CameraExample() {
  const [cameraPermission, setCameraPermission] = useState(null);

  useEffect(() => {
    (async () => {
      const { status } = await Camera.requestCameraPermissionsAsync();
      setCameraPermission(status === 'granted');
    })();
  }, []);

  if (cameraPermission === null) return <View />;
  if (cameraPermission === false) return <Text>Access denied</Text>;

  return (
    <Camera style={{ flex: 1 }}>
      <View style={{ flex: 1, backgroundColor: 'transparent' }}>
        <Button title="Take Photo" onPress={handleCapture} />
      </View>
    </Camera>
  );
}
Note: For native dependencies, always use npx expo install to ensure compatibility with the current Expo SDK version.
Warning: Adding native dependencies may require rebuilding the app for iOS or Android.

11. Updating Native Projects

When updating native projects in an Expo project, you need to ensure compatibility between your Expo SDK version and the native dependencies. Updates can involve changes in native code, configuration files, or build processes.

Step Description Action
Check SDK Version Verify the current Expo SDK version in app.json or app.config.js. Update expo and related packages to the latest compatible version.
Update Dependencies Update native dependencies like react-native, expo-dev-client, and others. Run npx expo install --check to identify outdated packages.
Update Native Configuration Ensure android/app/build.gradle and ios/Podfile are compatible with the new SDK. Update Gradle and CocoaPods versions as needed.
Rebuild Native Projects After updating, rebuild the native projects to apply changes. Run npx expo run:android or npx expo run:ios.

Example: Updating Expo SDK

npx expo install expo@latest

Example: Rebuilding Native Projects

npx expo run:android
npx expo run:ios
Note: Always test native builds after updating to ensure no regressions in functionality or performance.

12. Creating Config Plugin Mods

Config plugins in Expo allow you to modify the native project configuration (e.g., AndroidManifest.xml, Info.plist) or inject custom code during the build process. They are a powerful way to extend Expo's capabilities without ejecting.

Property Description Values
config The Expo configuration object Object
platform The target platform (android or ios) 'android' | 'ios'
projectRoot Root directory of the project String
configPath Path to the Expo config file (e.g., app.json or app.config.js) String

Example: Basic Config Plugin

export default function configPlugin(config) {
  return {
    name: 'my-config-plugin',
    async configure(projectConfig) {
      // Modify project config here
      return projectConfig;
    }
  };
}

Example: Adding Permissions to AndroidManifest.xml

export default function androidPermissionPlugin(config) {
  return {
    name: 'android-permission-plugin',
    async configure(projectConfig) {
      if (projectConfig.android) {
        projectConfig.android.permissions = [
          ...projectConfig.android.permissions,
          'android.permission.CAMERA'
        ];
      }
      return projectConfig;
    }
  };
}
Note: Config plugins are executed during the build process and must be compatible with the Expo SDK version in use.

Was this article helpful?