Quick Start Guide

Summary
Follow this step-by-step guide to integrate the MultiScan SDK. There are example codes that can be referenced for both iOS and Android across multiple frameworks.
The latest version of MultiScan SDK is "23.11"

Example Code

AHI have written multiple one-page example apps that are identical in appearance and functionality, which enable you, the developer, to view the code and test the functionality of the AHI SDK.

Repo: https://github.com/ahi-dev/ahi-app-examples/tree/<VERSION_NUMBER>/trunk

Each example app have been written for both iOS and Android, in multiple languages and frameworks. Here's the current list of example apps:

  • Swift UIKit [Apple]
  • SwiftUI [Apple]
  • Kotlin [Android]
  • Jetpack Compose [Android]
  • React Native [Hybrid]
  • Flutter [Hybrid]

AHI have intentionally focused on the minimum functionality required to use the SDK with the optional and auxiliary functions also provided for convenient integration.

While there are hardware and software requirements for using AHI SDK, this guide does not touch on that; this guide focuses on how to integrate and use the SDK, with code examples found in the example apps.

Download

Run the following command to retrieve the AHI Examples to your local system:

  	 
git clone -b <VERSION_NUMBER> https://github.com/ahi-dev/ahi-app-examples.git
	
  
Copied!

Install

Follow the SDK installation guide for iOS (CocoaPods and Swift Package Manager) and Android (Gradle) distribution.

Before you begin

Before you begin developing, please ensure you have the following:

  1. A valid AHI Token - this should have been provided with access to the SDK.
  2. The Example App repository - you can access git.
  3. To access the SDK repo, we grant you access with some keys and tokens. We recommend that you add these to your system as environment variables. For Android development, your gradle file (Android) will read from these and fetch the SDKs. Open the Terminal (Mac) or Command Prompt (Windows) and access your system variables through vi or nano by typing nano .bash or nano .zshrc (or whichever profile your Terminal or Command Prompt uses) and add:
  	 
export AHI_MULTISCAN_AWS_URL=<URL WE PROVIDE>
export AHI_MULTISCAN_AWS_ACCESS_KEY=<ACCESS KEY WE PROVIDE>
export AHI_MULTISCAN_AWS_SECRET_KEY=<SECRET KEY WE PROVIDE>
	
  
Copied!

         4. A mobile device, ideally running the latest version of iOS or Android.

Some additional information for your development purposes:

  • All function names described are consistent in each app so that you can search for them as required across all example apps.
  • In your own app, you will be required to add camera access permissions.
  • iOS: info.plist - NSCameraUsageDescription = "We need to use your camera to take a scan.";
  • Android: manifest.xml - <uses-permission android:name="android.permission.CAMERA" />
  • Our SDK uses the Metric measurement format for all inputs and outputs. If you support Imperial or another measurement format, please ensure that you implement appropriate conversions to Metric before attempting scans.

Running the app

The first thing you will need to do is download and install your packages.

  • Swift/SwiftUI: Cocoapods - pod install
  • Kotlin/Jetpack: Gradle - build project
  • Flutter: pub get
  • iOS: pod install
  • Android: build project
  • React Native: npm install
  • iOS: pod install
  • Android: build project

If you have installed the dependencies, open the project in your preferred IDE or text editor. Next, you will want to search for a property called AHI_MULTI_SCAN_TOKEN and assign the value to the token we supplied, which is of type String.

At this point, you should be able to run the app on a device and utilize the functionality

Stage one: Setup SDK

The first state of the app requires you to setup the AHI MultiScan to enable performing scans. The "Setup SDK" functionality can be automated by your app as a background function, however for the sake of demonstrating the requirement, it is an actionable event that is triggered by the tap gesture on the button.

The setup button performs two tasks:

  • setupMultiScanSDK() - will call the AHI MultiScan SDK and pass in the AHI_MULTI_SCAN_TOKEN. If this succeeds, it will call:
  • authorizeUser() - will call the AHI MultiScan SDK and pass in the AHI_TEST_USER_ID, AHI_TEST_USER_SALT, and AHI_TEST_USER_CLAIMS variables, which are of type String,  and Array<String> respectively.

