RORK LABEN
TOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しましたTOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しました
記事一覧/開発ツール
開発ツール/2026-04-11上級

Rork Maxでネイティブ統合マスタリング:iOS/Android本番環境対応ガイド

Rork MaxでiOS/Androidのネイティブ機能を完全統合。デバイス機能、パフォーマンス最適化、本番環境対応まで体系的に解説します。

Rork Max230ネイティブ統合iOS108Android43高度な実装

取り組みの背景

Rork Maxは「コードなしで複雑なアプリが作れる」という謳い文句で知られていますが、本当の価値はネイティブ機能との統合にあります。HealthKit、AR Kit、CameraX、NFC などのプラットフォーム固有機能を使えば、他のノーコードツールでは実装不可能な高付加価値アプリを数日で完成させることができます。

しかし、ネイティブ機能の統合には落とし穴があります:

  • iOS/Android間でAPIの概念・呼び出し方が全く異なる
  • デバイス権限要求のタイミングを誤ると、ユーザーが機能を有効にしてくれない
  • メモリリーク対策・バージョン互換性チェックを怠ると、本番環境でクラッシュする
  • Androidのマルチスレッド実装を誤解すると、致命的なバグが隠れたままになる

このガイドでは、Rork Maxで実際に動くネイティブ統合実装を、完全なコード例+テスト方法付きで解説します。ヘルスケアアプリ・AR アプリ・決済アプリなど、本当に売上を作るアプリ開発に必要な知識をすべて習得できます。


Rork Maxのネイティブ統合アーキテクチャ

Rork Max でネイティブ機能を利用する方法は、大きく3つあります。

① Expo Modules API(推奨) Expo が提供するモジュール拡張APIで、Swift(iOS)と Kotlin(Android)を TypeScript から呼び出せます。Rork Max はこのレイヤーを通じてネイティブコードへのアクセスを公式サポートしています。React Native のネイティブモジュールよりもタイプセーフで、非同期処理のコールバック設計が整理されており、初めてネイティブ統合に取り組む開発者にも扱いやすい構造です。

② Expo SDK 標準ライブラリ(手軽) expo-cameraexpo-healthexpo-nfc など、主要なプラットフォーム機能はすでに Expo SDK にパッケージとして含まれています。ほとんどのユースケースはこれで対応可能で、追加のネイティブコードを書く必要がありません。まずここから始め、SDK にないものだけカスタムモジュールで補うのが効率的です。

③ React Native 生ブリッジ(上級者向け) SDK にも Expo Modules にも存在しないAPIを直接呼び出す場合の最終手段です。Objective-C / C++ まで踏み込む場面もあり、メンテナンスコストが高いため、本番アプリでの利用は最小限にとどめることを強く推奨します。

TypeScript ↔ ネイティブの型マッピング

ネイティブ統合で最も頻繁に失敗するのが型の不一致です。TypeScript と Swift/Kotlin では null の扱い方が根本的に異なります。

// Expo Modules での型定義例(TypeScript 側)
export interface HealthDataRecord {
  timestamp: number;   // Unix ms — Swift の TimeInterval と要変換
  heartRate: number;   // Double をそのままマップ可
  steps: number;       // Int64 は number で受けると精度落ちに注意
  unit: string;        // Swift の HKUnit.description を文字列変換
}
// Swift 側の対応
@objc(HealthKitModule)
class HealthKitModule: Module {
  func definition() -> ModuleDefinition {
    AsyncFunction("getHeartRate") { (startMs: Double, endMs: Double) -> [String: Any] in
      // Double で受けて TimeInterval に変換
      let start = Date(timeIntervalSince1970: startMs / 1000)
      let end   = Date(timeIntervalSince1970: endMs / 1000)
      // ... HealthKit query
      return ["timestamp": end.timeIntervalSince1970 * 1000,
              "heartRate": latestValue,
              "unit": "bpm"]
    }
  }
}

HealthKit統合(iOS専用)

ヘルスケアデータを扱うアプリは、App Store でも高単価カテゴリの筆頭です。HealthKit を正しく統合すれば、フィットネストラッカー・医師向けダッシュボード・女性向け体調管理アプリなど多様なマネタイズが可能になります。

権限要求のタイミングが収益を左右する

