Device binding in Dart/Flutter
Dart can call native APIs via message passing. Let's call a function called generateKey with the parameter
{'alias': 'my_key_01'}:
Future<void> _generateKey() async {
setState(() => _isLoading = true);
try {
// Calling the native method
final String result = await platform.invokeMethod('generateKey', {
'alias': 'my_key_01',
});
setState(() {
_keyStoreResult = result;
_isLoading = false;
});
} on PlatformException catch (e) {
setState(() {
_keyStoreResult = "Failed to generate key: '${e.message}'.";
_isLoading = false;
});
}
}
Since the call might block, it is marked async and a loading indicator is shown in the UI via the _isLoading field.
Now to the platform code, for example for Android:
class MainActivity: FlutterActivity() {
private val CHANNEL = "com.example.secure/keystore"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "generateKey") {
val alias = call.argument<String>("alias") ?: "default_alias"
try {
val keyStoreResult = [..] // Call the KeyStore here.
// Send the result back to Flutter.
result.success(keyStoreResult)
} catch (e: Exception) {
// If generation fails (e.g., hardware issues), send an error
result.error("KEY_GEN_FAIL", e.localizedMessage, null)
}
} else {
result.notImplemented()
}
}
}
}
And for iOS:
import UIKit
import Flutter
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// 1. Standard plugin registration for things like path_provider, etc.
GeneratedPluginRegistrant.register(with: self)
// 2. Create a registrar for our custom "inline" plugin
// The name "SecureKeystorePlugin" can be anything unique.
let registrar = self.registrar(forPlugin: "SecureKeystorePlugin")
// 3. Setup the channel using the registrar's messenger
let channel = FlutterMethodChannel(
name: "com.example.secure/keystore",
binaryMessenger: registrar!.messenger()
)
// 4. Handle the method calls
channel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
if call.method == "generateResidentKey" {
let alias = (call.arguments as? [String: Any])?["alias"] as? String ?? "unknown"
// Just for the example, get the iOS version.
result("iOS \(version)")
} else {
result(FlutterMethodNotImplemented)
}
})
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
And the Flutter code gets this result back: iOS 26.2.1 (for example).