Quick start
This tutorial builds the smallest useful native app shell: an Ovok client, the SDK theme, and a composable sign-in screen. It assumes the repository-compatible Expo SDK 57 / React Native 0.86.3 toolchain and a native development build.
1. Install and configure
npm install @ovok/native @ovok/core
Before building, install the peer packages for the features you use from the
installation matrix. The root import
contains the shared/auth/Bluetooth-management surface; use a public subpath for
optional integrations such as @ovok/native/data-sync or
@ovok/native/background-sync. The SDK requires a development build; Expo Go cannot
load its native Bluetooth and health modules.
2. Build the native app
npx expo prebuild
npx expo run:ios
# or
npx expo run:android
3. Initialize the client and mount the app shell
Call polyfillMedplumWebAPIs once, create an OvokClient with durable storage, and
mount OvokProvider from @ovok/core before native components:
import { OvokClient, OvokProvider } from "@ovok/core";
import {
DEFAULT_COLORS,
DEFAULT_MULTIPLIERS,
ExpoClientStorage,
ThemeProvider as OvokThemeProvider,
polyfillMedplumWebAPIs,
} from "@ovok/native";
import { Stack } from "expo-router";
polyfillMedplumWebAPIs();
const client = new OvokClient({
storage: new ExpoClientStorage(),
baseUrl: process.env.EXPO_PUBLIC_OVOK_BASE_URL,
fhirUrlPath: "/fhir",
});
export default function RootLayout() {
return (
<OvokProvider client={client}>
<OvokThemeProvider
theme={{
colors: DEFAULT_COLORS,
dark: false,
spacingMultiplier: DEFAULT_MULTIPLIERS.spacing,
borderRadiusMultiplier: DEFAULT_MULTIPLIERS.borderRadius,
}}
>
<Stack />
</OvokThemeProvider>
</OvokProvider>
);
}
Add KeyboardProvider, BottomSheetModalProvider, and your navigation theme when
the screens you use require them. See App shell and providers
for the complete provider order.
4. Render a screen
import { SignIn } from "@ovok/native";
export function LoginScreen() {
return (
<SignIn>
<SignIn.Header>
<SignIn.Header.Title />
</SignIn.Header>
<SignIn.EmailForm
loginType="Patient"
tenantCode={process.env.EXPO_PUBLIC_TENANT_CODE}
onSuccess={() => console.log("signed in")}
onError={(error) => console.error(error)}
>
<SignIn.EmailForm.Inputs />
<SignIn.EmailForm.SigninButton />
</SignIn.EmailForm>
</SignIn>
);
}
5. Add a native integration
For Bluetooth, create one long-lived BLE manager and mount BTProvider around the
screen that owns the measurement flow:
import { BleManager } from "react-native-ble-plx";
import { BTProvider, IntegratedDevices } from "@ovok/native/bt-management";
const bleManager = new BleManager();
const acceptedDevices = [IntegratedDevices.BP2] as const;
export function BluetoothScreen() {
return (
<BTProvider
bleManager={bleManager}
acceptedDevices={acceptedDevices}
onResult={(result) => console.log("measurement", result.data)}
onError={({ error }) => console.error(error)}
>
<YourDeviceScreen />
</BTProvider>
);
}
For HealthKit, Health Connect, or background delivery, use the corresponding subpath and continue with Health data or Background sync before adding its native entry point.
6. Verify native behavior
Test the flow on a physical device. The minimum useful smoke test is one login, one BLE reading or health authorization, and one restart of the app.
For the full path, continue to installation, Bluetooth, health data, and the public API map.