The authorizeUser() values that get passed in are hardcoded for the example app. When you integrate this functionality into your app, you will need to use your own:

  • USER_ID - A unique user ID which will never change
  • SALT - A salt string used for hashing your users data for greater privacy of their information.
  • CLAIMS - An optional array of unique Strings unique to the user (i.e. firebase creation timestamp).

More information on this can be found here: Authorization vs. Authentication

Note to Developer: User Claims

An array of user claims is needed for user authorization in order to provide additional information about the user beyond their username and identifier. These claims can be used to verify that the user is who they claim to be.

Here are a few examples of user claims that do not change and can be used for user authorization:

  1. dateCreated: The date an account was created can not change.
  2. dateOfBirth: A user's birthday can not change.

Stage two: ScanControl API

The ScanControl API helps to tack and manage scan usage. Each user should be announced to the ScanControl API before they take a scan for the first time. The user will then be able to continue conducting scans until the allocated credits are fully consumed or expired.

This stage can be completed after pricing and subscription schemes have been agreed on. There will be a handover of the API KEY and a list of relevant PRODUCT ID for calling the API. Please contact us if you have not received this.

1. Get Credentials

For the AHI token provided to the client (either dev or prod token), call the following API to determine the associated RevenueCat API for the account:

  	 
curl --request POST \
  --url https://api.ahi.tech/connect \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: <API KEY>' \
  --data '{"token":"<TOKEN>"}'
	
  
Copied!
  • API KEY - provided by AHI on request.
  • TOKEN - the AHI SDK development/production token.

The response will include the link to the RevenueCat complaint Web Hook API, VENDOR ID and APP ID as used to announce to AHI when payment transaction occurs, and credit the user wallet with scan credit:

  	 
{
  "url": "https://<ACC API>/revenuecat",
  "x-api-key": "<ACC API KEY>",
  "environment": "<ACC ENV>",
  "vid": "<VENDOR ID>",
  "aid": "<APP ID>"
}
	
  
Copied!
  • ACC API - URL endpoint for the account specific RevenueCat Web Hook API.
  • ACC API KEY - the API key to pass to the x-api-key request header.
  • ACC ENV - the associated environment type, either SANDBOX for dev, or PRODUCTION for prod, depending on what environment the provided token is associated with.
  • VENDOR ID - an ID unique to the client of AHI (formally referred to as "vendor").
  • APP ID - an ID unique to the App. This allows a client to have multiple apps and stages (prod, dev).
2. Announcing Transaction

Whenever a payment transaction occurs, whether user subscription for month or year, single scan purchase, or bulk purchase, AHI must be informed so that the user "wallet" can be credited with scan credits accordingly. With the details returned in step 1, call the RevenueCat Web Hook to inform payment transaction occured (if using RevenueCat product, this will be done automatically once configured).

For example, to announce a user monthly subscription pay transaction, using the details from 1. Get Link step, make the following call:

  	 
curl --request POST \
  --url https://<ACC API>/revenuecat \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: <ACC API KEY>' \
  --data '{
    "event":{
      "original_app_user_id": "<USER ID>",
      "product_id": "<PRODUCT ID>",
      "type": "<INITIAL_PURCHASE / RENEWAL / NON_RENEWING_PURCHASE>",
      "environment": "<ACC ENV>",
      "purchased_at_ms": <PURCHASE TIME>,
      "expiration_at_ms": <EXPIRE TIME>,
      "transaction_id": "<TRANSACTION ID>"
    }
  }'
	
  
Copied!
  • ACC API, ACC API KEY, ACC ENV - same as in Step 1.
  • USER ID - the associated user ID, as passed to AHI user authorization call.
  • PRODUCT ID - the purchase type, which AHI will supply to the client all product_id codes to facilitate the contract agreements.
  • INITIAL_PURCHASE / RENEWAL / NON_RENEWING_PURCHASE - payment type.
  • PURCHASE TIME and EXPIRE TIME - not used by AHI, but can be used for client audits.
  • TRANSACTION ID - not used by AHI, but can be used for client audits or cross-reference key.
Check Scan Credits Status