HealthKit の権限要求は「なぜ必要か」をユーザーが直感的に理解できる瞬間に行わなければなりません。アプリ起動直後に要求すると、理由が分からず拒否されます。推奨パターンは「機能を実際に使おうとした瞬間に要求する」方式です。

import * as Health from 'expo-health';
import { Alert } from 'react-native';
 
export async function requestHealthPermission(): Promise<boolean> {
  // まず現在の権限状態を確認
  const status = await Health.getPermissionStatusAsync([
    Health.HealthDataType.HEART_RATE,
    Health.HealthDataType.STEP_COUNT,
    Health.HealthDataType.SLEEP_ANALYSIS,
  ]);
 
  if (status === Health.PermissionStatus.GRANTED) return true;
 
  // 初回または拒否時:理由を説明してから要求
  return new Promise((resolve) => {
    Alert.alert(
      '健康データへのアクセス',
      '心拍数・歩数・睡眠データを取得して、あなただけの健康レポートを作成します。',
      [
        { text: 'キャンセル', onPress: () => resolve(false) },
        {
          text: '許可する',
          onPress: async () => {
            const result = await Health.requestPermissionsAsync([
              Health.HealthDataType.HEART_RATE,
              Health.HealthDataType.STEP_COUNT,
            ]);
            resolve(result.granted);
          },
        },
      ]
    );
  });
}

バックグラウンド同期とバッテリー消費

HealthKit のリアルタイム更新を受け取るには enableBackgroundDelivery を設定しますが、更新頻度をアプリのユースケースに合わせて選択することがバッテリー消費の最小化につながります。

// Swift: バックグラウンド更新の登録
let heartRateType = HKObjectType.quantityType(forIdentifier: .heartRate)!
 
healthStore.enableBackgroundDelivery(
  for: heartRateType,
  frequency: .hourly,   // .immediate は常時接続機器向け。通常は .hourly
  withCompletion: { success, error in
    if let error = error {
      print("Background delivery error: \(error.localizedDescription)")
    }
  }
)

フリークエンシーの目安は次の通りです。フィットネストラッカーのように常に最新値が必要なアプリには .immediate、デイリーレポート系アプリには .daily、それ以外の大半のユースケースは .hourly で十分です。


CameraX統合(Android)

Android での高品質カメラ機能は、CameraX ライブラリを通じて実装します。従来の Camera2 API と比べて設定コードが大幅に削減されており、Rork Max + Expo のネイティブモジュールとの相性も良好です。

MLKit との組み合わせでリアルタイムOCR

カメラ映像に対してリアルタイムでテキスト認識(OCR)を行う実装は、名刺スキャン・レシート解析・QRコード読み取りなど多くのビジネスシーンで活用できます。

// Kotlin: CameraX + MLKit OCR
@ExpoMethod
fun startTextRecognition(promise: Promise) {
  val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
 
  val imageAnalysis = ImageAnalysis.Builder()
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)  // 最新フレームのみ処理
    .build()
 
  imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(context)) { imageProxy ->
    val mediaImage = imageProxy.image ?: run {
      imageProxy.close()
      return@setAnalyzer
    }
 
    val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
 
    recognizer.process(inputImage)
      .addOnSuccessListener { visionText ->
        val result = visionText.textBlocks.joinToString("\n") { it.text }
        promise.resolve(result)
      }
      .addOnFailureListener { e ->
        promise.reject("OCR_ERROR", e.message, e)
      }
      .addOnCompleteListener {
        imageProxy.close()  // 必ずcloseしないとフリーズする
      }
  }
}

imageProxy.close() の呼び出し忘れは、カメラ映像がフリーズする最大の原因です。addOnCompleteListener 内で必ず実行することを徹底してください。

端末別カメラ性能の差を吸収する

Android は端末ごとにカメラ性能が大きく異なります。ハイエンド端末向けの設定をそのまま低スペック端末で動かすと、プレビューが遅延したり OOM クラッシュが発生します。

fun getOptimalCameraConfig(): CameraConfig {
  val availableMemoryMB = getAvailableMemoryMB()
  val cameraCapabilities = CameraInfo.getCameraCharacteristics()
 
  return when {
    availableMemoryMB > 512 && cameraCapabilities.supportsRaw ->
      CameraConfig(resolution = Size(4032, 3024), enableHDR = true)
    availableMemoryMB > 256 ->
      CameraConfig(resolution = Size(1920, 1080), enableHDR = false)
    else ->
      CameraConfig(resolution = Size(1280, 720), enableHDR = false)
  }
}

