Skip to main content
Most issues developers encounter with the Neurofit Vitals SDK fall into a small number of categories: licence validation, camera permissions, scan quality, and platform-specific integration edge cases. Work through the relevant section below to resolve the symptom you are seeing. If your issue is not covered here, reach out to care@neurofit.app with your SDK version, device model, and any relevant log output.
Symptoms
  • iOS: VitalsError.licenseInvalid returned in the SDK initialisation callback
  • Android: VitalsException with code LICENSE_INVALID thrown from VitalsSDK.configure()
Common causes
  • 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
Fix
  1. Print or log the exact key string your app is passing and verify its length matches the key you received.
  2. Trim whitespace before passing the key: in Swift use licenseKey.trimmingCharacters(in: .whitespaces); in Kotlin use licenseKey.trim().
  3. If the error persists after trimming, contact care@neurofit.app to confirm that your key is active.
Never commit your license key directly into source control. Use an environment variable, a secrets manager, or your CI pipeline’s secret injection to supply it at build time.
Symptoms
  • iOS: VitalsError.cameraPermissionDenied surfaced through the scan delegate
  • Android: VitalsException with code CAMERA_PERMISSION_DENIED before or during scan launch
iOS fix
  1. Add NSCameraUsageDescription to your app’s Info.plist with 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.
  2. Request camera access before presenting the scan using:
  3. If the user has previously denied access, direct them to Settings → Privacy & Security → Camera to re-enable it.
Android fix
  1. Declare the permission in your AndroidManifest.xml:
  2. Request the permission at runtime before launching VitalsScanActivity (required on API 23+):
  3. Handle the result in onRequestPermissionsResult and only launch the scan when PERMISSION_GRANTED is 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.
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.
Configure an Xcode Run Destination or Android Studio deployment target that points directly to your test device. Keeping a dedicated test device on your desk prevents accidental simulator runs during development.
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)
Fix
  1. 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.
  2. Apply firm but comfortable pressure — approximately the force you’d use to press a doorbell.
  3. Keep the device steady and rest your elbow on a surface to reduce movement artefacts.
  4. Test in moderate indoor lighting rather than under direct sunlight or harsh overhead fluorescents.
  5. If the device has a thick or raised camera bump, verify the case is not preventing full lens coverage.
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/null breathing 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.
SymptomThe app crashes with a 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:
Register your Application subclass in AndroidManifest.xml:
Do not call VitalsSDK.configure() from an Activity.onCreate() or a dependency-injection initialiser that runs on the main thread after launch — this risks initialising the SDK before the Application context is fully ready or re-initialising it on subsequent activity restarts.
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:
If you are using VitalsScanView in a SwiftUI sheet, update your binding state variable inside the async block for the same reason.
SymptomWhen embedding 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:
Keep all other state that changes during the scan (e.g. progress indicators, timers) in a ViewModel and observe it from a sibling composable rather than from within the VitalsScanComposable branch of the tree.