본문으둜 κ±΄λ„ˆλ›°κΈ°

πŸ“ž 콜백 μ²˜λ¦¬ν•˜κΈ°

Test Solution SDKλŠ” ν…ŒμŠ€νŠΈ μ§„ν–‰ 쀑 λ°œμƒν•˜λŠ” μ£Όμš” μ΄λ²€νŠΈλ‚˜ 둜그 데이터λ₯Ό Host μ•±μœΌλ‘œ λΉ„λ™κΈ°μ μœΌλ‘œ μ „λ‹¬ν•©λ‹ˆλ‹€. 이 톡신은 콜백(Callback) λ§€μ»€λ‹ˆμ¦˜μ„ 톡해 μ΄λ£¨μ–΄μ§‘λ‹ˆλ‹€.

Host μ•±μ—μ„œλŠ” onResult, onLog, onInfo μ„Έ κ°€μ§€ μ½œλ°±μ„ 톡해 SDK의 μƒνƒœλ₯Ό μˆ˜μ‹ ν•˜κ³  ν•„μš”ν•œ 후속 쑰치λ₯Ό μ·¨ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


onResult μ½œλ°±β€‹

onResultλŠ” ν…ŒμŠ€νŠΈμ˜ 핡심적인 μƒνƒœ 변경이 λ°œμƒν–ˆμ„ λ•Œ ν˜ΈμΆœλ©λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, μ‚¬μš©μžκ°€ λͺ¨λ“  문항을 μ™„λ£Œν–ˆκ±°λ‚˜, 쀑간에 ν…ŒμŠ€νŠΈλ₯Ό μ’…λ£Œν–ˆκ±°λ‚˜, λ˜λŠ” 예기치 μ•Šμ€ 였λ₯˜κ°€ λ°œμƒν•œ κ²½μš°κ°€ ν•΄λ‹Ήλ©λ‹ˆλ‹€.

⚠️ μ€‘μš”: onResult μ½œλ°±μ€ 성격 μœ ν˜• 뢄석 κ²°κ³Όλ₯Ό λ°˜ν™˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. ν…ŒμŠ€νŠΈμ˜ μ™„λ£Œ 여뢀와 같은 μƒνƒœ 값을 μ „λ‹¬ν•˜λŠ” μš©λ„μž…λ‹ˆλ‹€.

데이터 ꡬ쑰​

onResultλŠ” μ•„λž˜μ™€ 같은 ꡬ쑰의 JSON 객체(λ˜λŠ” 각 ν”Œλž«νΌμ— λ§žλŠ” Map/Dictionary ν˜•νƒœ)λ₯Ό μ „λ‹¬ν•©λ‹ˆλ‹€.

ν•„λ“œλͺ…νƒ€μž…μ„€λͺ…
statusStringν•„μˆ˜. 콜백의 원인을 λ‚˜νƒ€λ‚΄λŠ” μƒνƒœ μ½”λ“œμž…λ‹ˆλ‹€. ('COMPLETED', 'USERCANCELLED', 'ERROR')
dataObjectν•„μˆ˜. μƒνƒœμ— λŒ€ν•œ μ‚¬λžŒμ΄ 읽을 수 μžˆλŠ” λ©”μ‹œμ§€μž…λ‹ˆλ‹€. λ””λ²„κΉ…μ΄λ‚˜ μ‚¬μš©μž μ•ˆλ‚΄μ— ν™œμš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
sdkVersionStringν•„μˆ˜. SDK의 버전 μ •λ³΄μž…λ‹ˆλ‹€.
customDataObjectν•„μˆ˜. startTest 호좜 μ‹œ μ „λ‹¬ν–ˆλ˜ customData 객체λ₯Ό κ·ΈλŒ€λ‘œ λ°˜ν™˜ν•©λ‹ˆλ‹€. Host μ•±μ˜ μ„Έμ…˜ 정보 등을 μœ μ§€ν•˜λŠ” 데 μœ μš©ν•©λ‹ˆλ‹€.

