Skip to main content
This page takes you from an empty project to a completed reading (heart rate, HRV (RMSSD), breathing rate and related measures) on Android 9 (API 28) or later. The SDK is the Kotlin package com.neurofit.vitals, published as com.neurofit:vitals-sdk:1.0.0. It compiles against compileSdk 36, targets JVM 17 and is built with CameraX 1.5.0.
1

Add the dependency

Binary license. Your delivery contains the AAR and a Maven layout you can publish to your artifact repository (or install to mavenLocal() with the included build_aar.sh, which runs publishToMavenLocal).
build.gradle.kts
The Maven artifact declares the dependencies the SDK’s public API uses as api, so your app compiles against PreviewView, LifecycleOwner, ComponentActivity and the coroutine flows without declaring them. If you add the AAR file directly instead, add its dependencies yourself, at these exact versions:
build.gradle.kts
Source license. Include the :vitals-sdk module from the delivered Gradle project with includeBuild or by copying the module into your project; it compiles the engine sources from ppgcore/ports/kotlin in place.Your app needs minSdk 28 or higher and Kotlin 2.0 or newer. The SDK’s consumer-rules.pro keeps the public API (com.neurofit.vitals.*) under R8.
2

Declare the camera permission

The AAR’s manifest already declares the camera permission and marks the camera and flash features optional, and manifest merging brings them into your app. Declaring them yourself is harmless:
AndroidManifest.xml
The SDK does not need INTERNET or any other permission. It reads the accelerometer, which needs no permission, to help estimate breathing rate.
3

Activate the SDK

Call activate once, early in the app’s life, before you create a session. Verification is offline. The call throws a VitalsException when the key is malformed, signed by another key, issued for a different application id, or does not cover this SDK build’s date. A debuggable build accepts a key that does not list its application id and reports it in warnings; a release build rejects it.
VitalsSDK is a Kotlin object whose members are @JvmStatic, so Java calls them as statics (VitalsSDK.activate(...)); the VitalsSDK.INSTANCE.activate(...) form works too. See License keys for the key format, updatesUntil and the debuggable-build behaviour.
4

Check device support and request the camera permission

The SDK registers its own permission launcher through the activity’s ActivityResultRegistry, so there is no launcher to declare in your activity. Call it on the main thread. When the permission is already granted the callback runs synchronously with true.
The launcher is registered on this activity instance: if the activity is recreated while the system dialog is showing (a rotation), the callback is never called. Lock the orientation of the screen that asks, and check hasCameraPermission again in onResume.
5

Create a session and collect events

A VitalsSession owns the camera, the torch and one reading at a time. events (a SharedFlow) and state (a StateFlow) are emitted on the SDK’s engine thread, so collect them inside lifecycleScope (which runs on the main thread) before touching views, and subscribe before start(): events are not replayed. Java callers use setListener with the executor the callbacks should run on. start() must be called on the main thread.
start(owner, previewView) binds the camera to the given LifecycleOwner, turns on the torch and moves the session to Positioning before it returns. It throws VitalsException.InternalError when called off the main thread or on a stopped or finished session, NotActivated, CameraDenied, UnsupportedDevice and AlreadyRunning. The camera itself opens asynchronously: a camera that cannot be opened fails the session with Failed(CameraUnavailable) through state and events rather than an exception from start(). VitalsSDK.createSession throws NotActivated, UnsupportedDevice or InternalError("invalid configuration: ...").For a one-shot flow you can also val result = session.awaitResult() from a coroutine; it suspends until Completed and throws the VitalsException on Failed (or Interrupted if stop() runs first). session.getResult { } is the callback form for Kotlin code outside a coroutine; its callback receives a Kotlin Result, so from Java handle VitalsEvent.Completed and VitalsEvent.Failed in the listener instead.
6

Use the result

VitalsResult is a data class with a toMap() for JSON serialisation (capturedAt as ISO 8601, quality as "clean" / "usable" / "withheld"). Field meanings, units and how to treat the WITHHELD tier are on the Metrics page.

Jetpack Compose

Wrap CameraX’s PreviewView in an AndroidView and pass it to start from the main thread. Collect session.events inside a LaunchedEffect and session.state with collectAsStateWithLifecycle.

Guidance copy

Guidance is an enum (NO_CONTACT, LOW_QUALITY, WEAK_SIGNAL, COMPLIANT, FRAME_DROP); the SDK does not ship strings. Map each value to your own localized copy. The NEUROFIT app’s copy is listed on the Reading lifecycle page as a starting point.

Lifecycle notes

  • Call stop() (or close(), the session is AutoCloseable) when the reading screen goes away. The SDK also stops the session when the lifecycle owner you passed is destroyed, but call it yourself as well.
  • The SDK binds and unbinds only its own CameraX use cases (never unbindAll()), and clears the Camera2 options it applied when it stops, so your own camera features are untouched and open with default exposure afterwards.
  • Leaving the foreground (the owner’s ON_STOP) while positioning or measuring fails the session with Interrupted("The app moved to the background"); a finalizing session still delivers its result. A rotation recreates the activity and passes through ON_STOP, so lock the reading screen’s orientation and keep the screen on (FLAG_KEEP_SCREEN_ON).
  • After a cancel with autoRestartAfterCancel = false the session is Idle with the camera off, and start(owner, previewView) works again on the same session. After stop(), Completed or Failed, create a new session.
  • Battery Saver can lower the camera frame rate. While positioning, the session reports a recoverable FrameRateUnsustainable once and re-arms; a second recoverable error fails with UnsupportedDevice. See Troubleshooting.
  • For debugging, VitalsSDK.loggingEnabled = true logs state transitions with tag NeurofitVitals; it is off by default and never logs values or frames.

Next steps