> ## Documentation Index
> Fetch the complete documentation index at: https://developer.neurofit.app/llms.txt
> Use this file to discover all available pages before exploring further.

# API reference (wrappers)

> Methods, events, types, errors and platform differences of the React Native, Flutter and Capacitor packages.

The React Native, Flutter and Capacitor packages forward every call to the native iOS and Android SDKs. The three
share method names, event names and payload keys, and no reading logic runs in JavaScript or Dart. The React Native
module works on the legacy bridge and, through the interop layer, on the New Architecture. Minor releases may add
events, enum cases and error codes: keep a default branch ([Versioning](/changelog#versioning)).

<CodeGroup>
  ```ts React Native theme={null}
  import { NeurofitVitals, VitalsReadingFlow } from '@neurofit/vitals-react-native';
  ```

  ```dart Flutter theme={null}
  import 'package:neurofit_vitals/neurofit_vitals.dart';
  ```

  ```ts Capacitor theme={null}
  import { NeurofitVitals } from '@neurofit/vitals-capacitor';
  ```
</CodeGroup>

## Methods

<Tabs>
  <Tab title="React Native">
    ```ts theme={null}
    NeurofitVitals.activate(licenseKey: string): Promise<void>
    NeurofitVitals.deviceSupport(): Promise<DeviceSupport>
    NeurofitVitals.hasCameraPermission(): Promise<boolean>
    NeurofitVitals.requestCameraPermission(): Promise<boolean>
    NeurofitVitals.start(): Promise<void>
    NeurofitVitals.stop(): Promise<void>
    NeurofitVitals.cancelReading(): Promise<void>
    NeurofitVitals.setAutoStartEnabled(enabled: boolean): Promise<void>
    NeurofitVitals.addListener<Name extends VitalsEventName>(
      event: Name,
      callback: (payload: VitalsEventPayload<Name>) => void,
    ): VitalsSubscription   // { remove(): void }
    ```
  </Tab>

  <Tab title="Flutter">
    ```dart theme={null}
    abstract final class NeurofitVitals {
      static Stream<VitalsEvent> get events;   // broadcast, errors are VitalsException
      static Future<void> activate(String licenseKey);
      static Future<DeviceSupport> deviceSupport();
      static Future<bool> hasCameraPermission();
      static Future<bool> requestCameraPermission();
      static Future<void> start();
      static Future<void> stop();
      static Future<void> cancelReading();
      static Future<void> setAutoStartEnabled(bool enabled);
    }
    ```
  </Tab>

  <Tab title="Capacitor">
    ```ts theme={null}
    interface NeurofitVitalsPlugin {
      activate(options: { licenseKey: string }): Promise<void>;
      deviceSupport(): Promise<DeviceSupport>;
      hasCameraPermission(): Promise<{ granted: boolean }>;
      requestCameraPermission(): Promise<{ granted: boolean }>;
      start(): Promise<void>;
      stop(): Promise<void>;
      cancelReading(): Promise<void>;
      setAutoStartEnabled(options: { enabled: boolean }): Promise<void>;
      addListener<Name extends VitalsEventName>(
        eventName: Name,
        listenerFunc: (event: VitalsEventPayload<Name>) => void,
      ): Promise<PluginListenerHandle>;
      removeAllListeners(): Promise<void>;
    }
    ```
  </Tab>
</Tabs>

| Method | What it does | Rejects with |
| - | - | - |
| `activate` | Checks the license key offline, once per launch | `licenseInvalid` ([reasons and fixes](/license-keys#licenseinvalid-reasons)) |
| `deviceSupport` | `{ isSupported, hasTorch, reason }` ([Device support](/device-support)) | nothing |
| `hasCameraPermission` | Whether camera access is granted. Never prompts | nothing |
| `requestCameraPermission` | Shows the system prompt once, `true` when granted | `invalidUsage` (Android, no host Activity) |
| `start` | Starts the session: camera and torch on, then positioning. Never prompts | `notActivated`, `cameraPermissionDenied`, `unsupportedDevice`, `invalidUsage` |
| `stop` | Camera and torch off, session released. Idempotent | nothing |
| `cancelReading` | Cancels the reading in progress (`cancelledByApp`) | nothing |
| `setAutoStartEnabled` | Holds (`false`) or resumes (`true`) automatic starts, `true` by default | nothing |

Any call can also fail with `internalError` (a bug), and every call fails with `UNAVAILABLE` on the Capacitor
web build ([Errors](#errors)).

## One session at a time

There is no session object. `start()` creates the wrapper's one session and starts it, and you call `start()`
again to retry.

* While the session runs, `start()` rejects with `invalidUsage`, so call `stop()` first.
* A `start()` that rejects keeps no session, so call it again once the cause is fixed.
* `completed` or `failed` ends the session, and the next `start()` begins a new one without `stop()`. A
  `cancelled` event does not end it: the next reading starts by itself
  ([how retries work](/reading-lifecycle#finalizing)).
* `stop()` releases the session. Stopped after `measurementComplete`, it still delivers the outcome, then ends
  ([Finalizing](/reading-lifecycle#finalizing)). A `start()` before that outcome arrives supersedes the old session
  and drops its remaining events.
* `setAutoStartEnabled(false)` before `start()` holds that session, and the setting goes back to `true` when the
  session ends ([Holding the start](/reading-lifecycle#holding-the-start)).
* `activate`, `deviceSupport` and camera permission are app-wide.

## Events

| Event | Payload keys besides `event` |
| - | - |
| `guidance` | `guidance` (`Guidance`), `startProgress` (0 to 1) |
| `started` | none |
| `preview` | the `LivePreview` keys (Dart: `PreviewEvent.preview`) |
| `heartbeat` | `ibiMs` (number or `null`) |
| `cancelled` | `reason` (`CancelReason`) |
| `measurementComplete` | none |
| `completed` | `result` (`VitalsResult`) |
| `failed` | `error` (`VitalsError`, Dart `VitalsException`) |

Every key is always present, with `null` for an absent optional. TypeScript names the payloads `GuidanceEvent`,
`StartedEvent`, `PreviewEvent`, `HeartbeatEvent`, `CancelledEvent`, `MeasurementCompleteEvent`, `CompletedEvent`
and `FailedEvent`, with the `VitalsEvent` union and `VitalsEventPayload<Name>`. Dart uses the same names for the
subclasses of the sealed `VitalsEvent`. What each event means: [Events](/reading-lifecycle#events).

## Types

<Tabs>
  <Tab title="TypeScript">
    ```ts theme={null}
    type Guidance = 'noContact' | 'unsteady' | 'weakPulseSignal' | 'steady' | 'lowFrameRate';
    type CancelReason = 'contactLost' | 'poorSignal' | 'qualityCheckFailed' | 'interrupted' | 'cancelledByApp';
    type SignalLevel = 'strong' | 'good' | 'fair' | 'weak';
    type SignalQuality = 'clean' | 'usable';
    type BreathingRateConfidence = 'high' | 'low' | 'withheld';
    type VitalsErrorCode =
      | 'notActivated' | 'licenseInvalid' | 'cameraPermissionDenied' | 'cameraUnavailable'
      | 'unsupportedDevice' | 'invalidUsage' | 'internalError';   // Capacitor adds 'UNAVAILABLE'

    interface VitalsError { code: VitalsErrorCode; message: string }
    interface DeviceSupport { isSupported: boolean; hasTorch: boolean; reason: string | null }
    interface LivePreview {
      progress: number;   // 0 to 1
      heartRateBpm: number | null;
      rmssdMs: number | null;
      signalLevel: SignalLevel | null;
    }
    interface VitalsResult {
      id: string;
      capturedAt: string;
      heartRateBpm: number;
      rmssdMs: number;
      sdnnMs: number;
      baevskyStressIndex: number | null;
      sd2sd1: number | null;
      breathingRateBrpm: number | null;
      breathingRateConfidence: BreathingRateConfidence;
      quality: SignalQuality;
      sdkVersion: string;
    }
    ```
  </Tab>

  <Tab title="Dart">
    ```dart theme={null}
    enum Guidance { noContact, unsteady, weakPulseSignal, steady, lowFrameRate }
    enum CancelReason { contactLost, poorSignal, qualityCheckFailed, interrupted, cancelledByApp }
    enum SignalLevel { strong, good, fair, weak }
    enum SignalQuality { clean, usable }
    enum BreathingRateConfidence { high, low, withheld }
    enum VitalsErrorCode { notActivated, licenseInvalid, cameraPermissionDenied, cameraUnavailable,
                           unsupportedDevice, invalidUsage, internalError, unknown }

    class VitalsException implements Exception { VitalsErrorCode code; String message; }
    class DeviceSupport { bool isSupported; bool hasTorch; String? reason; }
    class LivePreview { double progress; double? heartRateBpm; double? rmssdMs; SignalLevel? signalLevel; }
    class VitalsResult {
      String id;
      DateTime capturedAt;
      double heartRateBpm, rmssdMs, sdnnMs;
      double? baevskyStressIndex, sd2sd1, breathingRateBrpm;
      BreathingRateConfidence breathingRateConfidence;
      SignalQuality quality;
      String sdkVersion;
      Map<String, Object?> toMap();
      factory VitalsResult.fromMap(Map<Object?, Object?> map);
    }
    ```
  </Tab>
</Tabs>

Fields are final. Enum values are the same on iOS and Android.

* `VitalsResult` is the [Result JSON](/metrics#result-json). TypeScript receives it as is in `completed`. In Dart,
  `toMap()` writes it, `VitalsResult.fromMap` reads it back, and results compare by value.
* `LivePreview`: `heartRateBpm` is `null` until there is a heart rate, `rmssdMs` until about 20 seconds in, and
  `signalLevel` until the first scored preview. Each reading's first preview follows `started` at once, with
  `progress` 0 and every other value `null`.

## Errors

A rejection (TypeScript) or a thrown `VitalsException` (Dart) carries `code` and `message`. Match on `code`, the
message is informational. [Methods](#methods) lists what each call rejects with. The `failed` event's `error` has
the same shape, with `cameraUnavailable` (the camera could not be opened, or stopped and could not be restored) or
`internalError`, and the session has ended. Dart maps a code the package does not know to `unknown`, and delivers
errors on `NeurofitVitals.events` as `VitalsException` too.

`invalidUsage` means a calling mistake in your app:

| Condition | Message |
| - | - |
| `start()` while the session runs | `The session is already running` |
| Android, no host Activity at `start()` | `No Activity available to bind the camera to` |
| Android, Flutter: no host Activity at `requestCameraPermission()` | `No Activity available to request the camera permission` |
| Android, React Native and Capacitor: the host is not a `ComponentActivity` at `requestCameraPermission()` | `No ComponentActivity available to request the camera permission` |
| Flutter and Capacitor: an argument missing | `licenseKey is required`, `enabled is required` |

What to do about each code: [Failures](/reading-lifecycle#failures).

## Reading flow (React Native and Flutter)

Both packages export the pre-built flow and its parts. Screens, options, theme and strings:
[Pre-built reading flow](/reading-flow).

| | React Native | Flutter |
| - | - | - |
| Flow | `VitalsReadingFlow`, props `VitalsReadingFlowProps`, ref `VitalsReadingFlowHandle` with `back()` | `VitalsReadingFlow` widget |
| `onError` receives | `VitalsError` | `VitalsException` |
| Camera preview | `VitalsCameraPreview` with `onReady` | `VitalsCameraPreview` with `onReady` |
| Building blocks | `VitalsProgressRing`, `VitalsGuidanceCard`, `VitalsSignalIndicator`, `VitalsLiveStrip`, `VitalsResultsCard` | the same names |
| Defaults | `VitalsStrings()`, `VitalsTheme()` | `const VitalsStrings()`, `const VitalsTheme()` |
| Overrides | partial objects (`VitalsStringsOverride`, `VitalsThemeOverride`) | `copyWith` |
| Text for your own screens | `guidanceText(guidance, hasTorch)`, `cancelNotice(reason)` | `guidanceText(guidance, hasTorch: ...)`, `cancelNotice(reason)` |

The camera preview and the building blocks may change in minor releases
([Building blocks](/reading-flow#building-blocks)).

## Threads

Events arrive in native order. Call any method from your JavaScript or Dart code: the wrappers move to the main
thread themselves.

| Framework | Events | Method calls |
| - | - | - |
| React Native | Sent from the main thread, listeners run on the JavaScript thread | Moved to the main thread |
| Flutter | Sent on the platform (main) thread, received on the root isolate | Run on the platform thread |
| Capacitor | Sent from the main thread, listeners run in the WebView's JavaScript context | Moved to the main thread |

## Platform differences

| Difference | Where | Detail |
| - | - | - |
| Arguments | Capacitor | One options object (`{ licenseKey }`, `{ enabled }`), and the permission calls resolve `{ granted }`. The others take plain arguments and resolve booleans |
| Listening | All | React Native `addListener` returns `{ remove() }`. Flutter listens to `NeurofitVitals.events`. Capacitor `addListener` resolves a `PluginListenerHandle` |
| Errors | Flutter | Throws `VitalsException`. The others reject with `{ code, message }` |
| Web | Capacitor | A stub: every method rejects with `UNAVAILABLE`. Check `Capacitor.isNativePlatform()` first |
| Pre-built flow, camera preview | React Native, Flutter | Not on Capacitor (a camera preview is on the [Roadmap](/roadmap)). The React Native flow needs the New Architecture, and in a `Modal` you forward Android back to the ref's `back()` |
| Orientation | React Native, Flutter | The flows lock portrait on Android while mounted. On iOS, restrict your app's orientations ([Orientation and screen](/reading-lifecycle#orientation-and-screen)) |
| Camera prompt | Android | React Native, Capacitor and `FlutterFragmentActivity` hosts use the SDK's request ([caveat](/api-reference-android#vitalssdk)). A plain `FlutterActivity` falls back to `ActivityCompat.requestPermissions` |
| Preview order | Android | The session binds the preview mounted at `start()`, so wait for `onReady`. On iOS the order does not matter |
| `weakPulseSignal` | iOS | Reported on iOS only for now ([Positioning](/reading-lifecycle#positioning)) |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.