μƒνƒœ(Status) μ½”λ“œ μ’…λ₯˜β€‹

  • 'COMPLETED': μ‚¬μš©μžκ°€ 180개 문항을 λͺ¨λ‘ μ™„λ£Œν•˜κ³  SDKκ°€ μ •μƒμ μœΌλ‘œ μ’…λ£Œλ˜μ—ˆμŒμ„ μ˜λ―Έν•©λ‹ˆλ‹€.
  • 'USERCANCELLED': μ‚¬μš©μžκ°€ ν…ŒμŠ€νŠΈλ₯Ό μ™„λ£Œν•˜κΈ° 전에 μ˜λ„μ μœΌλ‘œ 'λ‚˜κ°€κΈ°'와 같은 UIλ₯Ό 톡해 μ’…λ£Œν–ˆμŒμ„ μ˜λ―Έν•©λ‹ˆλ‹€.
  • 'ERROR': μœ νš¨ν•˜μ§€ μ•Šμ€ νŒŒλΌλ―Έν„° 전달, λ„€νŠΈμ›Œν¬ 였λ₯˜ λ“± 예기치 μ•Šμ€ 문제둜 ν…ŒμŠ€νŠΈκ°€ μ€‘λ‹¨λ˜μ—ˆμŒμ„ μ˜λ―Έν•©λ‹ˆλ‹€.

데이터 μ˜ˆμ‹œβ€‹

1. ν…ŒμŠ€νŠΈ 정상 μ™„λ£Œ μ‹œ

{
"status": "COMPLETED",
"data": {
"suid": "[SUID]",
"goodsId": "[GOODS_ID]",
"language": "[LANGUAGE]",
"completedAt": "2025-01-01T00:00:00.000000"
},
"sdkVersion": "1.0.0+10000",
"customData": { "internalUserId": "user-1234" }
}

2. 였λ₯˜ λ°œμƒ μ‹œ

{
"status": "USERCANCELLED",
"data": { "errorCode": "N005", "errorMessage": "DATA_PARSE_FAILED" },
"sdkVersion": "1.0.0+10000",
"customData": { "internalUserId": "user-1234" }
}

onLog μ½œλ°±β€‹

onLogλŠ” SDK λ‚΄λΆ€μ˜ 상세 λ™μž‘ 둜그λ₯Ό Host μ•±μœΌλ‘œ μ „λ‹¬ν•˜κΈ° μœ„ν•œ λ””λ²„κΉ…μš© μ½œλ°±μž…λ‹ˆλ‹€. 이 μ½œλ°±μ€ startTest 호좜 μ‹œ isDevelopment 와 enableHostLogging 값을 true둜 μ„€μ •ν•΄μ•Όλ§Œ ν™œμ„±ν™”λ©λ‹ˆλ‹€.

πŸ’‘ Tip: 개발 및 ν…ŒμŠ€νŠΈ λ‹¨κ³„μ—μ„œλŠ” enableHostLogging을 ν™œμ„±ν™”ν•˜μ—¬ 문제의 원인을 λΉ λ₯΄κ²Œ νŒŒμ•…ν•˜κ³ , ν”„λ‘œλ•μ…˜(릴리즈) λ²„μ „μ—μ„œλŠ” λΉ„ν™œμ„±ν™”ν•˜λŠ” 것을 ꢌμž₯ν•©λ‹ˆλ‹€.

데이터 ꡬ쑰​

ν•„λ“œλͺ…νƒ€μž…μ„€λͺ…
levelStringν•„μˆ˜. 둜그 λ ˆλ²¨μž…λ‹ˆλ‹€. ('INFO', 'WARNING', 'ERROR')
messageStringν•„μˆ˜. μ‹€μ œ 둜그 λ©”μ‹œμ§€ λ‚΄μš©μž…λ‹ˆλ‹€.
errorString였λ₯˜ μ •λ³΄μž…λ‹ˆλ‹€.
timestampStringν•„μˆ˜. λ‘œκ·Έκ°€ λ°œμƒν•œ μ‹œκ° (ISO 8601 ν˜•μ‹)

데이터 μ˜ˆμ‹œβ€‹

{
"level": "INFO",
"message": "개발 λͺ¨λ“œλ‘œ μ„€μ • λ˜μ—ˆμŠ΅λ‹ˆλ‹€.",
"error": null,
"timestamp": "2025-01-01T00:00:00.000000"
}

onInfo μ½œλ°±β€‹

onInfoλŠ” SDK의 버전 정보λ₯Ό Host μ•±μœΌλ‘œ μ „λ‹¬ν•˜κΈ° μœ„ν•œ μ½œλ°±μž…λ‹ˆλ‹€.

