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 The Maven artifact declares the dependencies the SDK’s public API uses as Source license. Include the
mavenLocal() with the included build_aar.sh, which runs publishToMavenLocal).build.gradle.kts
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
: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:The SDK does not need
AndroidManifest.xml
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 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
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.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’sPreviewView 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()(orclose(), the session isAutoCloseable) 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 withInterrupted("The app moved to the background"); a finalizing session still delivers its result. A rotation recreates the activity and passes throughON_STOP, so lock the reading screen’s orientation and keep the screen on (FLAG_KEEP_SCREEN_ON). - After a cancel with
autoRestartAfterCancel = falsethe session isIdlewith the camera off, andstart(owner, previewView)works again on the same session. Afterstop(),CompletedorFailed, create a new session. - Battery Saver can lower the camera frame rate. While positioning, the session reports a recoverable
FrameRateUnsustainableonce and re-arms; a second recoverable error fails withUnsupportedDevice. See Troubleshooting. - For debugging,
VitalsSDK.loggingEnabled = truelogs state transitions with tagNeurofitVitals; it is off by default and never logs values or frames.
Next steps
- Reading lifecycle: every state, event and guidance value.
- Metrics: definitions, units and validated accuracy.
- API reference (Android): every public type.