npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

expo-roomplan

v1.2.1

Published

React Native implementation of Apple's RoomPlan SDK

Readme

expo-roomplan

React Native implementation of Apple's RoomPlan SDK.

Read more about it here: Apple - RoomPlan

Usage

useRoomPlan

The simplest way to use this package is with the useRoomPlan hook:

const { startRoomPlan } = useRoomPlan();

async function startScan() {
  try {
    await startRoomPlan("My Scan");
  } catch (error) {
    console.error("Error: ", error);
  }
}

startRoomPlan is an async function that starts a new instance of a UIViewController with the RoomCaptureViewDelegate and RoomCaptureSessionDelegate protocols from RoomPlan. It provides instructions throughout the scan. Once it's finished, you can either add another room to the structure or export what you have. You are then redirected back to the previous screen you were on.

It accepts one parameter, a string for the name of the exported USDZ and JSON from RoomPlan. You can also add other options by providing a config object to the hook:

const { startRoomPlan } = useRoomPlan({ exportType: ExportType.Mesh, sendFileLoc: true });

By default, it exports to parametric generates an ActivityViewController to share via AirDrop or email or other methods.

There are three export types:

  • parametric
  • mesh
  • model

You can read more about them here: Apple - RoomPlan USD Export Options

sendFileLoc is an option that lets you opt out of the ActivityViewController flow and receive the URLs of the exported files manually.

There's also a const called roomScanStatus that the hook returns:

const { roomScanStatus } = useRoomPlan();

It watches the internal status of the scan. It has 4 different states:

  • Not Started
  • Canceled
  • Error
  • OK

RoomPlanView

This tutorial shows how to embed the native RoomPlan scanning UI directly with RoomPlanView and control it imperatively via props.

  1. Render a full-screen overlay with basic controls.
import React, { useState } from "react";
import { SafeAreaView, View, Text, Pressable, StyleSheet } from "react-native";
import { RoomPlanView, ExportType } from "expo-roomplan";

export default function SimpleRoomPlanScreen() {
  const [show, setShow] = useState(false);
  const [running, setRunning] = useState(false);
  const [finishTrigger, setFinishTrigger] = useState<number | undefined>();
  const [addAnotherTrigger, setAddAnotherTrigger] = useState<
    number | undefined
  >();

  return (
    <SafeAreaView style={{ flex: 1 }}>
      <Pressable
        onPress={() => {
          setShow(true);
          setRunning(true);
        }}
      >
        <Text>Start Scan</Text>
      </Pressable>

      {show && (
        <View style={StyleSheet.absoluteFill}>
          <RoomPlanView
            style={StyleSheet.absoluteFill}
            scanName="MyRoom"
            exportType={ExportType.Parametric}
            sendFileLoc
            running={running}
            finishTrigger={finishTrigger}
            addAnotherTrigger={addAnotherTrigger}
            onStatus={(e) => console.log("status", e.nativeEvent)}
            onPreview={() => console.log("preview")}
            onExported={(e) => console.log("exported", e.nativeEvent)}
          />

          <SafeAreaView
            style={{
              position: "absolute",
              top: 16,
              left: 16,
              right: 16,
              flexDirection: "row",
              justifyContent: "space-between",
            }}
          >
            <Pressable
              onPress={() => {
                setRunning(false);
                setShow(false);
              }}
            >
              <Text>Cancel</Text>
            </Pressable>
            <Pressable onPress={() => setFinishTrigger(Date.now())}>
              <Text>Finish</Text>
            </Pressable>
            <Pressable onPress={() => setAddAnotherTrigger(Date.now())}>
              <Text>Add Room</Text>
            </Pressable>
          </SafeAreaView>
        </View>
      )}
    </SafeAreaView>
  );
}
  1. Finish stops capture, shows Apple’s preview, then exports automatically if exportOnFinish is true (default). Add Room stops and restarts to accumulate more rooms.

useRoomPlanView

