We recommend integrating the standard native SDK directly, following the integration documentation provided for iOS platform.
Why should we integrate the standard native SDK instead of the SDK wrapped by the KMM multi platform library? There are several reasons:
The code rules and architecture of KMM require that native UI-related parts should be handled separately in each native platform of the code, while the Liveness SDK is strongly related to the native UI part.
After the Liveness SDK is finished, the app usually processes the response on the UI according to the result of the Liveness SDK (success or failure). Therefore, this part of the code also needs to be implemented separately in each native platform (because it is related to the UI).
In fact, the Liveness SDK itself has almost no additional logic that can be separated and included in the KMM shared moudle.
KMM's support for CocoaPods
is not very good, and even has some bugs. In order to allow KMM to integrate the iOS SDK by configure build.gradle.kts
, some workarounds have to be made. These workarounds make it complicated to integrate the iOS SDK, whereas integrating the native iOS SDK directly is much easier.
In summary, the Liveness SDK is not suitable for or cannot be fully wrapped into the multi-platform library required by KMM, and direct integration of native SDK is more appropriate.
Nevertheless, we still try to wrap the native SDK to allow the APIs of the native SDK to be called on the kotlin side, and provide a demo project for reference only. The native SDK on the Android side does not need to do additional processing, since it is language matching, KMM can call the APIs of the SDK directly, but the iOS side needs to do extra processing, and then the KMM side can call the iOS native SDK API in the kotlin way.
Please note that the following configurations apply only to the iOS side.
Add the pod dependencies configuration to build.gradle.kts
in the shared
folder:
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods")
id("com.android.library")
}
kotlin {
android {
compilations.all {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
iosX64()
iosArm64()
iosSimulatorArm64()
cocoapods {
summary = "Some description for the Shared Module"
homepage = "Link to the Shared Module homepage"
version = "1.0"
ios.deploymentTarget = "14.1"
podfile = project.file("../iosApp/Podfile")
framework {
baseName = "shared"
}
// Add pod dependencies
// Note you need to copy the 'podDependencies' folder to the root of your KMM project
pod("AAILivenessSDK") {
source = path(project.file("../podDependencies/AAILiveness/AAILivenessSDK"))
}
pod("AAILivenessUI") {
source = path(project.file("../podDependencies/AAILiveness/AAILivenessUI"))
}
pod("AAINetwork") {
source = path(project.file("../podDependencies/AAILiveness/AAINetwork"))
}
}
sourceSets {
...
}
}
android {
namespace = "com.example.mykmmapplication02"
compileSdk = 33
defaultConfig {
minSdk = 24
targetSdk = 33
}
}
Add the following configuration to the Podfile
of the iOS native project(iosApp/Podfile
) to solve the strange issue that the pod module cannot be found:
We test found that if you only add the pod configuration in
build.gradle.kts
, the pod modulesAAINetwork
,AAILivenessUI
,AAILivenessSDK
cannot be found correctly during compilation, but if you configure it again in the Podfile, this issue will be resolved.
target 'iosApp' do
use_frameworks!
platform :ios, '14.1'
pod 'shared', :path => '../shared'
# Add pod dependencies
# Note you need to copy the 'podDependencies' folder to the root of your KMM project
pod 'AAINetwork', :path => '../podDependencies/AAILiveness/AAINetwork'
pod 'AAILivenessUI', :path => '../podDependencies/AAILiveness/AAILivenessUI'
pod 'AAILivenessSDK', :path => '../podDependencies/AAILiveness/AAILivenessSDK'
end
Add the swift codes to initialize and display the SDK page in your iOS project:
import SwiftUI
import shared
import AAILivenessSDK
import AAILivenessUI
struct ContentView: View {
let livenessSDK = LivenessSDKManager.companion.sharedInstance
let greet = Greeting().greet()
@State var isPresented = false
@State var livenssResultStr = ""
var body: some View {
Text(greet)
Text(livenssResultStr)
Button("Show Liveness Page(\(AAILivenessSDK.sdkVersion()))") {
showLivenessSDK()
}.fullScreenCover(isPresented: $isPresented) {
AAILivenessRepresentedView()
}
}
func showLivenessSDK() {
let license = "your-license-string"
// There are two ways to using the SDK, both of which are supported.
// Way 1 is to use the native SDK method. (Recommended)
// <Step1>: Initialize the SDK
// If you want to do additional settings,
// please refer to the native sdk documentation
// https://doc.advance.ai/sdk/ios/liveness/en/standard.html#usage
// Sample code as follows:
let market: AAILivenessMarket = .indonesia
AAILivenessSDK.initWith(market, isGlobalService: false)
AAILivenessSDK.additionalConfig().detectionLevel = .easy
// <Step2>: Listen detection result
livenessSDK.setDetectionResultListener { jsonResult in
// <Step4>: Process detection result
// Convert result json data to AAILivenessRepresentedView.Response instance
if let result = try? JSONDecoder().decode(AAILivenessRepresentedView.Response.self, from: jsonResult.data(using: .utf8)!) {
if result.isSuccess {
// Update UI
livenssResultStr = "Detection succeed! \nLivenessId: \(result.livenessId ?? "")"
} else {
// Update UI
livenssResultStr = "Detection failed! \nerrorCode: \(result.code). \nerrorMsg: \(result.message). \ntransactionId: \(result.transactionId)"
}
}
}
// <Step3>: Check whether the license is valid
let result = AAILivenessSDK.configLicenseAndCheck(license)
if result == "SUCCESS" {
// <Step4>: Show liveness page
isPresented = true
} else {
// Update UI
livenssResultStr = "License check failed: \(result)"
}
/*
// Way 2 is to use the SDK api wrapped by kotlin. Sample code as follows:
// <Step1>: Initialize the SDK
// Note "marketStr" is case-insensitive
livenessSDK.doInitSDK(marketStr: "Indonesia", isGlobalService: false)
// Note "levelStr" is case-insensitive
livenessSDK.setDetectionLevel(levelStr: "Easy")
// <Step2>: Listen detection result
livenessSDK.setDetectionResultListener { jsonResult in
// <Step4>: Process detection result
// Convert result json data to AAILivenessRepresentedView.Response instance
if let result = try? JSONDecoder().decode(AAILivenessRepresentedView.Response.self, from: jsonResult.data(using: .utf8)!) {
if result.isSuccess {
// Update UI
livenssResultStr = "Detection succeed! \nLivenessId: \(result.livenessId ?? "")"
} else {
// Update UI
livenssResultStr = "Detection failed! \nerrorCode: \(result.code). \nerrorMsg: \(result.message). \ntransactionId: \(result.transactionId)"
}
}
}
// <Step3>: Check whether the license is valid
let result = livenessSDK.setLicenseAndCheck(license: license)
if result == "SUCCESS" {
// <Step4>: Show liveness page
isPresented = true
} else {
livenssResultStr = "License check failed: \(result)"
}
*/
}
}
// ---- The following is the fixed liveness SDK code ----
struct AAILivenessRepresentedView: UIViewControllerRepresentable {
struct Response: Codable {
let isSuccess: Bool
let livenessId: String?
let base64Image: String?
let code: String
let message: String
let transactionId: String
}
typealias UIViewControllerType = UINavigationController
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
func makeUIViewController(context: Context) -> UINavigationController {
let vc = AAILivenessViewController()
vc.recordUserGiveUp = true
// If you want to do additional configuration for this vc,
// please refer to the native sdk documentation
// https://doc.advance.ai/sdk/ios/liveness/en/standard.html#usage
vc.detectionSuccessBlk = {(rawVC, result) in
// Convert result info to json string
let base64ImageStr = result.img.jpegData(compressionQuality: 1)?.base64EncodedString() ?? ""
let resp = Response(isSuccess: true,
livenessId: result.livenessId,
base64Image: base64ImageStr,
code: "SUCCESS",
message: "SUCCESS",
transactionId: result.transactionId ?? "")
var respJsonStr = ""
if let data = try? JSONEncoder().encode(resp.self) {
respJsonStr = String(data: data, encoding: .utf8) ?? ""
}
rawVC.presentingViewController?.dismiss(animated: true, completion: {
LivenessSDKManager.companion.sharedInstance.receiveDetectionResult(resultJsonStr: respJsonStr)
})
}
vc.detectionFailedBlk = {(rawVC, errorInfo) in
// Convert error info to json string
let failedResult = AAILivenessFailedResult(errorInfo: errorInfo)
let resp = Response(isSuccess: false,
livenessId: nil,
base64Image: nil,
code: failedResult.errorCode,
message: failedResult.errorMsg,
transactionId: failedResult.transactionId)
var respJsonStr = ""
if let data = try? JSONEncoder().encode(resp.self) {
respJsonStr = String(data: data, encoding: .utf8) ?? ""
}
rawVC.presentingViewController?.dismiss(animated: true, completion: {
LivenessSDKManager.companion.sharedInstance.receiveDetectionResult(resultJsonStr: respJsonStr)
})
}
let navc = UINavigationController(rootViewController: vc)
navc.isNavigationBarHidden = true
return navc
}
}
// ---- Liveness SDK fixed code END ----
Add relevant kotlin codes(LivenessSDK
and LivenessSDKManager
), refer to the demo project for details.
Code | Explanation |
---|---|
PREPARE_TIMEOUT | Timeout during the preparation stage |
ACTION_TIMEOUT | Timeout during the motion stage |
MUTI_FACE | Multiple faces detected during the motion stage |
FACE_MISSING | Face is missing during the motion stage |
MUCH_ACTION | Multiple motions detected during the motion stage |
USER_GIVE_UP | The user clicked the top left back button during the detection process |
NO_RESPONSE | Network request failed |
UNDEFINED | Other undefined errors |
...(Other server side error codes) | / |
podDependencies
folder of your project with podDependencies
folder from the demo project.pod install
.