Skip to content

feat: job status polling #73

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Release Notes

## 10.1.0

* Introduced polling methods for products
* Smartselfie
* Biometric kyc
* Document verification
* Enhanced document verification

## 10.0.12

* Fixed a bug where SmartSelfieEnrollment and SmartSelfieAuthentication would return invalid `livenessImages` in `onSuccess`
Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group "com.smileidentity.flutter"
version findProperty("SDK_VERSION") ?: "10.0.4"
version findProperty("SDK_VERSION") ?: "10.1.2"

buildscript {
ext.kotlin_version = "1.9.10"
Expand Down
100 changes: 93 additions & 7 deletions android/src/main/kotlin/com/smileidentity/flutter/SmileIDPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,23 @@ import SmileIDApi
import android.app.Activity
import android.content.Context
import com.smileidentity.SmileID
import com.smileidentity.networking.pollBiometricKycJobStatus
import com.smileidentity.networking.pollDocumentVerificationJobStatus
import com.smileidentity.networking.pollEnhancedDocumentVerificationJobStatus
import com.smileidentity.networking.pollSmartSelfieJobStatus
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.single
import kotlinx.coroutines.launch
import java.net.URL
import kotlin.time.Duration.Companion.seconds

class SmileIDPlugin : FlutterPlugin, SmileIDApi, ActivityAware {

private var activity: Activity? = null
private lateinit var appContext: Context

Expand Down Expand Up @@ -172,10 +178,89 @@ class SmileIDPlugin : FlutterPlugin, SmileIDApi, ActivityAware {
callback = callback,
)

override fun getServices(
callback: (Result<FlutterServicesResponse>) -> Unit,
override fun getServices(callback: (Result<FlutterServicesResponse>) -> Unit) =
launch(
work = { SmileID.api.getServices().toResponse() },
callback = callback,
)

override fun pollSmartSelfieJobStatus(
request: FlutterJobStatusRequest,
interval: Long,
numAttempts: Long,
callback: (Result<FlutterSmartSelfieJobStatusResponse>) -> Unit,
) = launch(
work = {
SmileID.api.pollSmartSelfieJobStatus(
request.toRequest(),
interval.seconds,
numAttempts.toInt(),
)
.map { smartSelfieJobStatusResponse ->
smartSelfieJobStatusResponse.toResponse()
}
.single()
},
callback = callback,
)

override fun pollDocumentVerificationJobStatus(
request: FlutterJobStatusRequest,
interval: Long,
numAttempts: Long,
callback: (Result<FlutterDocumentVerificationJobStatusResponse>) -> Unit,
) = launch(
work = {
SmileID.api.pollDocumentVerificationJobStatus(
request.toRequest(),
interval.seconds,
numAttempts.toInt(),
)
.map { documentVerificationJobStatusReponse ->
documentVerificationJobStatusReponse.toResponse()
}
.single()
},
callback = callback,
)

override fun pollBiometricKycJobStatus(
request: FlutterJobStatusRequest,
interval: Long,
numAttempts: Long,
callback: (Result<FlutterBiometricKycJobStatusResponse>) -> Unit,
) = launch(
work = {
SmileID.api.pollBiometricKycJobStatus(
request.toRequest(),
interval.seconds,
numAttempts.toInt(),
)
.map { biometricJobStatusResponse ->
biometricJobStatusResponse.toResponse()
}
.single()
},
callback = callback,
)

override fun pollEnhancedDocumentVerificationJobStatus(
request: FlutterJobStatusRequest,
interval: Long,
numAttempts: Long,
callback: (Result<FlutterEnhancedDocumentVerificationJobStatusResponse>) -> Unit,
) = launch(
work = { SmileID.api.getServices().toResponse() },
work = {
SmileID.api.pollEnhancedDocumentVerificationJobStatus(
request.toRequest(),
interval.seconds,
numAttempts.toInt(),
)
.map { enhancedDocumentVerificationJobStatus ->
enhancedDocumentVerificationJobStatus.toResponse()
}
.single()
},
callback = callback,
)

Expand Down Expand Up @@ -205,9 +290,10 @@ private fun <T> launch(
callback: (Result<T>) -> Unit,
scope: CoroutineScope = CoroutineScope(Dispatchers.IO),
) {
val handler = CoroutineExceptionHandler { _, throwable ->
callback.invoke(Result.failure(throwable))
}
val handler =
CoroutineExceptionHandler { _, throwable ->
callback.invoke(Result.failure(throwable))
}
scope.launch(handler) {
callback.invoke(Result.success(work()))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1859,6 +1859,10 @@ interface SmileIDApi {
fun getProductsConfig(request: FlutterProductsConfigRequest, callback: (Result<FlutterProductsConfigResponse>) -> Unit)
fun getValidDocuments(request: FlutterProductsConfigRequest, callback: (Result<FlutterValidDocumentsResponse>) -> Unit)
fun getServices(callback: (Result<FlutterServicesResponse>) -> Unit)
fun pollSmartSelfieJobStatus(request: FlutterJobStatusRequest, interval: Long, numAttempts: Long, callback: (Result<FlutterSmartSelfieJobStatusResponse>) -> Unit)
fun pollDocumentVerificationJobStatus(request: FlutterJobStatusRequest, interval: Long, numAttempts: Long, callback: (Result<FlutterDocumentVerificationJobStatusResponse>) -> Unit)
fun pollBiometricKycJobStatus(request: FlutterJobStatusRequest, interval: Long, numAttempts: Long, callback: (Result<FlutterBiometricKycJobStatusResponse>) -> Unit)
fun pollEnhancedDocumentVerificationJobStatus(request: FlutterJobStatusRequest, interval: Long, numAttempts: Long, callback: (Result<FlutterEnhancedDocumentVerificationJobStatusResponse>) -> Unit)

companion object {
/** The codec used by SmileIDApi. */
Expand Down Expand Up @@ -2161,6 +2165,94 @@ interface SmileIDApi {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.smileid.SmileIDApi.pollSmartSelfieJobStatus", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val requestArg = args[0] as FlutterJobStatusRequest
val intervalArg = args[1].let { if (it is Int) it.toLong() else it as Long }
val numAttemptsArg = args[2].let { if (it is Int) it.toLong() else it as Long }
api.pollSmartSelfieJobStatus(requestArg, intervalArg, numAttemptsArg) { result: Result<FlutterSmartSelfieJobStatusResponse> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.smileid.SmileIDApi.pollDocumentVerificationJobStatus", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val requestArg = args[0] as FlutterJobStatusRequest
val intervalArg = args[1].let { if (it is Int) it.toLong() else it as Long }
val numAttemptsArg = args[2].let { if (it is Int) it.toLong() else it as Long }
api.pollDocumentVerificationJobStatus(requestArg, intervalArg, numAttemptsArg) { result: Result<FlutterDocumentVerificationJobStatusResponse> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.smileid.SmileIDApi.pollBiometricKycJobStatus", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val requestArg = args[0] as FlutterJobStatusRequest
val intervalArg = args[1].let { if (it is Int) it.toLong() else it as Long }
val numAttemptsArg = args[2].let { if (it is Int) it.toLong() else it as Long }
api.pollBiometricKycJobStatus(requestArg, intervalArg, numAttemptsArg) { result: Result<FlutterBiometricKycJobStatusResponse> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.smileid.SmileIDApi.pollEnhancedDocumentVerificationJobStatus", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val requestArg = args[0] as FlutterJobStatusRequest
val intervalArg = args[1].let { if (it is Int) it.toLong() else it as Long }
val numAttemptsArg = args[2].let { if (it is Int) it.toLong() else it as Long }
api.pollEnhancedDocumentVerificationJobStatus(requestArg, intervalArg, numAttemptsArg) { result: Result<FlutterEnhancedDocumentVerificationJobStatusResponse> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
14 changes: 9 additions & 5 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ PODS:
- Flutter (1.0.0)
- integration_test (0.0.1):
- Flutter
- smile_id (10.0.11):
- lottie-ios (4.4.3)
- smile_id (10.1.0):
- Flutter
- SmileID (= 10.0.11)
- SmileID (10.0.11):
- SmileID (= 10.1.3)
- SmileID (10.1.3):
- lottie-ios (~> 4.4.2)
- Zip (~> 2.1.0)
- Zip (2.1.2)

Expand All @@ -16,6 +18,7 @@ DEPENDENCIES:

SPEC REPOS:
trunk:
- lottie-ios
- SmileID
- Zip

Expand All @@ -30,8 +33,9 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
integration_test: 13825b8a9334a850581300559b8839134b124670
smile_id: 82636e504fcef7d98dbffeb2b15ac51df9c0604a
SmileID: 93d6f6bee1b988d12c2dc8872e7113dbe9edd5a1
lottie-ios: fcb5e73e17ba4c983140b7d21095c834b3087418
smile_id: a17b29545bbd29dabe9279c4d5016e866993bede
SmileID: 61ae985152b559099730bf211b74c9fbbcc4e516
Zip: b3fef584b147b6e582b2256a9815c897d60ddc67

PODFILE CHECKSUM: 929954fb8941cef06249e96bd1516fd2a22ed7a5
Expand Down
1 change: 1 addition & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ class MainContent extends StatelessWidget {
body: SmileIDSmartSelfieEnrollment(
onSuccess: (String? result) {
// Your success handling logic
print("Japhet Ndhlovu is here with result $result");
final snackBar = SnackBar(content: Text("Success: $result"));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
Navigator.of(context).pop();
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ packages:
path: ".."
relative: true
source: path
version: "10.0.8"
version: "10.1.0"
source_span:
dependency: transitive
description:
Expand Down
9 changes: 4 additions & 5 deletions ios/Classes/SmileIDBiometricKYC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import SmileID
import SwiftUI

class SmileIDBiometricKYC : NSObject, FlutterPlatformView, BiometricKycResultDelegate {

private var _view: UIView
private var _channel: FlutterMethodChannel
private var _childViewController: UIViewController?
Expand Down Expand Up @@ -58,14 +59,12 @@ class SmileIDBiometricKYC : NSObject, FlutterPlatformView, BiometricKycResultDel
return _view
}

func didSucceed(selfieImage: URL, livenessImages: [URL], jobStatusResponse: BiometricKycJobStatusResponse) {
func didSucceed(selfieImage: URL, livenessImages: [URL], didSubmitBiometricJob: Bool) {
_childViewController?.removeFromParent()
let encoder = JSONEncoder()
let jsonData = try! encoder.encode(jobStatusResponse)
_channel.invokeMethod("onSuccess", arguments: """
{"selfieFile": "\(selfieImage.absoluteString)",
"livenessImages": "\(livenessImages.map{ $0.absoluteString })",
"jobStatusResponse": \(String(data: jsonData, encoding: .utf8)!)}
"livenessImages": \(livenessImages.map{ $0.absoluteString }),
"didSubmitBiometricJob": \(didSubmitBiometricJob),
""")
}

Expand Down
17 changes: 5 additions & 12 deletions ios/Classes/SmileIDDocumentVerification.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import SmileID
import SwiftUI

class SmileIDDocumentVerification : NSObject, FlutterPlatformView, DocumentVerificationResultDelegate {

private var _view: UIView
private var _channel: FlutterMethodChannel
private var _childViewController: UIViewController?
Expand Down Expand Up @@ -55,21 +56,13 @@ class SmileIDDocumentVerification : NSObject, FlutterPlatformView, DocumentVerif
return _view
}

func didSucceed(
selfie: URL,
documentFrontImage: URL,
documentBackImage: URL?,
jobStatusResponse: DocumentVerificationJobStatusResponse
) {
func didSucceed(selfie: URL, documentFrontImage: URL, documentBackImage: URL?, didSubmitDocumentVerificationJob: Bool) {
_childViewController?.removeFromParent()
let encoder = JSONEncoder()
let jsonData = try! encoder.encode(jobStatusResponse)
let documentBackFileJson = documentBackImage.map{ "\"\($0.absoluteString)\"" } ?? "null"
_channel.invokeMethod("onSuccess", arguments: """
{"selfieFile": "\(selfie.absoluteString)",
"documentFrontFile": "\(documentFrontImage.absoluteString)",
"documentBackFile": \(documentBackFileJson),
"jobStatusResponse": \(String(data: jsonData, encoding: .utf8)!)}
"documentFrontImage": \(documentFrontImage.absoluteString),
"documentBackImage": \(documentBackImage?.absoluteString ?? ""),
"didSubmitDocumentVerificationJob": \(didSubmitDocumentVerificationJob),
""")
}

Expand Down
11 changes: 11 additions & 0 deletions ios/Classes/SmileIDEnhancedDocumentVerification.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import SmileID
import SwiftUI

class SmileIDEnhancedDocumentVerification : NSObject, FlutterPlatformView, EnhancedDocumentVerificationResultDelegate {

private var _view: UIView
private var _channel: FlutterMethodChannel
private var _childViewController: UIViewController?
Expand Down Expand Up @@ -72,6 +73,16 @@ class SmileIDEnhancedDocumentVerification : NSObject, FlutterPlatformView, Enhan
"jobStatusResponse": \(String(data: jsonData, encoding: .utf8)!)}
""")
}

func didSucceed(selfie: URL, documentFrontImage: URL, documentBackImage: URL?, didSubmitEnhancedDocVJob: Bool) {
_childViewController?.removeFromParent()
_channel.invokeMethod("onSuccess", arguments: """
{"selfieFile": "\(selfie.absoluteString)",
"documentFrontImage": \(documentFrontImage.absoluteString),
"documentBackImage": \(documentBackImage?.absoluteString ?? ""),
"didSubmitEnhancedDocVJob": \(didSubmitEnhancedDocVJob),
""")
}

func didError(error: Error) {
print("[Smile ID] An error occurred - \(error.localizedDescription)")
Expand Down
Loading