AHI SDK will automate the checking and redeeming of scan credits in the user wallet. However, the client can query the current state for a given user, to determine how much credit and expiry of said credit a user has for scans:

  	 
curl --request POST \
  --url https://<ACC API>/state \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: <ACC API KEY>' \
  --data '{
    "uid": "<USER ID>",
    "vid": "<VENDOR ID>",
    "aid": "<APP ID>"
  }'
	
  
Copied!
  • ACC API, ACC API KEY, ACC ENV - same as in step 1.
  • USER ID - the associated user ID, as passed to AHI user authorization call.
  • VENDOR ID - an ID unique to the client of AHI (formally referred to as "vendor").
  • APP ID - an ID unique to the App. This allows a client to have multiple apps and stages (prod, dev).

Stage three: Initiate FaceScan and/or Download Resources

If the Setup SDK process was successful, you will then see the second state containing two buttons:

  1. Start FaceScan
  2. Download Resources

Figure 3. After successfully setting up, you will have two options available to use.

Start FaceScan

This button invokes a function called startFaceScan() which performs the following:

  1. Defined a Map (Dictionary for iOS devs) of hardcoded user inputs which adhere to our SDK Schema. You will need to obtain these inputs from your users via some form of user input view.
  2. A simple validation containing all the relevant rules which is a function called areFaceScanConfigOptionsValid() and returns a boolean value.
  3. If the inputs are valid, proceed to initiate the scan.
  4. FaceScan will take control of your view and perform a FaceScan.
  5. The FaceScan can be cancelled and will return an Error (Exception for Flutter) and we have defined in code comments the values expected for the cancel event.
  6. If the FaceScan returns an error, it's handled accordingly.
  7. If the FaceScan completes successfully, results are printed in the logs, which adhere to the schema.
Download Resources (Required for BodyScan)

This button invokes the function downloadResources() which performs three different functions asynchronously:

  1. AHI MultiScan SDK function downloadResourcesInBackground() - this performs a void type function invoking the download of resources, which are required to complete a BodyScan.
  2. AHI MultiScan SDK function are AHIResourcesAvailable() - this performs a boolean type function
    a. true - The resources are downloaded and available.
    b. false - The resources are not downloaded.
  3. AHI MultiScan SDK function - check AHIResourcesDownloadSize() - this performs a long type function which returns the size of the download in bytes. We have conveniently provided the method to convert it to MB, however it's up to you how you best use or display this information.

In the event that the resources have not been downloaded, these three functions have been wrapped in a very simple timed loop set to 30 seconds; these three functions will be called every 30 seconds until the are AHIResourcesAvailable() returns true. We recommend implementing a polling mechanism in your app to provide a better User Experience.

Stage four: Initiate FaceScan and/or BodyScan

If the resources are available, you will then see the final state containing two buttons:

  1. Start FaceScan
  2. Start BodyScan

Since we have defined the FaceScan logic above, we will focus on the BodyScan.

Start BodyScan

This button invokes a function called startBodyScan() which performs the following:

  1. Defined a Map (Dictionary for iOS devs) of hardcoded user inputs which adhere to our SDK Schema. You will need to obtain these inputs from your users via some form of user input view.
  2. A simple validation containing all the relevant rules which is a function called areBodyScanConfigOptionsValid() and returns a boolean value.
  3. If the inputs are valid, we proceed to initiate the scan.
  4. BodyScan will take control of your view and perform a FaceScan.
  5. The BodyScan can be cancelled and will return an Error (Exception for Flutter) and we have defined in code comments the values expected for the cancel event.
  6. If the BodyScan returns an error, it's handled accordingly.
  7. If the BodyScan completes successfully, results are printed in the logs, which adhere to the schema.

Stage five: Configure strings for real-time guide - iOS

The iOS SDK provides some localization ability where the real-time on-screen prompts can be translated.

You will need to set the strings in the main bundle's Localizable.strings file accordingly. The English example is provided below..

Note: The Android SDK does not currently have this function.