Prefer this hook to avoid manually juggling triggers. It returns the props to spread onto RoomPlanView, controller methods, and state.

import React, { useEffect, useState } from "react";
import { SafeAreaView, View, Text, Pressable, StyleSheet } from "react-native";
import { RoomPlanView, useRoomPlanView, ExportType } from "expo-roomplan";

export default function HookDemo() {
  const [overlay, setOverlay] = useState(false);
  const { viewProps, controls, state } = useRoomPlanView({
    scanName: "HookRoom",
    exportType: ExportType.Parametric,
    exportOnFinish: true,
    sendFileLoc: true,
    autoCloseOnTerminalStatus: true,
    onStatus: (e) => console.log("status", e.nativeEvent),
    onPreview: () => console.log("preview"),
    onExported: (e) => console.log("exported", e.nativeEvent),
  });

  useEffect(() => {
    if (overlay && !state.isRunning) setOverlay(false);
  }, [overlay, state.isRunning]);

  return (
    <SafeAreaView style={{ flex: 1 }}>
      <Pressable
        onPress={() => {
          setOverlay(true);
          controls.start();
        }}
      >
        <Text>Open Scanner</Text>
      </Pressable>
      {overlay && (
        <View style={StyleSheet.absoluteFill}>
          <RoomPlanView style={StyleSheet.absoluteFill} {...viewProps} />
          <SafeAreaView
            style={{
              position: "absolute",
              top: 16,
              left: 16,
              right: 16,
              flexDirection: "row",
              justifyContent: "space-between",
            }}
          >
            <Pressable
              onPress={() => {
                controls.cancel();
                setOverlay(false);
              }}
            >
              <Text>Cancel</Text>
            </Pressable>
            <Pressable onPress={controls.finishScan}>
              <Text>Finish</Text>
            </Pressable>
            <Pressable onPress={controls.addRoom}>
              <Text>Add Room</Text>
            </Pressable>
          </SafeAreaView>
        </View>
      )}
    </SafeAreaView>
  );
}

RoomPlanProvider

Use the provider to control RoomPlanView anywhere in a subtree via context. Render RoomPlanViewConsumer or call useRoomPlanContext for controls/state.

import React, { useState, useEffect } from "react";
import { SafeAreaView, View, Text, Pressable, StyleSheet } from "react-native";
import {
  RoomPlanProvider,
  RoomPlanViewConsumer,
  useRoomPlanContext,
  ExportType,
} from "expo-roomplan";

function TopBar({ onClose }: { onClose: () => void }) {
  const { controls } = useRoomPlanContext();
  return (
    <SafeAreaView
      style={{
        position: "absolute",
        top: 16,
        left: 16,
        right: 16,
        flexDirection: "row",
        justifyContent: "space-between",
      }}
    >
      <Pressable
        onPress={() => {
          controls.cancel();
          onClose();
        }}
      >
        <Text>Cancel</Text>
      </Pressable>
      <Pressable onPress={controls.finishScan}>
        <Text>Finish</Text>
      </Pressable>
      <Pressable onPress={controls.addRoom}>
        <Text>Add Room</Text>
      </Pressable>
    </SafeAreaView>
  );
}

export default function ContextDemo() {
  const [overlay, setOverlay] = useState(false);
  return (
    <RoomPlanProvider
      scanName="ContextRoom"
      exportType={ExportType.Parametric}
      exportOnFinish
      sendFileLoc
      autoCloseOnTerminalStatus
    >
      <Inner overlay={overlay} setOverlay={setOverlay} />
    </RoomPlanProvider>
  );
}

function Inner({
  overlay,
  setOverlay,
}: {
  overlay: boolean;
  setOverlay: (v: boolean) => void;
}) {
  const { controls, state } = useRoomPlanContext();
  useEffect(() => {
    if (overlay && !state.isRunning) setOverlay(false);
  }, [overlay, state.isRunning]);
  return (
    <SafeAreaView style={{ flex: 1 }}>
      <Pressable
        onPress={() => {
          setOverlay(true);
          controls.start();
        }}
      >
        <Text>Open with Context</Text>
      </Pressable>
      {overlay && (
        <View style={StyleSheet.absoluteFill}>
          <RoomPlanViewConsumer style={StyleSheet.absoluteFill as any} />
          <TopBar onClose={() => setOverlay(false)} />
        </View>
      )}
    </SafeAreaView>
  );
}