AR統合(ARKit / ARCore)

拡張現実は「写真映えするデモ動画」が作りやすく、App Store / Google Play のフィーチャー選定にも有利なカテゴリです。家具配置シミュレーターや試着アプリは特に購買転換率との相関が強く、D2Cブランドとの提携案件としても成立します。

平面検出と物体配置

AR で最も基本的な操作は「現実空間の平面(床・机)を検出して、そこに3Dオブジェクトを置く」処理です。

import { ARView, PlaneDetection } from 'expo-ar';
 
export function ARFurniturePlacement() {
  const [planes, setPlanes] = useState<ARPlane[]>([]);
  const [placedObjects, setPlacedObjects] = useState<PlacedObject[]>([]);
 
  const handlePlaneDetected = useCallback((newPlanes: ARPlane[]) => {
    setPlanes(prev => [...prev, ...newPlanes]);
  }, []);
 
  const handleTap = useCallback((event: ARTapEvent) => {
    // タップ位置の平面上に家具モデルを配置
    const hitResult = event.hitResults.find(r => r.type === 'existingPlane');
    if (!hitResult) return;
 
    setPlacedObjects(prev => [
      ...prev,
      {
        id: Date.now().toString(),
        modelUri: selectedFurnitureModel,
        transform: hitResult.worldTransform,
      },
    ]);
  }, [selectedFurnitureModel]);
 
  return (
    <ARView
      planeDetection={PlaneDetection.Horizontal}
      onPlanesDetected={handlePlaneDetected}
      onTap={handleTap}
    >
      {placedObjects.map(obj => (
        <ARModel key={obj.id} uri={obj.modelUri} transform={obj.transform} />
      ))}
    </ARView>
  );
}

iOS(ARKit)と Android(ARCore)の差異を吸収する

同じコードで両プラットフォームを動かすには、プラットフォーム差を吸収するユーティリティ層を設けるのが鉄則です。

import { Platform } from 'react-native';
 
export const ARUtils = {
  // ARKit は cm 単位、ARCore は m 単位で平面サイズを返す
  normalizeSize: (rawSize: number): number =>
    Platform.OS === 'ios' ? rawSize / 100 : rawSize,
 
  // トラッキング精度の基準値もプラットフォームで異なる
  isTrackingReliable: (quality: number): boolean =>
    Platform.OS === 'ios' ? quality > 0.7 : quality > 0.5,
 
  // ライティング推定の扱い
  getLightEstimate: (frame: ARFrame): LightEstimate => {
    if (Platform.OS === 'ios') {
      return { intensity: frame.lightEstimate?.ambientIntensity ?? 1000 };
    } else {
      return { intensity: frame.lightEstimate?.pixelIntensity ?? 1.0 * 1000 };
    }
  },
};

NFC決済・タグ読み取り

NFC は実店舗との連携に強力なユースケースをもたらします。ポイントカード・チケット・電子決済の補助機能として実装すれば、O2O(オンライン to オフライン)アプリのコア機能になり得ます。

iOS と Android の NFC 制限の違い

iOS は NFC の書き込みが非常に制限されており、読み取り専用に近い状態です。Android は読み書き両方に対応しています。アプリ設計段階でこの差を理解しておかないと、後から大幅な設計変更が必要になります。

import * as NFC from 'expo-nfc';
import { Platform, Alert } from 'react-native';
 
export async function readNFCTag(): Promise<NFCTagData | null> {
  // NFC 対応確認(iPhone 7 以前・古い Android 端末は非対応)
  const isSupported = await NFC.isNFCSupportedAsync();
  if (!isSupported) {
    Alert.alert('非対応', 'お使いの端末はNFCに対応していません。');
    return null;
  }
 
  try {
    const tag = await NFC.readTagAsync({
      alertMessage: 'NFCタグにかざしてください',  // iOS のみ表示されるダイアログ
      timeout: 10000,  // 10秒でタイムアウト
    });
 
    // NDEF フォーマットを解析
    const payload = tag.ndefRecords?.[0]?.payload;
    if (!payload) return null;
 
    // UTF-8 テキスト Record の場合、先頭3バイトはヘッダー
    const text = new TextDecoder().decode(payload.slice(3));
    return { rawId: tag.id, text };
 
  } catch (error: unknown) {
    const e = error as { code?: string };
    if (e.code === 'USER_CANCELLED') return null;  // ユーザーがキャンセル
    if (e.code === 'TIMEOUT') {
      Alert.alert('タイムアウト', 'NFCタグが見つかりませんでした。');
      return null;
    }
    throw error;
  }
}

