From harness-claude
Guides bridging native platform APIs (Bluetooth, NFC, HealthKit) into React Native using Expo Modules API for iOS (Swift) and Android (Kotlin), plus Turbo Modules. For custom native SDKs or performance code.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Bridge native platform APIs into React Native with Expo Modules API and Turbo Modules
Guides building and integrating native modules in React Native, covering Turbo Modules, Swift/iOS and Kotlin/Android bridging, and platform APIs.
Guides creation of React Native native modules using Expo Modules API, Turbo Modules, or Fabric Components with TypeScript APIs, Swift iOS, and Kotlin Android implementations.
Guides building Expo native modules and views with Expo Modules API using Swift, Kotlin, TypeScript. Covers module DSL, native views, lifecycle hooks, config plugins, autolinking for Expo apps.
Share bugs, ideas, or general feedback.
Bridge native platform APIs into React Native with Expo Modules API and Turbo Modules
Prefer existing Expo SDK modules and community packages first. Most common native functionality (camera, location, notifications, file system, biometrics) already has a well-maintained package. Only write custom native modules for genuinely missing functionality.
Use Expo Modules API for new native modules in Expo projects. It provides a unified Swift/Kotlin API that is simpler than React Native's Turbo Modules.
npx create-expo-module my-native-module
This scaffolds a module with iOS (Swift) and Android (Kotlin) source files.
// ios/MyNativeModule.swift
import ExpoModulesCore
public class MyNativeModule: Module {
public func definition() -> ModuleDefinition {
Name("MyNativeModule")
// Synchronous function
Function("getDeviceId") { () -> String in
return UIDevice.current.identifierForVendor?.uuidString ?? ""
}
// Async function
AsyncFunction("fetchHealthData") { (type: String, promise: Promise) in
HealthKitManager.fetch(type: type) { result in
switch result {
case .success(let data):
promise.resolve(data)
case .failure(let error):
promise.reject(error)
}
}
}
// Events
Events("onStatusChange")
// View component
View(MyNativeView.self) {
Prop("color") { (view, color: UIColor) in
view.backgroundColor = color
}
}
}
}
// android/src/main/java/expo/modules/mynativemodule/MyNativeModule.kt
package expo.modules.mynativemodule
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
class MyNativeModule : Module() {
override fun definition() = ModuleDefinition {
Name("MyNativeModule")
Function("getDeviceId") {
android.provider.Settings.Secure.getString(
appContext.reactContext?.contentResolver,
android.provider.Settings.Secure.ANDROID_ID
)
}
AsyncFunction("fetchHealthData") { type: String ->
// Android Health Connect implementation
}
Events("onStatusChange")
}
}
// src/MyNativeModule.ts
import { requireNativeModule } from 'expo-modules-core';
const MyNativeModule = requireNativeModule('MyNativeModule');
export function getDeviceId(): string {
return MyNativeModule.getDeviceId();
}
export async function fetchHealthData(type: string): Promise<HealthData> {
return MyNativeModule.fetchHealthData(type);
}
// Listen to events
import { EventEmitter } from 'expo-modules-core';
const emitter = new EventEmitter(MyNativeModule);
export function onStatusChange(callback: (status: string) => void) {
return emitter.addListener('onStatusChange', callback);
}
.ios.ts and .android.ts extensions.src/
biometrics.ts # Shared interface
biometrics.ios.ts # iOS implementation (Face ID)
biometrics.android.ts # Android implementation (Fingerprint)
// plugin/withMyModule.ts
import { ConfigPlugin, withInfoPlist, withAndroidManifest } from 'expo/config-plugins';
const withMyModule: ConfigPlugin = (config) => {
config = withInfoPlist(config, (config) => {
config.modResults.NSHealthShareUsageDescription = 'Access health data';
return config;
});
config = withAndroidManifest(config, (config) => {
// Add Android permissions
return config;
});
return config;
};
export default withMyModule;
Expo Modules API vs. Turbo Modules: Expo Modules API provides a higher-level DSL in Swift/Kotlin with automatic type conversion, event support, and view definitions. Turbo Modules (React Native's new architecture) use C++ and codegen for type-safe bridging. Use Expo Modules for Expo projects; use Turbo Modules for bare RN projects or when you need C++ performance.
Threading: By default, native module functions run on the main thread (iOS) or the native modules thread (Android). For heavy computation, dispatch to a background queue/thread and use promises to return results.
Data types that cross the bridge: Strings, numbers, booleans, arrays, and dictionaries (objects) are automatically converted. For complex types, serialize to JSON. Binary data should use base64 encoding or file URIs.
Common mistakes:
https://docs.expo.dev/modules/overview/