Token导航 LogoToken导航

Agent Skill · 研究检索

native-modules(原生模块)

native-modules 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

native-modules 是研究检索类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证为 MIT,第三方扫描已通过,按安装量在研究检索类的 73,675 个 Skill 里排第 56,650。

总安装 447周安装 19GitHub Stars 127下载 157核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
MIT

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:native-modules(原生模块)
来源仓库:https://github.com/anton-abyzov/specweave
仓库路径:skills/native-modules
安装命令:
npx skills add https://github.com/anton-abyzov/specweave --skill native-modules
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/anton-abyzov/specweave --skill native-modules

这个 Skill 是做什么的

研究检索类 Skill 主要解决「找资料、核对事实、把来源整理清楚」的问题,通常会让助手先检索再总结。

用于集成 React Native 原生模块与新架构。适合 Turbo Modules、JSI 和 Fabric 等现代开发模式。使用时需参考当前 React Native 文档版本细节。涉及平台特定 API 时,应处理权限和线程安全问题。

安装命令:npx skills add https://github.com/anton-abyzov/specweave --skill native-modules

来源仓库内路径:skills/native-modules

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/anton-abyzov/specweave --skill native-modules;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

native-modules 怎么安装?

在终端执行 npx skills add https://github.com/anton-abyzov/specweave --skill native-modules。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

native-modules 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

native-modules 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

native-modules 是免费的吗?

来源标注的许可证是 MIT。本站不代收任何费用,也不托管安装包;能否商用、要不要署名,以许可证原文为准。

native-modules 用的人多吗?

公开数据是累计安装 447 次,近一周安装 19 次,下载 157 次,GitHub 127 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

native-modules 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 3 个目录同时收录,说明这个 Skill 流传比较广。
  • 许可证明确为 MIT,商用和分发边界好判断。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理研究检索类任务、又不想每次都手写提示词的人。
  • 需要许可证清楚、并且有第三方扫描结果的团队项目。

不太适合

  • 需要厂商级技术支持和 SLA 的生产系统。

研究检索类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
find-skills

寻找技能 · Codex、Claude

31,199,81316,486操作浏览器MIT有提醒
self-improving-agent

自我改进 Agent · OpenClaw

16,464,0023,396接触敏感数据MIT-0通过
soultrace

灵魂追踪 · Codex、Claude

7,588,94412需要联网未明确有提醒
self-improving

自我完善 · OpenClaw

6,829,2841,046需要联网MIT-0通过
microsoft-foundry

微软代工厂 · Codex、Claude

6,614,144764调用外部服务未明确有提醒
native-modules

原生模块 · 本页

447127需要联网MIT通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

Native Modules Expert (New Architecture)

Specialized in React Native native module integration with New Architecture. Expert in Turbo Modules, JSI, Fabric, Codegen, and modern native development patterns. Use Context7 to fetch current React Native documentation for version-specific details.

What I Know

Native Module Fundamentals

What Are Native Modules?

  • Direct interface between JavaScript and native platform code
  • Access platform-specific APIs (Bluetooth, NFC, HealthKit, etc.)
  • Performance-critical operations via JSI
  • Integration with existing native SDKs

New Architecture (Default in RN 0.76+)

  • JSI (JavaScript Interface): Direct JS ↔ Native communication (no JSON serialization)
  • Turbo Modules: Lazy-loaded, type-safe native modules with Codegen
  • Fabric: New concurrent rendering engine
  • Codegen: TypeScript → Native type generation

Key Benefits of New Architecture

  • 10-100x faster than old bridge
  • Synchronous method calls possible
  • Type safety across JS/Native boundary
  • Lazy module loading (better startup)
  • Concurrent rendering with Fabric

Using Third-Party Native Modules

Installation with Autolinking

# Install module
npm install react-native-camera

# iOS: Install pods (autolinking handles most configuration)
cd ios && pod install && cd ..

# Rebuild the app
npm run ios
npm run android

Manual Linking (Legacy)