パーミッション管理のベストプラクティス

ネイティブ機能の多くはOSレベルの権限が必要です。権限管理の設計ミスは、ユーザーレビューでの低評価に直結します。

段階的権限要求パターン

すべての権限を一度に要求するのは最悪のUXです。代わりに「機能に近づいた瞬間に、理由を添えて要求する」段階的アプローチを採用してください。

import * as Permissions from 'expo-permissions';
 
type PermissionContext = {
  feature: string;
  reason: string;
  permission: Permissions.PermissionType;
};
 
export async function requestPermissionWithContext(
  ctx: PermissionContext
): Promise<boolean> {
  const { status } = await Permissions.getAsync(ctx.permission);
 
  if (status === 'granted') return true;
 
  if (status === 'denied') {
    // 一度拒否されたら設定アプリへ誘導
    Alert.alert(
      `${ctx.feature}を有効化`,
      `${ctx.reason}\n設定アプリから「${ctx.feature}」を許可してください。`,
      [
        { text: 'キャンセル' },
        { text: '設定を開く', onPress: () => Linking.openSettings() },
      ]
    );
    return false;
  }
 
  // undetermined: 理由を説明してから要求
  const { status: newStatus } = await Permissions.askAsync(ctx.permission);
  return newStatus === 'granted';
}
 
// 使用例
await requestPermissionWithContext({
  feature: 'カメラ',
  reason: 'QRコードを読み取るためにカメラへのアクセスが必要です。',
  permission: Permissions.CAMERA,
});

メモリリーク対策と本番環境安定化

React Native + ネイティブモジュールの組み合わせで最も多い本番クラッシュ原因は、イベントリスナーの解除忘れ大容量バイナリデータの保持です。

useEffect のクリーンアップを徹底する

export function HealthMonitor() {
  const subscriptionRef = useRef<HealthSubscription | null>(null);
 
  useEffect(() => {
    let isMounted = true;
 
    async function subscribe() {
      subscriptionRef.current = await Health.watchStepCount(data => {
        if (!isMounted) return;  // アンマウント後の setState を防ぐ
        setSteps(data.steps);
      });
    }
 
    subscribe();
 
    return () => {
      isMounted = false;
      // クリーンアップ漏れが最大のメモリリーク原因
      subscriptionRef.current?.remove();
      subscriptionRef.current = null;
    };
  }, []);
}

大容量画像・動画データの扱い

カメラやARで取得した画像データをメモリ上に長時間保持すると、OOM(Out of Memory)クラッシュの原因になります。

import * as FileSystem from 'expo-file-system';
 
export async function processAndReleaseImage(uri: string): Promise<ProcessedResult> {
  try {
    // 処理を実行
    const result = await analyzeImage(uri);
    return result;
  } finally {
    // 一時ファイルを必ず削除(finally で確実に実行)
    const fileInfo = await FileSystem.getInfoAsync(uri);
    if (fileInfo.exists) {
      await FileSystem.deleteAsync(uri, { idempotent: true });
    }
  }
}

iOS と Android のメモリ警告への対応

import { AppState, Platform } from 'react-native';
 
// iOS: memoryWarning イベント
// Android: AppState 変更時に手動でキャッシュクリア
export function useMemoryPressureHandler(clearCache: () => void) {
  useEffect(() => {
    if (Platform.OS === 'ios') {
      const handler = AppState.addEventListener('memoryWarning' as any, clearCache);
      return () => handler.remove();
    } else {
      // Android はバックグラウンド移行時にキャッシュを解放
      const handler = AppState.addEventListener('change', (state) => {
        if (state === 'background') clearCache();
      });
      return () => handler.remove();
    }
  }, [clearCache]);
}

プラットフォーム別テスト戦略