FaceScan Strings

  	 
"MEASURING_COUNTDOWN" = "Measurement starting in...";
"MEASURING_GUEST" = "Measuring guest";
"MEASURING_MSG_DEFAULT" = "Please centre your face within the outline";
"MEASURING_PERFECT" = "Perfect! Please hold still";
"MEASURING_STARTED" = "Measurement Started\nPlease hold still";
"WARNING_CONSTRAINT_BACKLIGHT" = "The backlight is too strong\nPlease try changing position so more light falls directly on the face";
"WARNING_CONSTRAINT_BRIGHTNESS" = "Your face is too bright\nPlease try to dim the lights";
"WARNING_CONSTRAINT_DARKNESS" = "Your face is not well lit\nPlease try better lighting";
"WARNING_CONSTRAINT_DISTANCE" = "The face is too far\nPlease try to move closer to the camera";
"WARNING_CONSTRAINT_EXPOSURE" = "Please hold still while the camera is calibrating...";
"WARNING_CONSTRAINT_FPS" = "The camera is not maintaining the required frame rate\nPlease try closing all background applications";
"WARNING_CONSTRAINT_GAZE" = "The view of the face is poor\nPlease look directly into the camera";
"WARNING_CONSTRAINT_MOVEMENT" = "Too much movement\nPlease hold still";
"WARNING_CONSTRAINT_POSITION" = "Try to keep your face within the outline";
"WARNING_CONSTRAINT_TOO_CLOSE" = "Please move a little further away";
"WARNING_CONSTRAINT_TOO_FAR" = "Please move a little closer and centre your face within the outline";
"WARNING_LOW_POWER_MODE" = "For better accuracy, please disable Low Power Mode";
"MEASUREMENT_CANCELED" = "Measurement cancelled";
"ERR_CONSTRAINT_BRIGHTNESS" = "Your face was too bright\nPlease try to dim the lights";
"ERR_CONSTRAINT_DARKNESS" = "Your face was not well lit\nPlease try better lighting";
"ERR_CONSTRAINT_DISTANCE" = "Please move closer to the camera and try again";
"ERR_CONSTRAINT_FPS" = "The camera is not maintaining the required frame rate\nPlease try closing all background applications";
"ERR_CONSTRAINT_GAZE" = "Please directly face the camera throughout the measurement";
"ERR_CONSTRAINT_MOVEMENT" = "Please hold still throughout the measurement";
"ERR_CONSTRAINT_NO_NETWORK" = "To use AHI FaceScan, please make sure you're connected to WiFi or a mobile network";
"ERR_CONSTRAINT_POSITION" = "Please keep your face centred within the outline throughout the measurement";
"ERR_CONSTRAINT_TOO_FAR" = "Please move closer to the camera and keep your face centred within the outline throughout the measurement";
"ERR_MSG_SNR" = "The blood flow measurement was not reliable\nPlease try to allow the light to fall evenly across your face while holding still";
"ERR_MSG_SNR_SHORT" = "Please try to allow the light to fall evenly across your face while holding still";
"ERR_CAMERA_CALIBRATION_TIMEOUT" = "Could not calibrate camera\nRestarting calibration...";
	
  
Copied!

BodyScan Strings

  	 
//
//  AHI
//
//  Copyright (c) AHI. All rights reserved.
//

/* Text on a button in an error view */
"ahi.bs.adjust-height.button" = "Adjust Height and Try Again";

/* Text on a button in an error view */
"ahi.bs.adjust-phone.button" = "Adjust Phone and Try Again";

/* Title on a view helping the user align their phone */
"ahi.bs.alignment.title" = "Align Circles Over One Another.";

/* Title on a popup giving the user alignment tips */
"ahi.bs.alignment-help.title" = "Alignment Help";

/* Title on an error view */
"ahi.bs.cant-see-ankles.title" = "Can't See Your Ankles";

/* Alert message with positioning instructions */
"ahi.bs.feet-together.alert" = "Feet Together\nArms by Sides";

/* Alert message with positioning instructions */
"ahi.bs.outline.alert" = "Fit into Outline";

/* Alert message with positioning instructions */
"ahi.bs.body-in-frame.alert" = "Fit your Whole Body in the Frame";

/* Text in a popup giving the user positioning tips */
"ahi.bs.positioning-tips.text" = "If you are on the taller or shorter side, try to use some books or a lower surface.";

/* Text in a popup giving the user alignment tips */
"ahi.bs.alignment-help.text" = "Lean the phone against a solid object and then using your thumbs push the base gently until the circles are over one another.\n\nIf you have a phone with a flat edge, you might be able to balance it upright without needing a solid object.";