# React Native < 0.60 (rarely needed now)
react-native link react-native-camera

Expo Integration

# For Expo managed workflow, use config plugins
npx expo install react-native-camera

# Add plugin to app.json
{
  "expo": {
    "plugins": [
      [
        "react-native-camera",
        {
          "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera"
        }
      ]
    ]
  }
}

# Rebuild dev client
eas build --profile development --platform all

Creating Custom Native Modules

iOS Native Module (Swift)

// RCTCalendarModule.swift
import Foundation

@objc(CalendarModule)
class CalendarModule: NSObject {

  @objc
  static func requiresMainQueueSetup() -> Bool {
    return false
  }

  @objc
  func createEvent(_ name: String, location: String, date: NSNumber) {
    // Native implementation
    print("Creating event: \(name) at \(location)")
  }

  @objc
  func getEvents(_ callback: @escaping RCTResponseSenderBlock) {
    let events = ["Event 1", "Event 2", "Event 3"]
    callback([NSNull(), events])
  }

  @objc
  func findEvents(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    // Async with Promise
    DispatchQueue.global().async {
      let events = self.fetchEventsFromNativeAPI()
      resolve(events)
    }
  }
}
// RCTCalendarModule.m (Bridge file)
#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(CalendarModule, NSObject)

RCT_EXTERN_METHOD(createEvent:(NSString *)name location:(NSString *)location date:(nonnull NSNumber *)date)

RCT_EXTERN_METHOD(getEvents:(RCTResponseSenderBlock)callback)

RCT_EXTERN_METHOD(findEvents:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)

@end

Android Native Module (Kotlin)

// CalendarModule.kt
package com.myapp

import com.facebook.react.bridge.*

class CalendarModule(reactContext: ReactApplicationContext) :
    ReactContextBaseJavaModule(reactContext) {

    override fun getName(): String {
        return "CalendarModule"
    }

    @ReactMethod
    fun createEvent(name: String, location: String, date: Double) {
        // Native implementation
        println("Creating event: $name at $location")
    }

    @ReactMethod
    fun getEvents(callback: Callback) {
        val events = WritableNativeArray().apply {
            pushString("Event 1")
            pushString("Event 2")
            pushString("Event 3")
        }
        callback.invoke(null, events)
    }

    @ReactMethod
    fun findEvents(promise: Promise) {
        try {
            val events = fetchEventsFromNativeAPI()
            promise.resolve(events)
        } catch (e: Exception) {
            promise.reject("ERROR", e.message, e)
        }
    }
}
// CalendarPackage.kt
package com.myapp

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

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

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

JavaScript Usage

// CalendarModule.js
import { NativeModules } from 'react-native';

const { CalendarModule } = NativeModules;

export default {
  createEvent: (name, location, date) => {
    CalendarModule.createEvent(name, location, date);
  },

  getEvents: (callback) => {
    CalendarModule.getEvents((error, events) => {
      if (error) {
        console.error(error);
      } else {
        callback(events);
      }
    });
  },

  findEvents: async () => {
    try {
      const events = await CalendarModule.findEvents();
      return events;
    } catch (error) {
      console.error(error);
      throw error;
    }
  }
};

// Usage in components
import CalendarModule from './CalendarModule';

function MyComponent() {
  const handleCreateEvent = () => {
    CalendarModule.createEvent('Meeting', 'Office', Date.now());
  };

  const handleGetEvents = async () => {
    const events = await CalendarModule.findEvents();
    console.log('Events:', events);
  };

  return (
    <View>
      <Button title="Create Event" onPress={handleCreateEvent} />
      <Button title="Get Events" onPress={handleGetEvents} />
    </View>
  );
}

Turbo Modules (New Architecture - Default in RN 0.76+)

Creating a Turbo Module with Codegen

Step 1: Create the TypeScript spec (source of truth for types):