RoomPlanView (Reference)

Props

| Prop | Type | Default | Description | | ----------------- | --------------------------------------------------- | ---------- | --------------------------------------------------------------------------- | | scanName | string | "Room" | Base filename for exported .usdz and .json. | | exportType | ExportType | Parametric | Export mode: Parametric, Mesh, or Model. | | sendFileLoc | boolean | false | When true, onExported includes file URLs rather than showing a share sheet. | | running | boolean | false | Starts/stops scanning. Toggle true to begin; false to stop. | | exportTrigger | number | — | Bump to trigger export. Queued if no room captured yet. | | finishTrigger | number | — | Bump to stop capture and present Apple’s preview UI. | | addAnotherTrigger | number | — | Bump to finish current room and immediately start another. | | exportOnFinish | boolean | true | If true, finishing also exports after preview. | | style | ViewStyle | — | Standard React Native style prop. | | onStatus | ({ nativeEvent: { status, errorMessage? }}) => void | — | Receives status updates: OK, Error, Canceled, etc. | | onPreview | () => void | — | Called when preview UI is presented. | | onExported | ({ nativeEvent: { scanUrl?, jsonUrl? }}) => void | — | Emitted after export; URLs when sendFileLoc is true. |

Notes

  • Finish and Add Room are edge-triggered by changing the trigger numbers (e.g. Date.now()).
  • Export is queued until the first processed room exists; no premature "No rooms captured" errors.

useRoomPlanView (Reference)

Options

| Option | Type | Default | Description | | ------------------------- | ---------- | ---------- | ----------------------------------------------------- | | scanName | string | — | Base filename for export. | | exportType | ExportType | Parametric | Export mode. | | exportOnFinish | boolean | true | Auto-export after finish. | | sendFileLoc | boolean | true | Include file URLs in onExported. | | autoCloseOnTerminalStatus | boolean | false | Automatically set running=false on OK/Error/Canceled. | | onStatus | function | — | Intercepts status events. | | onPreview | function | — | Intercepts preview event. | | onExported | function | — | Intercepts exported event. |

Return shape

| Key | Type | Description | | --------- | ----------------- | --------------------------------------------------------------- | | viewProps | RoomPlanViewProps | Spread onto RoomPlanView. | | controls | object | { start, cancel, finishScan, addRoom, exportScan, reset }. | | state | object | { isRunning, status, isPreviewVisible, lastExport, lastError }. |

RoomPlanProvider (Reference)

Props: identical to useRoomPlanView options. Provides a context with the same return shape as the hook.

Exports

  • RoomPlanProvider: wraps a subtree and initialises the controller and state.
  • useRoomPlanContext(): returns { viewProps, controls, state } from context.
  • RoomPlanViewConsumer: convenience component to render RoomPlanView using viewProps from context.

Installation in managed Expo projects

For a step-by-step guide on using this library in a managed Expo app (including privacy manifests and the config plugin), see the tutorial here.

Installation in bare React Native projects

For bare React Native projects, you must ensure that you have installed and configured the expo package before continuing.

Add the package to your npm dependencies

npm install expo-roomplan

Add this to your expo-module.config.json:

{
  "platforms": ["android", "ios"],
  "android": {
    "moduleName": "ExpoRoomPlan"
  }
}

Configure for Android

Only compatible with iOS 17.0 or higher.

Configure for iOS

Only compatible with iOS 17.0 or higher.

Run npx pod-install after installing the npm package.

Contributing

Contributions are very welcome! Please refer to guidelines described in the contributing guide.

Development Notes

Replace YOUR_TEAM_ID with a real development team in the example project.