React Native setup

Set up a project for building a Viam mobile app using React Native specifically. If you do not already have a React Native codebase, prefer Flutter for new cross-platform apps; React Native is here for teams whose existing apps are React Native. This page covers the project scaffolding, the Viam SDK install, six runtime polyfill packages, a custom XHR-based gRPC transport, a Metro bundler configuration fix for a dependency version conflict, and platform permissions on Android and iOS. For the connection patterns your app will actually use, see Connect to a machine.

Prerequisites

  • The React Native CLI environment for your development platform (React Native environment setup)
  • Xcode for iOS targets, Android Studio for Android targets
  • A configured Viam machine
  • The machine’s address, an API key, and an API key ID

Get the credentials from the machine’s CONNECT tab in the Viam app. Click CONNECT, select TypeScript, toggle Include API key on, and copy the host, authEntity, and payload values.

Create a project

npx @react-native-community/cli@latest init MyViamApp
cd MyViamApp

Install the SDK and dependencies

The Viam SDK needs six runtime packages in addition to itself:

npm install @viamrobotics/sdk \
  fast-text-encoding \
  react-native-fast-encoder \
  react-native-fetch-api \
  react-native-url-polyfill \
  react-native-webrtc \
  web-streams-polyfill

After the install completes, run pod install for iOS:

cd ios && pod install && cd ..

Add polyfill files

The SDK expects browser-style globals (TextEncoder, fetch, Headers, Request, Response, ReadableStream, WebRTC APIs) that React Native does not provide natively. The polyfill files from the SDK example repo register these globals before the SDK initializes.

Create polyfills.native.ts in your project root with the contents from the SDK example’s polyfills.native.ts. The file is stable and you can copy it as-is.

Also create polyfills.ts (no-op fallback for web builds) with the contents from the SDK example’s polyfills.ts.

Add the custom gRPC transport

React Native’s fetch does not support the streaming and binary-body semantics that Connect’s default gRPC-web transport requires. The SDK example provides an XHR-based transport that works around this. Copy it from the SDK example:

Create transport.ts in your project root with the contents from the SDK example’s transport.ts. Do not modify it. The transport implementation is verbose (roughly 350 lines) and stable; treating it as a copy-and-forget dependency is the intended approach.

Update metro.config.js

react-native and react-native-webrtc depend on conflicting versions of event-target-shim. Metro’s default resolver picks the wrong one, which causes runtime errors. Tell Metro to resolve event-target-shim inside react-native-webrtc using its own nested version:

const { getDefaultConfig } = require("@react-native/metro-config");
const resolveFrom = require("resolve-from");

const config = getDefaultConfig(__dirname);

config.resolver.resolveRequest = (context, moduleName, platform) => {
  if (
    moduleName.startsWith("event-target-shim") &&
    context.originModulePath.includes("react-native-webrtc")
  ) {
    return {
      filePath: resolveFrom(context.originModulePath, moduleName),
      type: "sourceFile",
    };
  }
  return context.resolveRequest(context, moduleName, platform);
};

module.exports = config;

Save this as metro.config.js in your project root, replacing whatever react-native init generated.

Add Android permission

React Native apps targeting Android need the ACCESS_NETWORK_STATE permission for WebRTC. Add it to android/app/src/main/AndroidManifest.xml inside the top-level <manifest> element:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

Register polyfills and transport in App.tsx

Replace the contents of App.tsx with:

import * as VIAM from "@viamrobotics/sdk";
import { polyfills } from "./polyfills";
polyfills();

import { GrpcWebTransportOptions } from "@connectrpc/connect-web";
import { createXHRGrpcWebTransport } from "./transport";

// @ts-expect-error -- globalThis.VIAM is not in standard types
globalThis.VIAM = {
  GRPC_TRANSPORT_FACTORY: (opts: GrpcWebTransportOptions) => {
    return createXHRGrpcWebTransport(opts);
  },
};

import React, { useEffect, useState } from "react";
import { SafeAreaView, Text, StyleSheet } from "react-native";

// Replace these with the values from your machine's CONNECT tab.
// For a production app, use react-native-config or a secure store
// instead of hardcoding credentials in source.
const HOST = "my-robot-main.xxxx.viam.cloud";
const API_KEY_ID = "your-api-key-id";
const API_KEY = "your-api-key-secret";

function App(): React.JSX.Element {
  const [status, setStatus] = useState("Connecting...");

  useEffect(() => {
    async function connect() {
      try {
        const machine = await VIAM.createRobotClient({
          host: HOST,
          credentials: {
            type: "api-key",
            authEntity: API_KEY_ID,
            payload: API_KEY,
          },
          signalingAddress: "https://app.viam.com:443",
        });
        const resources = await machine.resourceNames();
        setStatus(`Connected. Found ${resources.length} resources.`);
      } catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        setStatus(`Connection failed: ${msg}`);
      }
    }
    connect();
  }, []);

  return (
    <SafeAreaView style={styles.container}>
      <Text style={styles.text}>{status}</Text>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: "center", alignItems: "center" },
  text: { fontSize: 18 },
});

export default App;

The order of imports in this file matters. The polyfills and the transport factory assignment must run before any SDK call. If you move the polyfills() call or the globalThis.VIAM assignment below the React imports, or import any SDK submodule before them, the SDK will fail to initialize.

Run the app

npm run ios
# or
npm run android

The app builds, launches on the selected simulator or device, and shows:

Connected. Found N resources.

where N is the number of components and services on your machine.

If you see Connection failed:, the most common causes are:

  • Polyfills imported out of order. The polyfills() call and the globalThis.VIAM assignment must appear before any other code that touches the SDK, including React imports if you reference SDK types in them.
  • metro.config.js not applied. Metro caches aggressively. Stop the Metro bundler and run npm start -- --reset-cache after editing metro.config.js.
  • Android permission missing. Confirm ACCESS_NETWORK_STATE is in AndroidManifest.xml and rebuild the Android app.
  • iOS pods out of date. Run cd ios && pod install && cd .. again after any native dependency change.
  • Using Expo. If you started from an Expo project, none of this setup will work. You must use a bare React Native project.

Next