데이터 ꡬ쑰​

ν•„λ“œλͺ…νƒ€μž…μ„€λͺ…
versionStringν•„μˆ˜. SDK 버전 μ •λ³΄μž…λ‹ˆλ‹€.

데이터 μ˜ˆμ‹œβ€‹

{
"version": "1.0.0+10000"
}

κ΅¬ν˜„ μ˜ˆμ œβ€‹

μ•„λž˜λŠ” 각 λ„€μ΄ν‹°λΈŒ ν”Œλž«νΌμ—μ„œ μ½œλ°±μ„ μˆ˜μ‹ ν•˜κ³  μ²˜λ¦¬ν•˜λŠ” μ½”λ“œ μ˜ˆμ‹œμž…λ‹ˆλ‹€.

πŸ€– Android (Kotlin)​

Flutterμ™€μ˜ 톡신을 μœ„ν•΄ MethodChannel을 μ„€μ •ν•˜κ³  setMethodCallHandlerλ₯Ό 톡해 μ½œλ°±μ„ λ¦¬μŠ€λ‹ν•©λ‹ˆλ‹€.

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity: FlutterActivity() {
private val CHANNEL = "com.mscbrain.sdk.test_solution_sdk/channel"

override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)

val channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)

channel.setMethodCallHandler { call, result ->
when (call.method) {
"onResult" -> {
val resultMap = call.arguments as? Map<String, Any>
val status = resultMap?.get("status") as? String

Log.d("TestSolutionSDK", "onResult received: $resultMap")

when (status) {
"COMPLETED" -> {
// ν…ŒμŠ€νŠΈ μ™„λ£Œ ν›„ 처리 둜직
showCompletionDialog()
}
"USERCANCELLED" -> {
// μ‚¬μš©μž μ΄νƒˆ 처리 둜직
}
"ERROR" -> {
// 였λ₯˜ 처리 둜직
val data = resultMap?.get("data") as? Map<*, *>?
val errorMsg = data?.get("errorMessage") as? String
showErrorToast(errorMsg)
}
}
}
"onInfo" -> {
val versionMap = call.arguments as? Map<String, Any>
Log.d("TestSolutionSDK_VERSION", "$versionMap") // 버전 정보 좜λ ₯
result.success(null)
}
"onLog" -> {
val logMap = call.arguments as? Map<String, Any>
Log.d("TestSolutionSDK_LOG", "$logMap") // 디버그 둜그 좜λ ₯
}
else -> {
result.notImplemented()
}
}
}

// ...
}
// ...
}

🍎 iOS (Swift)​

AppDelegate λ˜λŠ” 화면을 ν‘œμ‹œν•˜λŠ” UIViewControllerμ—μ„œ FlutterMethodChannel을 μ„€μ •ν•˜μ—¬ μ½œλ°±μ„ μ²˜λ¦¬ν•©λ‹ˆλ‹€.

ViewController.swift
import UIKit
import Flutter

class ViewController: UIViewController {

private let CHANNEL: String = "com.mscbrain.sdk.test_solution_sdk/channel"

override func viewDidLoad() {
super.viewDidLoad()

guard let flutterEngine = (UIApplication.shared.delegate as? AppDelegate)?.flutterEngine else { return }
let channel = FlutterMethodChannel(name: CHANNEL, binaryMessenger: flutterEngine.binaryMessenger)

channel.setMethodCallHandler({(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in

switch call.method {
case "onResult":
guard let resultMap = call.arguments as? [String: Any],
let status = resultMap["status"] as? String else {
return
}

print("onResult received: \(resultMap)")

if status == "COMPLETED" {
// ν…ŒμŠ€νŠΈ μ™„λ£Œ ν›„ 처리 둜직
} else if status == "ERROR" {
// 였λ₯˜ 처리 둜직
}
break

case "onInfo":
guard let versionMap = call.arguments as? [String: Any] else { return }
print("TestSolutionSDK_VERSION: \(versionMap)") // 버전 정보 좜λ ₯
break

case "onLog":
guard let logMap = call.arguments as? [String: Any] else { return }
print("TestSolutionSDK_LOG: \(logMap)") // 디버그 둜그 좜λ ₯
break

default:
result(FlutterMethodNotImplemented)
}
})
}
// ...
}