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;
}
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
platformparameter 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();
}
Key Points
- Use
expo-modules-coreto 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. |
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>
expo-dev-client or a custom development build to ensure that changes are properly applied and tested.
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!")
}
}
}
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": {}
}
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 }} />
);
}
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> const result = await MyNativeModule.fetchData();<br> console.log(result);<br>}
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 |
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>
);
}
npx expo install to ensure compatibility with the current Expo SDK version.
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. |
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;
}
};
}