ネイティブ機能のテストは、通常の Jest ユニットテストだけでは不十分です。実機テストのポイントを押さえることで、本番クラッシュを大幅に減らせます。

iOS テストチェックリスト

実機テストで必ず確認すべき項目は次の通りです。まず権限ダイアログが正しいタイミングで表示されるかを確認します。設定アプリで権限を「オフ」にしてからアプリに戻り、フォールバック動作が正しく機能するかも検証してください。次にバックグラウンド移行後の再開動作を確認します。ARや HealthKit のセッションがバックグラウンドで一時停止し、フォアグラウンド復帰時に正しく再開されるかどうかをテストします。最後に低メモリ状態のシミュレーションです。Xcode のデバッガーから「Simulate Memory Warning」を発行し、アプリがクラッシュせずにキャッシュを解放できることを確認します。

Android テストチェックリスト

Android では複数の API レベルでの動作確認が欠かせません。Android 10(API 29)以降でファイルアクセス権限の仕様が変わったため、古い実装はそのままでは動きません。また Doze モードとバッテリー最適化の影響も確認します。adb shell dumpsys deviceidle force-idle コマンドで強制的に Doze モードへ移行させ、バックグラウンド処理が期待通り動作するかを検証します。さらに端末メーカー固有のカメラ挙動も注意が必要です。Samsung・Pixel・Xiaomi でカメラのフレームレートや色補正が異なることがあります。

Jest でのモック設計

// __mocks__/expo-health.ts
const mockHealthData = {
  steps: 8432,
  heartRate: 72,
  timestamp: Date.now(),
};
 
export const getPermissionStatusAsync = jest.fn().mockResolvedValue('granted');
export const requestPermissionsAsync = jest.fn().mockResolvedValue({ granted: true });
export const getStepCountAsync = jest.fn().mockResolvedValue(mockHealthData.steps);
export const getHeartRateAsync = jest.fn().mockResolvedValue(mockHealthData.heartRate);
export const watchStepCount = jest.fn().mockReturnValue({ remove: jest.fn() });
 
// テスト例
describe('HealthMonitor', () => {
  it('権限拒否時にフォールバック表示される', async () => {
    getPermissionStatusAsync.mockResolvedValueOnce('denied');
    const { getByText } = render(<HealthMonitor />);
    await waitFor(() => {
      expect(getByText('設定からアクセスを許可してください')).toBeTruthy();
    });
  });
});

まとめ

Rork Max でネイティブ統合を極めると、以下のような高付加価値アプリが実現できます:

  • 医療/ヘルスケア: HealthKit 連携で医師向けダッシュボード(月額 $30-99)
  • AR/ビジュアル: ARKit/ARCore で家具配置シミュレーター(買い切り $9.99)
  • 決済: NFC/Apple Pay で実店舗ポイントカード(手数料 3%)

本ガイドで習得すべき3つのコアスキル:

  1. 型安全な Bridge 設計 — TypeScript と Swift/Kotlin の型対応
  2. プラットフォーム差を理解 — iOS 固有(HealthKit)と Android 固有(Google Fit)の概念差
  3. 本番環境への責任感 — メモリリーク・バッテリー効率・クラッシュログの監視

次のステップ:

シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

開発ツール2026-05-01
Rork Max で画面録画と配信を実装する — ReplayKit と MediaProjection の本番運用ガイド
Rork Max のアプリに iOS ReplayKit と Android MediaProjection を統合し、ゲームプレイ録画・チュートリアル動画作成・ライブ配信機能を実装する本番運用ガイド。
開発ツール2026-04-08
Rork Max + EAS Build カスタムネイティブモジュール導入エラー完全解決ガイド
Rork MaxアプリにカスタムネイティブモジュールをEAS Buildで導入するときのエラーを完全解決。Config Plugin作成・iOS/Androidビルドエラー・Hermes互換性・Podfileの競合解決まで実例コード付きで解説。
開発ツール2026-07-13
HealthKitの差分同期でデータを取りこぼす — HKAnchoredObjectQueryのアンカーを設計する
HealthKitの差分同期で歩数や睡眠が二重に数えられたり欠けたりする原因は、HKQueryAnchorの永続化設計にあります。newAnchorとdeletedObjectsを正しく扱い、再インストールやバックグラウンド更新をまたいでも整合するローカル取り込みの実装を、動くSwiftコードでまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →