Skip to main content
Flutter

Integrate the Human Token Journey into your Flutter app using Platform Channels.

Markdown

The integration uses MethodChannel to delegate to ASWebAuthenticationSession on iOS and Custom Tabs on Android. No external packages are needed.

Prerequisites​

  • Flutter 3.x or later
  • Xcode 14+ for iOS
  • Android Studio for Android
  • A unique URL scheme for your app (e.g. myapp)

Step 1: Define your URL scheme​

Choose a unique scheme (e.g. myapp). This is used as the callbackUri when creating the process:

callbackUri: myapp://callback

Step 2: Create the Dart service​

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

class UnicoService {
static const _channel = MethodChannel('unico_plugin');

Future<String?> openSession(String url) async {
try {
final result = await _channel.invokeMethod<String>('openSession', {'url': url});
if (result != null) {
final uri = Uri.parse(result);
return uri.queryParameters['id'];
}
return null;
} on PlatformException {
return null;
}
}
}

Step 3: Implement the iOS plugin​

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

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

static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "unico_plugin", binaryMessenger: registrar.messenger())
let instance = UnicoPlugin()
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
}
}

Register in AppDelegate.swift:

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

Step 4: Implement the Android plugin​

Add the dependency to android/app/build.gradle:

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

import android.content.Intent
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 UnicoPlugin : 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, "unico_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 }
}

Register in MainActivity.kt and handle redirects:

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

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

Step 5: Configure URL schemes​

Add to Info.plist:

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

Step 6: Launch the journey​

final unicoService = UnicoService();
final processId = await unicoService.openSession(processLink);

if (processId != null) {
// Journey completed — send processId to your backend via Get Process
} else {
// User canceled
}

File summary​

FilePurpose
lib/services/unico_service.dartDart MethodChannel service
ios/Runner/UnicoPlugin.swiftiOS — ASWebAuthenticationSession
ios/Runner/AppDelegate.swiftPlugin registration
android/.../UnicoPlugin.ktAndroid — Custom Tabs
android/.../MainActivity.ktPlugin registration + redirect handling
Info.plistiOS URL scheme
AndroidManifest.xmlAndroid intent filter