메인 콘텐츠로 건너뛰기
Flutter 캡처

플랫폼 채널을 사용해 Flutter 앱에서 캡처를 엽니다.

Markdown

백엔드가 Journey 플로우와 앱의 URL 스킴을 사용하는 callbackUri로 Human Plugin에서 프로세스를 생성합니다. MethodChannel이 응답의 link를 iOS에서는 ASWebAuthenticationSession, Android에서는 Custom Tabs로 엽니다. 외부 패키지는 필요하지 않습니다.

생성 응답의 Human Plugin id를 보관하세요. 백엔드가 이 값으로 결과를 읽습니다.

사전 요구 사항​

  • Flutter 3 이상
  • iOS용 Xcode 14 이상
  • Android용 Android Studio
  • 앱 고유의 URL 스킴(예: myapp)

1. 스킴을 지정해 프로세스 생성​

백엔드가 스킴을 callbackUri로 전송합니다.

{
"flow": "humantoken",
"clientId": "client-a",
"correlationId": "order-1001",
"credential": { "type": "DUI_TYPE_NG_BVN", "value": "<document number>" },
"callbackUri": "myapp://callback"
}

그리고 id와 link를 앱에 반환합니다.

2. Dart 서비스 생성​

// lib/services/capture_service.dart
import 'package:flutter/services.dart';

class CaptureService {
static const _channel = MethodChannel('capture_plugin');

/// 캡처가 끝나면 true, 사용자가 닫으면 false를 반환합니다.
Future<bool> open(String link) async {
try {
final result = await _channel.invokeMethod<String>('openSession', {'url': link});
return result != null;
} on PlatformException {
return false;
}
}
}

3. iOS 플러그인 구현​

// ios/Runner/CapturePlugin.swift
import Flutter
import AuthenticationServices

class CapturePlugin: NSObject, FlutterPlugin, ASWebAuthenticationPresentationContextProviding {
private var session: ASWebAuthenticationSession?

static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "capture_plugin", binaryMessenger: registrar.messenger())
let instance = CapturePlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}

func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
guard call.method == "openSession",
let args = call.arguments as? [String: Any],
let urlString = args["url"] as? String,
let url = URL(string: urlString) else {
result(FlutterMethodNotImplemented)
return
}

let newSession = ASWebAuthenticationSession(
url: url,
callbackURLScheme: "myapp"
) { callbackURL, error in
self.session = nil
if let callbackURL = callbackURL {
result(callbackURL.absoluteString)
} else {
result(FlutterError(code: "SESSION_ERROR",
message: error?.localizedDescription ?? "Session cancelled",
details: nil))
}
}
newSession.presentationContextProvider = self
newSession.prefersEphemeralWebBrowserSession = true
newSession.start()
session = newSession
}

func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = windowScene.windows.first else {
return ASPresentationAnchor()
}
return window
}
}

AppDelegate.swift에 등록합니다.

CapturePlugin.register(with: self.registrar(forPlugin: "CapturePlugin")!)

4. Android 플러그인 구현​

android/app/build.gradle에 의존성을 추가합니다.

implementation("androidx.browser:browser:1.5.0")
// android/app/src/main/kotlin/<your.package>/CapturePlugin.kt
package com.yourapp

import android.net.Uri
import androidx.browser.customtabs.CustomTabsIntent
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel

class CapturePlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware {
private var channel: MethodChannel? = null
private var binding: ActivityPluginBinding? = null
private var pendingResult: MethodChannel.Result? = null

override fun onAttachedToEngine(flutterBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterBinding.binaryMessenger, "capture_plugin")
channel?.setMethodCallHandler(this)
}

override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
if (call.method == "openSession") {
val url = call.argument<String>("url") ?: return result.error("MISSING_URL", "URL is required", null)
pendingResult = result
val activity = binding?.activity ?: return result.error("NO_ACTIVITY", "No activity", null)
CustomTabsIntent.Builder().build().launchUrl(activity, Uri.parse(url))
} else {
result.notImplemented()
}
}

fun handleRedirect(uri: Uri) {
if (uri.scheme == "myapp") {
pendingResult?.success(uri.toString())
pendingResult = null
}
}

override fun onAttachedToActivity(binding: ActivityPluginBinding) { this.binding = binding }
override fun onDetachedFromActivity() { binding = null }
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { this.binding = binding }
override fun onDetachedFromActivityForConfigChanges() { binding = null }
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { channel = null }
}

MainActivity.kt에서 등록하고 복귀를 처리합니다.

override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngine.plugins.add(CapturePlugin())
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
intent.data?.let { uri ->
if (uri.scheme == "myapp") {
val plugin = flutterEngine?.plugins?.get(CapturePlugin::class.java) as? CapturePlugin
plugin?.handleRedirect(uri)
}
}
}

5. 스킴 등록​

Info.plist에 추가합니다.

<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>

6. 캡처 시작​

final ended = await CaptureService().open(link);

if (ended) {
// 캡처가 끝났습니다. 백엔드에 Human Plugin id로
// GET /v1/processes/{id}를 호출해 결과를 읽도록 요청하세요.
} else {
// 사용자가 세션을 닫았습니다.
}

요약​

파일용도
lib/services/capture_service.dartDart MethodChannel 서비스
ios/Runner/CapturePlugin.swiftiOS: ASWebAuthenticationSession
ios/Runner/AppDelegate.swift플러그인 등록
android/.../CapturePlugin.ktAndroid: Custom Tabs
android/.../MainActivity.kt플러그인 등록 및 복귀 처리
Info.plistiOS URL 스킴
AndroidManifest.xmlAndroid 인텐트 필터