/* Alert message telling the user to change position. */
"ahi.bs.move-back.alert" = "Move Back";

/* Alert message telling the user to change position. */
"ahi.bs.move-closer.alert" = "Move Closer";

/* Alert message with positioning instructions */
"ahi.bs.one-person.alert" = "One Person Only";

/* Alert message with positioning instructions */
"ahi.bs.hold-still.alert" = "Perfect Spot\nHold Still";

/* Title on a popup giving the user positioning tips */
"ahi.bs.positioning-tips.title" = "Phone Height Tips";

/* Text on a button in an error view */
"ahi.bs.height-tips.button" = "Phone Height Tips...";

/* Text on a button in an error view */
"ahi.bs.placement-tips.button" = "Phone Placement Tips...";

/* Alert message telling the user to change position. */
"ahi.bs.too-high.alert" = "Phone Too High";

/* Alert message telling the user to change position. */
"ahi.bs.too-low.alert" = "Phone Too Low";

/* Title on an error view */
"ahi.bs.move-higher.title" = "Place Phone on Higher Surface";

/* Title on an error view */
"ahi.bs.move-lower.title" = "Place Phone on Lower Surface";

/* Subtitle on a popup giving positioning tips */
"ahi.bs.positioning-tips.subtitle" = "Try a surface the same height as your hips.";

/* Text on a button in an error view */
"ahi.bs.try-again.button" = "Try Again";

/* Alert message with positioning instructions */
"ahi.bs.turn-left.alert" = "Turn Left";

/* Title on an error view */
"ahi.bs.cant-see-you.title" = "We Can't See You";

/* Text on a button in an error view */
"ahi.bs.cant-see-you.button" = "Why can't you see me?";
	
  
Copied!

SDK Required functionality

There are a minimum of three required functions in order to get scan results, but we recommend implementing the following five functions to perform all the available scan options.

setupMultiScanSDK()

Uses your token to setup the SDK. Will return an error if the function fails or null/nil /empty String for success.

authorizeUser()

Requires the three parameters containing a unique user ID, salt and optional array of claims. Will return and error if the function fails or null/nil /empty String for success.

downloadAHIResources()

A void function that initialises the download of remote resources.

startFaceScan()/startBodyScan()

These functions require valid user input that adheres to the respective schema before invoking a scan session. If successful, a Map of results which also adheres to the respective schema will be returned. If it failed, a relevant error will be returned.

Additional functionality

The SDK provides several other functions that can enhance your user experience and the performance of your app.

areAHIResourcesAvailable()

Will return a boolean informing you of the status of the resources availability. The resources are required for BodyScan.

checkAHIResourcesDownloadSize()

Returns both the total size of all resources the SDK needs to download and the total size of the resources that have been downloaded so far.

getBodyScanExtras()

Requires an Array of valid body scan results schema containing the raw and ent values for a body scan and will return a Map object containing a meshURL with the path to a 3D avatar file.

getMultiScanStatus()

Will return a status of ready , disconnected or unavailable .

getMultiScanDetails()

Will return a Map containing information about your registered session with the AHI MultiScan

getUserAuthorisedState()

Requires the current user ID and will return the authorised state for that user. This can be used to indicate whether you need to call authoriseUser .

deauthorizeUser()

Will deauthorise the user from using the MultiScan service.

releaseMultiScanSDK()

Will release the MultiScan SDK session from memory requiring setupSDK() to be called again if you need to use it.

setMultiScanPersistenceDelegate()

Recommend using this to provide customised results based on historical user scan data. We refer to this process as "smoothing". You need to provide the previous BodyScan scan results in the format of an Array<Map<String, Any>> via your results database.

Validation

Several functions that support validating your inputs to the SDK, which adhere to our Schemas. We recommend that you use these functions to ensure that your integration will provide the best Quality Assurance.

You will note validation occurs for:

  • FaceScan input.
  • BodyScan input.
  • setMultiScanPersistenceDelegate results.

Summary

The example apps demonstrate how to use the primary functionality of the AHI MultiScan SDK and how to rapidly integrate that functionality into your app.

We hope you had a smooth integration experience and we welcome any feedback you might have on the example app, the SDK or this guide.