// specs/NativeCalendarModule.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  // Sync method (fast, blocks JS thread)
  getConstants(): {
    DEFAULT_REMINDER_MINUTES: number;
  };

  // Async methods (recommended for most cases)
  createEvent(name: string, location: string, date: number): Promise<string>;
  findEvents(): Promise<string[]>;
  deleteEvent(eventId: string): Promise<boolean>;

  // Callback-based (legacy pattern, prefer Promise)
  getEventsWithCallback(callback: (events: string[]) => void): void;
}

export default TurboModuleRegistry.getEnforcing<Spec>('CalendarModule');

Step 2: Configure Codegen in package.json:

{
  "codegenConfig": {
    "name": "CalendarModuleSpec",
    "type": "modules",
    "jsSrcsDir": "specs",
    "android": {
      "javaPackageName": "com.myapp.calendar"
    }
  }
}

Step 3: Implement the native side (iOS - Swift):

// CalendarModule.swift
import Foundation

@objc(CalendarModule)
class CalendarModule: NSObject {

  @objc static func moduleName() -> String! {
    return "CalendarModule"
  }

  @objc static func requiresMainQueueSetup() -> Bool {
    return false
  }

  @objc func getConstants() -> [String: Any] {
    return ["DEFAULT_REMINDER_MINUTES": 15]
  }

  @objc func createEvent(_ name: String, location: String, date: Double,
                         resolve: @escaping RCTPromiseResolveBlock,
                         reject: @escaping RCTPromiseRejectBlock) {
    DispatchQueue.global().async {
      // Native implementation
      let eventId = UUID().uuidString
      resolve(eventId)
    }
  }

  @objc func findEvents(_ resolve: @escaping RCTPromiseResolveBlock,
                        reject: @escaping RCTPromiseRejectBlock) {
    DispatchQueue.global().async {
      let events = ["Meeting", "Lunch", "Call"]
      resolve(events)
    }
  }

  @objc func deleteEvent(_ eventId: String,
                         resolve: @escaping RCTPromiseResolveBlock,
                         reject: @escaping RCTPromiseRejectBlock) {
    resolve(true)
  }
}

Step 4: Implement the native side (Android - Kotlin):

// CalendarModule.kt
package com.myapp.calendar

import com.facebook.react.bridge.*
import com.facebook.react.module.annotations.ReactModule

@ReactModule(name = CalendarModule.NAME)
class CalendarModule(reactContext: ReactApplicationContext) :
    NativeCalendarModuleSpec(reactContext) {

    companion object {
        const val NAME = "CalendarModule"
    }

    override fun getName(): String = NAME

    override fun getConstants(): MutableMap<String, Any> {
        return mutableMapOf("DEFAULT_REMINDER_MINUTES" to 15)
    }

    override fun createEvent(name: String, location: String, date: Double, promise: Promise) {
        val eventId = java.util.UUID.randomUUID().toString()
        promise.resolve(eventId)
    }

    override fun findEvents(promise: Promise) {
        val events = Arguments.createArray().apply {
            pushString("Meeting")
            pushString("Lunch")
            pushString("Call")
        }
        promise.resolve(events)
    }

    override fun deleteEvent(eventId: String, promise: Promise) {
        promise.resolve(true)
    }
}

Benefits of Turbo Modules

  • Lazy loading: Only loaded when first accessed
  • Type safety: Codegen generates native interfaces from TypeScript
  • 10x faster: Direct JSI calls, no JSON serialization
  • Synchronous calls: getConstants() can be sync
  • Better DX: TypeScript errors caught at build time

Native UI Components

Custom Native View (iOS - Swift)

// RCTCustomViewManager.swift
import UIKit

@objc(CustomViewManager)
class CustomViewManager: RCTViewManager {

  override static func requiresMainQueueSetup() -> Bool {
    return true
  }

  override func view() -> UIView! {
    return CustomView()
  }

  @objc func setColor(_ view: CustomView, color: NSNumber) {
    view.backgroundColor = RCTConvert.uiColor(color)
  }
}

