License key errors — VitalsError.licenseInvalid / LICENSE_INVALID
License key errors — VitalsError.licenseInvalid / LICENSE_INVALID
Symptoms
- iOS:
VitalsError.licenseInvalidreturned in the SDK initialisation callback - Android:
VitalsExceptionwith codeLICENSE_INVALIDthrown fromVitalsSDK.configure()
- The key string contains a leading or trailing whitespace character copied from a document or email
- The key has not yet been activated on Neurofit’s side (new purchases may have a short activation window)
- The key was entered manually and contains a transcription error
- Print or log the exact key string your app is passing and verify its length matches the key you received.
- Trim whitespace before passing the key: in Swift use
licenseKey.trimmingCharacters(in: .whitespaces); in Kotlin uselicenseKey.trim(). - If the error persists after trimming, contact care@neurofit.app to confirm that your key is active.
Camera permission not granted — cameraPermissionDenied / CAMERA_PERMISSION_DENIED
Camera permission not granted — cameraPermissionDenied / CAMERA_PERMISSION_DENIED
Symptoms
- iOS:
VitalsError.cameraPermissionDeniedsurfaced through the scan delegate - Android:
VitalsExceptionwith codeCAMERA_PERMISSION_DENIEDbefore or during scan launch
- Add
NSCameraUsageDescriptionto your app’sInfo.plistwith a clear explanation of why camera access is needed (e.g. “Camera access is required to measure your heart rate and HRV.”). Without this key, iOS will not present the permission prompt and your app will be rejected by App Review. - Request camera access before presenting the scan using:
- If the user has previously denied access, direct them to Settings → Privacy & Security → Camera to re-enable it.
- Declare the permission in your
AndroidManifest.xml: - Request the permission at runtime before launching
VitalsScanActivity(required on API 23+): - Handle the result in
onRequestPermissionsResultand only launch the scan whenPERMISSION_GRANTEDis confirmed.
On Android, if the user selects “Don’t ask again”, you must direct them to the system App Settings screen — the runtime permission dialog will not appear again.
Scan shows no camera preview — running on a simulator
Scan shows no camera preview — running on a simulator
SymptomThe scan screen launches but displays a blank or black camera preview and never starts the countdown.CauseiOS Simulator and Android Emulator do not emulate real camera hardware. The scan pipeline depends on live PPG data from the physical rear camera and flash — neither is available in a simulator.FixConnect a physical device and run the target on it. There is no workaround or mock mode for simulator testing; this is by design to ensure signal integrity.
High rate of withheld results during testing
High rate of withheld results during testing
SymptomA significant proportion of test scans return
qualityTier of .withheld / QualityTier.WITHHELD even when the scan completes.Common causes during development- Testing with a finger placed on a desk or mat rather than holding the phone naturally, causing inconsistent pressure
- Flash being partially obstructed by the device case cut-out
- Scanning under very bright overhead lighting that overwhelms the flash signal
- Pressing the finger so firmly that blood flow is restricted (blanching)
- Hold the device naturally and place the pad (not the tip) of the index or middle finger flat over both the camera lens and the flash LED simultaneously.
- Apply firm but comfortable pressure — approximately the force you’d use to press a doorbell.
- Keep the device steady and rest your elbow on a surface to reduce movement artefacts.
- Test in moderate indoor lighting rather than under direct sunlight or harsh overhead fluorescents.
- If the device has a thick or raised camera bump, verify the case is not preventing full lens coverage.
Breathing rate always returns nil or null
Breathing rate always returns nil or null
SymptomEvery scan completes with an
.accepted quality tier and valid heart rate and HRV values, but the breathingRate field is always nil (Swift) or null (Kotlin).ExplanationThis is expected behaviour, not a bug. Respiration rate is derived from the respiratory sinus arrhythmia component of the PPG signal, which is weaker and more variable than the cardiac signal. When the SDK determines that the respiratory signal was not strong enough to produce a reliable value, it deliberately withholds the field rather than returning an inaccurate number.Factors that can reduce respiratory signal strength include shallow breathing, breath-holding during the scan, or natural inter-individual variation.What to do- Do not treat a
nil/nullbreathing rate as an error — the other metrics remain valid. - In your UI, conditionally hide or omit the breathing rate rather than showing a zero or an error state.
- Encourage users to breathe normally and calmly during the scan for the best chance of capturing a respiratory signal.
SDK initialization crash on Android
SDK initialization crash on Android
SymptomThe app crashes with a Register your
NullPointerException or IllegalStateException originating from VitalsSDK.configure() on Android.CauseVitalsSDK.configure() requires an Application context. Calling it on an Activity context, a Fragment context, or — most commonly — before Application.onCreate() has completed causes the SDK to receive an invalid context and crash.FixInitialize the SDK exactly once, inside your Application subclass’s onCreate() method, using applicationContext:Application subclass in AndroidManifest.xml:VitalsScanViewController not dismissing on iOS
VitalsScanViewController not dismissing on iOS
SymptomAfter a scan completes, the
VitalsScanViewController stays on screen or the app briefly freezes before navigating away.CauseCalling dismiss(animated:completion:) (or performing a navigation action) from directly inside the SDK delegate callback — before the SDK has finished its own internal teardown — creates a dismissal conflict. UIKit processes one dismissal at a time; the SDK’s own cleanup and your navigation call collide on the main thread.FixDefer your post-scan navigation to the next main-thread runloop tick so the SDK’s dismissal completes first:Jetpack Compose integration — scan restarts unexpectedly
Jetpack Compose integration — scan restarts unexpectedly
SymptomWhen embedding Keep all other state that changes during the scan (e.g. progress indicators, timers) in a
VitalsScanComposable in a Jetpack Compose screen, the scan session appears to restart mid-scan, or the composable is destroyed and recreated while the user is scanning.CauseIf the composable that hosts VitalsScanComposable recomposes (due to state changes elsewhere in the tree), and the shouldScan flag or launch trigger is derived from unstable state, the scan composable can be removed from and re-added to the composition, restarting the session.FixHoist the scan launch state above the recomposing boundary and stabilise it with remember:ViewModel and observe it from a sibling composable rather than from within the VitalsScanComposable branch of the tree.