# BTProvider

Canonical page: https://docs.ovok.com/native-sdk/guide/components/bt-management/bt-provider

`BTProvider` is the React boundary for an SDK-managed Bluetooth session. It requests runtime permissions, starts discovery for the device declarations you accept, exposes the runtime to Bluetooth hooks and UI, and releases managed devices when the provider unmounts.

Import it from `@ovok/native/bt-management`.

## Minimal setup [#minimal-setup]

```tsx
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;

<BTProvider
  bleManager={bleManager}
  acceptedDevices={acceptedDevices}
  onDeviceFound={(device) => device.connect()}
  onResult={(result) => saveMeasurement(result)}
>
  <MeasurementScreen />
</BTProvider>;
```

Keep `bleManager` and `acceptedDevices` stable. A new array on each render changes the provider's manager configuration and restarts the session.

## Props [#props]

Every prop, with its type and description, is listed in the [`@ovok/native/bt-management` API reference](/native-sdk/reference/bt-management).

## Background result delivery [#background-result-delivery]

`backgroundSync` is opt-in. It enables background scan behavior, creates a durable
result queue, and starts the provider-managed Android connected-device foreground
service. The queue configuration requires persistent storage and an asynchronous
delivery callback:

```tsx
<BTProvider
  bleManager={bleManager}
  acceptedDevices={acceptedDevices}
  backgroundSync={{
    enabled: true,
    storage: {
      getItem: (key) => measurementStore.getItem(key),
      setItem: (key, value) => measurementStore.setItem(key, value),
    },
    getOwnerKey: () => authStore.currentAccountKey,
    onResult: async (result) => {
      await uploadMeasurement(result, {
        accountKey: result.ownerKey,
        idempotencyKey: result.id,
      });
    },
    android: {
      foregroundService: {
        notificationTitle: "Bluetooth monitoring",
        notificationBody: "Listening for health measurements",
      },
    },
  }}
>
  <MeasurementScreen />
</BTProvider>
```

`storage` must survive process restarts. The queue writes the result before invoking
`onResult` and removes it after that promise resolves. Delivery is at least once, so
deduplicate server writes by `result.id`. The result also includes `queueId`,
`queuedAt`, and `attempt`. Failed items remain queued for automatic retry; default
retry settings are three attempts with a 30-second exponential backoff base. The
coordinator checks the active `getOwnerKey()` before each delivery and each result
includes the `ownerKey` captured when it was queued. Use that value to select the
account for persistence. A callback already running cannot be canceled by an account
switch; its remaining queued results wait until their owner is active again.

The provider's separate `onResult` prop remains an immediate measurement callback. It
is not the queue acknowledgement and its return value is not awaited. Use
`backgroundSync.onResult` for app-owned delivery that must complete before an item is
removed from the queue.

`backgroundSync.android.foregroundService` controls the provider-managed Android
service notification and optional battery-optimization request. If the app owns the
BLE scan outside this provider, use the standalone
[`AndroidBluetoothForegroundService`](/native-sdk/guide/components/background-sync/) instead. Do not start
both service owners for the same scan.

For the iOS restoration identifier, service UUID filters, Android native setup, and
HealthKit or Health Connect background paths, see the [background sync guide](/native-sdk/guide/modules/background-sync).

## Permission behavior [#permission-behavior]

By default, the provider requests Bluetooth permissions on mount. Android 12 and later requests `BLUETOOTH_CONNECT` and `BLUETOOTH_SCAN`; earlier Android versions request fine location. iOS requests Bluetooth access. It checks again when the app returns from Settings after the app opens Settings through the permission flow. Configure the corresponding native declarations in the [installation guide](/native-sdk/guide/how-to/installation).

When `gateOnPermission` is `true`, the provider renders a loading indicator while the automatic request is unresolved. If access is denied, it calls `permissionFallback` when supplied; otherwise it renders `DefaultPermissionFallback`, which opens the app settings.

To ask only after the patient chooses to pair, set `requestPermissionOnMount={false}`. `BTProvider` and its runtime stay mounted, but discovery waits until a child calls `useBluetoothState().requestPermission()` and the request succeeds. The hook must render beneath `BTProvider`; its returned promise resolves to whether access was granted. Handle a rejected promise in the app. In this mode, `gateOnPermission` does not gate children, so keep the pairing action available and use the hook's `permissionStatus` to gate other UI if needed.

```tsx
import { Button } from "react-native";
import { useBluetoothState } from "@ovok/native/bt-management";

function PairButton() {
  const { requestPermission } = useBluetoothState();

  return (
    <Button
      title="Pair a device"
      onPress={() => {
        void requestPermission().catch((error) =>
          console.warn("Bluetooth permission request failed", error),
        );
      }}
    />
  );
}

<BTProvider
  bleManager={bleManager}
  acceptedDevices={acceptedDevices}
  requestPermissionOnMount={false}
>
  <PairButton />
  <MeasurementScreen />
</BTProvider>;
```

## Runtime lifetime [#runtime-lifetime]

The provider creates its manager only when `acceptedDevices` has at least one entry and starts scanning after callback subscriptions are installed. With `requestPermissionOnMount={false}`, scanning instead waits until the app-triggered request succeeds. On teardown it stops the scan, removes listeners, and destroys the manager. Place the provider at the lowest stable tree level that should keep the scan active.

Bluetooth hooks and `PairingFlow` must render beneath this provider. A route may read runtime data while the provider is mounted, but it should not create a competing manager of its own.

## Related APIs [#related-apis]

- [Callbacks](/native-sdk/guide/components/bt-management/bt-provider/)
- [BluetoothDevicePicker](/native-sdk/guide/components/bt-management/bluetooth-device-picker/)
- [Runtime hooks](/native-sdk/guide/components/bt-management/hooks/)
- [Bluetooth guide](/native-sdk/guide/modules/bt-management)