class CustomView: UIView {
  override init(frame: CGRect) {
    super.init(frame: frame)
    self.backgroundColor = .blue
  }

  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
}

Custom Native View (Android - Kotlin)

// CustomViewManager.kt
class CustomViewManager : SimpleViewManager<View>() {

    override fun getName(): String {
        return "CustomView"
    }

    override fun createViewInstance(reactContext: ThemedReactContext): View {
        return View(reactContext).apply {
            setBackgroundColor(Color.BLUE)
        }
    }

    @ReactProp(name = "color")
    fun setColor(view: View, color: Int) {
        view.setBackgroundColor(color)
    }
}

JavaScript Usage

import { requireNativeComponent } from 'react-native';

const CustomView = requireNativeComponent('CustomView');

function MyComponent() {
  return (
    <CustomView
      style={{ width: 200, height: 200 }}
      color="red"
    />
  );
}

Common Native Module Issues

Module Not Found

# iOS: Clear build and reinstall pods
cd ios && rm -rf build Pods && pod install && cd ..
npm run ios

# Android: Clean and rebuild
cd android && ./gradlew clean && cd ..
npm run android

# Clear Metro cache
npx react-native start --reset-cache

Autolinking Not Working

# Verify module in package.json
npm list react-native-camera

# Re-run pod install
cd ios && pod install && cd ..

# Check react-native.config.js for custom linking config

Native Crashes

# iOS: Check Xcode console for crash logs
# Look for:
# - Unrecognized selector sent to instance
# - Null pointer exceptions
# - Memory issues

# Android: Check logcat
adb logcat *:E
# Look for:
# - Java exceptions
# - JNI errors
# - Null pointer exceptions

When to Use This Skill

Ask me when you need help with:

  • Integrating third-party native modules
  • Creating custom native modules
  • Troubleshooting native module installation
  • Writing iOS native code (Swift/Objective-C)
  • Writing Android native code (Kotlin/Java)
  • Debugging native crashes
  • Understanding Turbo Modules and JSI
  • Migrating to New Architecture
  • Creating custom native UI components
  • Handling platform-specific APIs
  • Resolving autolinking issues
  • Setting up Codegen for type-safe modules
  • Creating Fabric components (New Architecture UI)
  • JSI bindings for synchronous native calls
  • Expo config plugins for native configuration
  • Interop layer for legacy Bridge modules
  • Native listener lifecycle (singleton patterns for IAP, push, deep links)
  • Debugging silent failures in async native events

Essential Commands

Module Development

# Create module template
npx create-react-native-module my-module

# Build iOS module
cd ios && xcodebuild

# Build Android module
cd android && ./gradlew assembleRelease

# Test module locally
npm link
cd ../MyApp && npm link my-module

Debugging Native Code

# iOS: Run with Xcode debugger
open ios/MyApp.xcworkspace

# Android: Run with Android Studio debugger
# Open android/ folder in Android Studio

# Print native logs
# iOS
tail -f ~/Library/Logs/DiagnosticReports/*.crash

# Android
adb logcat | grep "CalendarModule"

Pro Tips & Tricks

1. Type-Safe Native Modules with Codegen

Use Codegen (New Architecture) for type safety:

// NativeMyModule.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  getString(key: string): Promise<string>;
  setString(key: string, value: string): void;
}

export default TurboModuleRegistry.getEnforcing<Spec>('MyModule');

2. Event Emitters for Native → JS Communication

// iOS - Emit events to JavaScript
import Foundation

@objc(DeviceOrientationModule)
class DeviceOrientationModule: RCTEventEmitter {

  override func supportedEvents() -> [String]! {
    return ["OrientationChanged"]
  }

  @objc
  override static func requiresMainQueueSetup() -> Bool {
    return true
  }

  @objc
  func startObserving() {
    NotificationCenter.default.addObserver(
      self,
      selector: #selector(orientationChanged),
      name: UIDevice.orientationDidChangeNotification,
      object: nil
    )
  }

  @objc
  func stopObserving() {
    NotificationCenter.default.removeObserver(self)
  }

  @objc
  func orientationChanged() {
    let orientation = UIDevice.current.orientation
    sendEvent(withName: "OrientationChanged", body: ["orientation": orientation.rawValue])
  }
}
// JavaScript - Listen to native events
import { NativeEventEmitter, NativeModules } from 'react-native';

const { DeviceOrientationModule } = NativeModules;
const eventEmitter = new NativeEventEmitter(DeviceOrientationModule);

function MyComponent() {
  useEffect(() => {
    const subscription = eventEmitter.addListener('OrientationChanged', (data) => {
      console.log('Orientation:', data.orientation);
    });

    return () => subscription.remove();
  }, []);

  return <View />;
}

3. Native Module with Callbacks

// Android - Pass callbacks
@ReactMethod
fun processData(data: String, successCallback: Callback, errorCallback: Callback) {
    try {
        val result = heavyProcessing(data)
        successCallback.invoke(result)
    } catch (e: Exception) {
        errorCallback.invoke(e.message)
    }
}
// JavaScript
CalendarModule.processData(
  'input data',
  (result) => console.log('Success:', result),
  (error) => console.error('Error:', error)
);

4. Synchronous Native Methods (Use Sparingly)

// iOS - Synchronous method (blocks JS thread!)
@objc
func getDeviceId() -> String {
    return UIDevice.current.identifierForVendor?.uuidString ?? "unknown"
}
// JavaScript - Synchronous call
const deviceId = CalendarModule.getDeviceId();
console.log(deviceId);  // Returns immediately

Warning: Synchronous methods block the JS thread. Use only for very fast operations (<5ms).

5. Expo Config Plugins

For Expo projects, use config plugins to modify native code:

// plugins/withCalendarPermission.ts
import { ConfigPlugin, withInfoPlist, withAndroidManifest } from '@expo/config-plugins';

const withCalendarPermission: ConfigPlugin = (config) => {
  // iOS: Modify Info.plist
  config = withInfoPlist(config, (config) => {
    config.modResults.NSCalendarsUsageDescription =
      'This app needs calendar access to schedule events';
    return config;
  });

  // Android: Modify AndroidManifest.xml
  config = withAndroidManifest(config, (config) => {
    const mainApplication = config.modResults.manifest.application?.[0];
    if (mainApplication) {
      // Add permissions
      config.modResults.manifest['uses-permission'] = [
        ...(config.modResults.manifest['uses-permission'] || []),
        { $: { 'android:name': 'android.permission.READ_CALENDAR' } },
        { $: { 'android:name': 'android.permission.WRITE_CALENDAR' } },
      ];
    }
    return config;
  });

  return config;
};

export default withCalendarPermission;
// app.json
{
  "expo": {
    "plugins": [
      "./plugins/withCalendarPermission"
    ]
  }
}

6. Interop Layer for Legacy Bridge Modules

RN 0.76+ includes an interop layer for Bridge modules in New Architecture:

// For legacy modules that don't support Turbo Modules yet
import { NativeModules, TurboModuleRegistry } from 'react-native';

// This works in New Architecture via interop layer
const LegacyModule = NativeModules.LegacyBridgeModule;

// Or use the Turbo Module if available
const TurboModule = TurboModuleRegistry.get('ModernModule');

// Recommended: Create a wrapper that handles both
export function getCalendarModule() {
  // Try Turbo Module first
  const turbo = TurboModuleRegistry.get('CalendarModule');
  if (turbo) return turbo;

  // Fall back to Bridge module via interop
  return NativeModules.CalendarModule;
}

7. Native Singleton Listeners (App-Lifetime)

CRITICAL: Some native listeners must persist for the app's entire lifetime. Never clean them up in useEffect cleanup.

Why This Matters

Native platform events often arrive asynchronously from system UI (payment sheets, biometric dialogs, push notifications). The component that initiated the flow may unmount while system UI is visible, but events arrive AFTER the system UI dismisses. If the listener is cleaned up, events are silently lost.

The Rule

Listener TypeCleanup on Unmount?Reason
iOS StoreKit (setPurchaseListener)NEVEREvents arrive after payment sheet dismisses
Google Play Billing listenerNEVERSame async flow as iOS
Push notification handlersNEVERCan arrive at any time
Deep link listenersNEVERApp may be backgrounded during navigation
Biometric auth callbacksNEVERSystem UI dialog is external
JavaScript event subscriptions✅ OKReact lifecycle managed, no native async

Implementation Pattern - Singleton Service

// services/IAPService.ts - Singleton, initialized once
class IAPService {
  private static initialized = false;

  static async initialize() {
    if (this.initialized) return;
    this.initialized = true;

    // Native listener - NEVER disconnected
    await InAppPurchases.connectAsync();
    InAppPurchases.setPurchaseListener(({ responseCode, results }) => {
      if (responseCode === IAPResponseCode.OK && results) {
        results.forEach(purchase => this.processPurchase(purchase));
      }
    });
  }

  private static async processPurchase(purchase: InAppPurchase) {
    // Validate receipt, unlock content, finish transaction
    await finishTransactionAsync(purchase, true);
  }

  // DO NOT add a disconnect method!
  // The native listener must persist for app lifetime
}
// App.tsx - Initialize once at app root
export default function App() {
  useEffect(() => {
    IAPService.initialize();
    // NO cleanup return - this is intentional!
  }, []);

  return <RootNavigator />;
}
// screens/PurchaseScreen.tsx - Component can have JS subscriptions
function PurchaseScreen() {
  useEffect(() => {
    // JS-level subscription - OK to cleanup
    const subscription = purchaseEvents.subscribe(handleLocalUI);
    return () => subscription.unsubscribe(); // ✅ OK - JS level only
  }, []);

  const handleBuy = async () => {
    // Initiates native purchase flow
    // Component may unmount while payment sheet is visible
    // But IAPService singleton still receives the event
    await InAppPurchases.purchaseItemAsync('premium_upgrade');
  };
}

Common Mistake - DO NOT DO THIS

// ❌ WRONG - Listener cleaned up, purchase events lost!
function BrokenPurchaseScreen() {
  useEffect(() => {
    InAppPurchases.connectAsync();
    InAppPurchases.setPurchaseListener(handlePurchase);

    return () => {
      InAppPurchases.disconnectAsync(); // 💥 BUG!
      // User completes purchase in payment sheet
      // Component unmounts during navigation
      // Event arrives but listener is gone
      // Purchase succeeds but app never knows!
    };
  }, []);
}

Debugging Silent Failures

If purchases succeed (user charged) but app doesn't respond:

  1. Check if setPurchaseListener is in a component that might unmount
  2. Verify listener is set at app root, not in purchase screen
  3. Add logging in the singleton to confirm events arrive
  4. Test scenario: Start purchase → navigate away → complete purchase

Applies To

  • expo-in-app-purchases / react-native-iap
  • expo-notifications / react-native-push-notification
  • expo-linking / Deep link handlers
  • expo-local-authentication / Biometric callbacks
  • Any native module with async system UI

Integration with SpecWeave

Native Module Planning

  • Document native dependencies in spec.md
  • Include native module setup in plan.md
  • Add native code compilation to tasks.md

Testing Strategy

  • Unit test native code separately
  • Integration test JS ↔ Native bridge
  • Test on both iOS and Android
  • Document platform-specific behaviors

Documentation

  • Maintain native module API documentation
  • Document platform-specific quirks
  • Keep runbooks for common native issues

平台分布

Claude Code

27.12%
按下载量换算43

Antigravity

24.03%
按下载量换算38

Cursor

20.98%
按下载量换算33

OpenCode

13.84%
按下载量换算22

Gemini CLI

7.4%
按下载量换算12

Codex

3.33%
按下载量换算5

安全审计

Gen Agent Trust Hub

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

继续浏览同类 Skills