Slide 1

Slide 1 text

Seamless Flutter Native Integration: FFI & Pigeon Dreamwalker (@jaichangpark) 1

Slide 2

Slide 2 text

/ Intro Proprietary & Confidential Dreamwalker Park Jai-Chang @jaichangpark @JAICHANGPARK Dreamus Company GDG Golang Korea / Flutter Seoul 2

Slide 3

Slide 3 text

Flutter Build for any screen Flutter is an open source framework for building beautiful, natively compiled, multi-platform applications from a single codebase. / overview 3

Slide 4

Slide 4 text

/ overview @source: https://dart.dev/overview 4

Slide 5

Slide 5 text

5

Slide 6

Slide 6 text

AI Artificial intelligence 6

Slide 7

Slide 7 text

AI on a smartphone 7

Slide 8

Slide 8 text

AI (On-Device LLM) on a smartphone 8

Slide 9

Slide 9 text

@source: https://docs.flutter.dev/resources/architectural-overview 9

Slide 10

Slide 10 text

@source: https://docs.flutter.dev/resources/architectural-overview 10

Slide 11

Slide 11 text

Platform channels ● Method Channel ○ A named channel for communicating with platform plugins using asynchronous method calls. ● Event Channel ○ A named channel for communicating with platform plugins using event streams. ● Codec ○ A message encoding/decoding mechanism. ■ MessageCodec ■ StandardMessageCodec ■ BinaryCodec ■ JSONMessageCodec ■ StringCodec 11 @source: https://docs.flutter.dev/resources/architectural-overview#integrating-with-other-code

Slide 12

Slide 12 text

FlutterActivity package com.dreamwalker.untitled import io.flutter.embedding.android.FlutterActivity class MainActivity: FlutterActivity() 12

Slide 13

Slide 13 text

FlutterActivity package com.dreamwalker.untitled import io.flutter.embedding.android.FlutterActivity class MainActivity: FlutterActivity(){ override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) // code ... } } 13

Slide 14

Slide 14 text

FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin class CustomFlutterPlugin : FlutterPlugin { override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { // Initialization and method channel setup for the plugin // code.. } override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { methodChannel.setMethodCallHandler(null) } } 14

Slide 15

Slide 15 text

FlutterActivity vs FlutterPlugin FlutterActivity FlutterActivity is an Android Activity that displays the Flutter application UI and starts the Flutter engine. You would use FlutterActivity when you want to show Flutter UI within an Android native application. FlutterPlugin FlutterPlugin is an interface for communication between Flutter and the native platform (e.g., Android, iOS). You would implement FlutterPlugin when you want to call native APIs or handle specific events in a Flutter application. 15

Slide 16

Slide 16 text

Platform-Specific Code Design 16

Slide 17

Slide 17 text

/ Platform-Specific Code Design ● plugin_platform_interface ● Pigeon ● FFI(Foreign function interface) → C ● FFIgen ● JNI (Java Native Interface) → Java & Kotlin ● JNIgen (Java Native Interface) ● and other bridges Interoperability 17 Methods : How to

Slide 18

Slide 18 text

/ Platform-Specific Code Design Writing a good Flutter plugin 18 ● Think functionality first, then APIs ● Avoid platform-specific API methods ● Avoid supporting only one platform ● Make your plugin easy to navigate and test ● Avoid writing static (or global) methods @sourece : https://medium.com/flutter/writing-a-good-flutter-plugin-1a561b986c9c

Slide 19

Slide 19 text

/ Platform-Specific Code Design 1. Requirements Definition 2. Identify required features 3. Data type & structure design 4. Platform code development 5. UI code development Design Checklist 19

Slide 20

Slide 20 text

/ Platform-Specific Code 20

Slide 21

Slide 21 text

/ Platform-Specific Code Design Channel Name Design Type Detail Channel Name Battery Battery Level MethodChannel com.dreamwalker.flutterkaigi2024/battery ${Common}/battery Battery Charging Status EventChannel com.dreamwalker.flutterkaigi2024/charging Sensor Gyroscope EventChannel com.dreamwalker.flutterkaigi2024/sensors/gyroscope ${Common}/sensors/gyroscope Common package or domain com.dreamwalker.flutterkaigi2024 21

Slide 22

Slide 22 text

Common (Flutter, Host(Native)) private val BATT_CHANNEL = "com.dreamwalker.flutterkaigi2024/battery" private val BATT_CHARGING_CHANNEL = "com.dreamwalker.flutterkaigi2024/charging" private val GYROSCOPE_CHANNEL = "com.dreamwalker.flutterkaigi2024/sensors/gyroscope" Host (Native) static const battMethodChannel = MethodChannel('com.dreamwalker.flutterkaigi2024/battery'); static const EventChannel battEventChannel = EventChannel('com.dreamwalker.flutterkaigi2024/charging'); static const EventChannel gyroscopeEventChannel = EventChannel('com.dreamwalker.flutterkaigi2024/sensors/gyroscope); Flutter 22

Slide 23

Slide 23 text

/ Platform-Specific Code Design Date Structure & Tpye Design Type Detail Data Type Battery Battery Level INT Sensor Gyroscope X Double Gyroscope Y Double Gyroscope Z Double Gyroscope List 23

Slide 24

Slide 24 text

/ Platform-Specific Code Design 24

Slide 25

Slide 25 text

Host (Native) - Android / Kotlin MethodChannel( flutterEngine.dartExecutor.binaryMessenger, BATT_CHANNEL, ).setMethodCallHandler { call, result -> if (call.method == "getBatteryLevel") { CoroutineScope(Dispatchers.Main).launch { val batteryLevel = getBatteryLevel() if (batteryLevel != -1) { result.success(batteryLevel) } else { result.error("UNAVAILABLE", "Battery level not available.", null) } } } else { result.notImplemented() } } 25

Slide 26

Slide 26 text

Host (Native) - Android / Kotlin MethodChannel( flutterEngine.dartExecutor.binaryMessenger, BATT_CHANNEL, ).setMethodCallHandler { call, result -> if (call.method == "getBatteryLevel") { CoroutineScope(Dispatchers.Main).launch { val batteryLevel = getBatteryLevel() if (batteryLevel != -1) { result.success(batteryLevel) } else { result.error("UNAVAILABLE", "Battery level not available.", null) } } } else { result.notImplemented() } } 26

Slide 27

Slide 27 text

● Configures, bootstraps, and starts executing Dart code. ● Once started, a DartExecutor cannot be stopped. The associated Dart code will execute until it completes, or until the FlutterEngine that owns this DartExecutor is destroyed. ● Creates a FlutterEngine in this group and run its DartExecutor with a default entrypoint of the "main" function in the "lib/main.dart" file. DartExecutor 27

Slide 28

Slide 28 text

Host (Native) - Android / Kotlin MethodChannel( flutterEngine.dartExecutor.binaryMessenger, BATT_CHANNEL, ).setMethodCallHandler { call, result -> if (call.method == "getBatteryLevel") { CoroutineScope(Dispatchers.Main).launch { val batteryLevel = getBatteryLevel() if (batteryLevel != -1) { result.success(batteryLevel) } else { result.error("UNAVAILABLE", "Battery level not available.", null) } } } else { result.notImplemented() } } 28

Slide 29

Slide 29 text

Host (Native) - Android / Kotlin MethodChannel( flutterEngine.dartExecutor.binaryMessenger, BATT_CHANNEL, ).setMethodCallHandler { call, result -> if (call.method == "getBatteryLevel") { CoroutineScope(Dispatchers.Main).launch { val batteryLevel = getBatteryLevel() if (batteryLevel != -1) { result.success(batteryLevel) } else { result.error("UNAVAILABLE", "Battery level not available.", null) } } } else { result.notImplemented() } } 29

Slide 30

Slide 30 text

Host (Native) - Android / Kotlin MethodChannel( flutterEngine.dartExecutor.binaryMessenger, BATT_CHANNEL, ).setMethodCallHandler { call, result -> if (call.method == "getBatteryLevel") { CoroutineScope(Dispatchers.Main).launch { val batteryLevel = getBatteryLevel() if (batteryLevel != -1) { result.success(batteryLevel) } else { result.error("UNAVAILABLE", "Battery level not available.", null) } } } else { result.notImplemented() } } 30

Slide 31

Slide 31 text

Host (Native) - Android / Kotlin MethodChannel( flutterEngine.dartExecutor.binaryMessenger, BATT_CHANNEL, ).setMethodCallHandler { call, result -> if (call.method == "getBatteryLevel") { CoroutineScope(Dispatchers.Main).launch { val batteryLevel = getBatteryLevel() if (batteryLevel != -1) { result.success(batteryLevel) } else { result.error("UNAVAILABLE", "Battery level not available.", null) } } } else { result.notImplemented() } } 31

Slide 32

Slide 32 text

Host (Native) - Android / Kotlin class SensorPlugin : FlutterPlugin { override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { val sensorsManager = binding.applicationContext.getSystemService(Context.SENSOR_SERVICE) as SensorManager gyroscopeChannel = EventChannel(binding.binaryMessenger, GYROSCOPE_CHANNEL) gyroscopeChannel.setStreamHandler(object : EventChannel.StreamHandler { override fun onListen(arguments: Any?, events: EventSink?) { } override fun onCancel(arguments: Any?) { } }) } 32

Slide 33

Slide 33 text

Host (Native) - Android / Kotlin class SensorPlugin : FlutterPlugin { override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { val sensorsManager = binding.applicationContext.getSystemService(Context.SENSOR_SERVICE) as SensorManager gyroscopeChannel = EventChannel(binding.binaryMessenger, GYROSCOPE_CHANNEL) gyroscopeChannel.setStreamHandler(object : EventChannel.StreamHandler { override fun onListen(arguments: Any?, events: EventSink?) { } override fun onCancel(arguments: Any?) { } }) } 33

Slide 34

Slide 34 text

Host (Native) - Android / Kotlin class SensorPlugin : FlutterPlugin { override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { val sensorsManager = binding.applicationContext.getSystemService(Context.SENSOR_SERVICE) as SensorManager gyroscopeChannel = EventChannel(binding.binaryMessenger, GYROSCOPE_CHANNEL) gyroscopeChannel.setStreamHandler(object : EventChannel.StreamHandler { override fun onListen(arguments: Any?, events: EventSink?) { } override fun onCancel(arguments: Any?) { } }) } 34

Slide 35

Slide 35 text

Host (Native) - Android / Kotlin class SensorPlugin : FlutterPlugin { override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { val sensorsManager = binding.applicationContext.getSystemService(Context.SENSOR_SERVICE) as SensorManager gyroscopeChannel = EventChannel(binding.binaryMessenger, GYROSCOPE_CHANNEL) gyroscopeChannel.setStreamHandler(object : EventChannel.StreamHandler { override fun onListen(arguments: Any?, events: EventSink?) { } override fun onCancel(arguments: Any?) { } }) } 35

Slide 36

Slide 36 text

Host (Native) - Android / Kotlin class SensorPlugin : FlutterPlugin { override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { val sensorsManager = binding.applicationContext.getSystemService(Context.SENSOR_SERVICE) as SensorManager gyroscopeChannel = EventChannel(binding.binaryMessenger, GYROSCOPE_CHANNEL) gyroscopeChannel.setStreamHandler(object : EventChannel.StreamHandler { override fun onListen(arguments: Any?, events: EventSink?) { } override fun onCancel(arguments: Any?) { } }) } 36

Slide 37

Slide 37 text

Host (Native) - Android / Kotlin FlutterPlugin private fun sensorEventListener(events: EventChannel.EventSink): SensorEventListener { return object : SensorEventListener { override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {} override fun onSensorChanged(event: SensorEvent) { val sensorValues = DoubleArray(event.values.size) event.values.forEachIndexed { index, value -> sensorValues[index] = value.toDouble() } events.success(sensorValues) } } } 37

Slide 38

Slide 38 text

Host (Native) - Android / Kotlin FlutterPlugin private fun sensorEventListener(events: EventChannel.EventSink): SensorEventListener { return object : SensorEventListener { override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {} override fun onSensorChanged(event: SensorEvent) { val sensorValues = DoubleArray(event.values.size) event.values.forEachIndexed { index, value -> sensorValues[index] = value.toDouble() } events.success(sensorValues) } } } 38

Slide 39

Slide 39 text

Host (Native) - Android / Kotlin FlutterPlugin private fun sensorEventListener(events: EventChannel.EventSink): SensorEventListener { return object : SensorEventListener { override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {} override fun onSensorChanged(event: SensorEvent) { val sensorValues = DoubleArray(event.values.size) event.values.forEachIndexed { index, value -> sensorValues[index] = value.toDouble() } events.success(sensorValues) } } } 39

Slide 40

Slide 40 text

Host (Native) - Android / Kotlin class MainActivity : FlutterActivity() { override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) flutterEngine.plugins.add(SensorPlugin()) MethodChannel( flutterEngine.dartExecutor.binaryMessenger, CHANNEL, ).setMethodCallHandler { call, result -> // code .. } } } You can easily add other Flutter plugins 40

Slide 41

Slide 41 text

Flutter : MethodChannel 41 Future getBatteryLevel() async { String batteryLevel; try { final result = await platform.invokeMethod('getBatteryLevel'); batteryLevel = 'Battery level at $result % .'; } on PlatformException catch (e) { batteryLevel = "Failed to get battery level: '${e.message}'."; } setState(() { // code.. }); } method channel

Slide 42

Slide 42 text

Flutter : MethodChannel 42 Future getBatteryLevel() async { String batteryLevel; try { final result = await platform.invokeMethod('getBatteryLevel'); batteryLevel = 'Battery level at $result % .'; } on PlatformException catch (e) { batteryLevel = "Failed to get battery level: '${e.message}'."; } setState(() { // code.. }); } int int?

Slide 43

Slide 43 text

Flutter : MethodChannel 43 Future getBatteryLevel() async { String batteryLevel; try { final result = await platform.invokeMethod('getBatteryLevel'); batteryLevel = 'Battery level at $result % .'; } on PlatformException catch (e) { batteryLevel = "Failed to get battery level: '${e.message}'."; } setState(() { // code.. }); } dynamic

Slide 44

Slide 44 text

Flutter : EventChannel 44 Stream? gyroscopeEvents; static const EventChannel gyroscopeEventChannel = EventChannel('com.xxx.xxx/sensors/gyroscope'); gyroscopeEvents ??= gyroscopeEventChannel.receiveBroadcastStream().map((dynamic event) { final list = event.cast(); return GyroscopeEventData( list[0]!, list[1]!, list[2]! ); });

Slide 45

Slide 45 text

Flutter : EventChannel 45 Stream? gyroscopeEvents; static const EventChannel gyroscopeEventChannel = EventChannel('com.xxx.xxx/sensors/gyroscope'); gyroscopeEvents ??= gyroscopeEventChannel.receiveBroadcastStream().map((dynamic event) { final list = event.cast(); return GyroscopeEventData( list[0]!, list[1]!, list[2]! ); });

Slide 46

Slide 46 text

/ Platform-Specific Code Design Platform-Specific Pattern 01 46

Slide 47

Slide 47 text

/ Platform-Specific Code Design Platform-Specific Pattern 02 47

Slide 48

Slide 48 text

/ Platform-Specific Code Design Platform-Specific Pattern 03 48

Slide 49

Slide 49 text

/ Platform-Specific Code Design Platform-Specific Pattern 04 49

Slide 50

Slide 50 text

/ Platform-Specific Code Design Platform-Specific Pattern 05 50 plugin_platform_interface

Slide 51

Slide 51 text

/ Platform-Specific Code Design Platform-Specific Pattern 05 51

Slide 52

Slide 52 text

/ Platform-Specific Code Design Platform-Specific Pattern 05 52

Slide 53

Slide 53 text

53 @source: https://pub.dev/packages/plugin_platform_interface

Slide 54

Slide 54 text

type-safe? 54

Slide 55

Slide 55 text

/ Pigeon type-safe? 55

Slide 56

Slide 56 text

/ Pigeon type-safe? 56 Protocol Buffers & JSON

Slide 57

Slide 57 text

@jaichangpark, shots on iPhone in Shinjuku Pigeon ハト 57

Slide 58

Slide 58 text

Pigeon is a code generator tool to make communication between Flutter and the host platform type-safe, easier, and faster. ● Pigeon removes the necessity to manage strings across multiple platforms and languages. ● It also improves efficiency over common method channel patterns. ● Most importantly though, it removes the need to write custom platform channel code, since pigeon generates it for you. Code generator tool Pigeon 58

Slide 59

Slide 59 text

Currently pigeon supports generating: ● Kotlin and Java code for Android ● Swift and Objective-C code for iOS and macOS ● C++ code for Windows ● GObject code for Linux Supported Platforms Pigeon 59

Slide 60

Slide 60 text

60 @Source ● https://pub.dev/packages/pigeon ● https://github.com/flutter/packages/tree/ main/packages/pigeon ● Access Date: 2024.10

Slide 61

Slide 61 text

flutter packages Use Cases 61 ● shared_preferences ● url_launcher ● image_picker ● camera ● in_app_purchase ● video_player ● and so on / Pigeon

Slide 62

Slide 62 text

Flutterfire Use Cases 62 @source: https://github.com/firebase/flutterfire/pull/8921 from 2022.06~ / Pigeon

Slide 63

Slide 63 text

Pigeon 63 Code generator tool

Slide 64

Slide 64 text

Mission 64 / Pigeon

Slide 65

Slide 65 text

1. Installing Add pigeon as a dev_dependency 65

Slide 66

Slide 66 text

66 enum AlgorithmType { sha1, sha2, md5, aesCbc, aesEcb, } class MessageData { MessageData({required this.type, required this.data}); AlgorithmType type; String? data; } 2. Define Messages

Slide 67

Slide 67 text

67 @HostApi() abstract class CustomHostApi { String generateHash(MessageData message); String encrypt(MessageData message); @async String decrypt(MessageData message); } 3. Define APIs

Slide 68

Slide 68 text

@HostApi ● @HostApi() being for procedures that are defined on the host platform. ● When you want to call the host platform from Flutter. @FlutterApi ● @FlutterApi() for procedures that are defined in Dart. ● When you want to call a Flutter app from the host platform. 68 / Pigeon

Slide 69

Slide 69 text

69 4. Code Generation dart run pigeon \ --input pigeons/messaging.dart \ --dart_out lib/pigeon.dart \ --objc_header_out ios/Runner/pigeon.h \ --objc_source_out ios/Runner/pigeon.m \ --java_out ./android/app/src/main/java/com/example/xxxx/Pigeon.java \ --java_package "com.example.xxxx"

Slide 70

Slide 70 text

70 @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', dartOptions: DartOptions(), cppOptions: CppOptions(namespace: 'flutter_pigeon'), cppHeaderOut: 'windows/runner/messages.g.h', cppSourceOut: 'windows/runner/messages.g.cpp', kotlinOut: 'android/app/src/main/kotlin/com/dreamwalker/flutter_pigeon/Messages.g.kt', kotlinOptions: KotlinOptions(), javaOut: 'android/app/src/main/java/io/flutter/plugins/Messages.java', javaOptions: JavaOptions(), swiftOut: 'ios/Runner/Messages.g.swift', swiftOptions: SwiftOptions(), objcHeaderOut: 'macos/Runner/messages.g.h', objcSourceOut: 'macos/Runner/messages.g.m', objcOptions: ObjcOptions(prefix: 'PGN'), copyrightHeader: 'pigeons/copyright.txt', dartPackageName: 'flutter_pigeon', )) 4. Code Generation message.dart

Slide 71

Slide 71 text

71 @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', dartOptions: DartOptions(), cppOptions: CppOptions(namespace: 'flutter_pigeon'), cppHeaderOut: 'windows/runner/messages.g.h', cppSourceOut: 'windows/runner/messages.g.cpp', kotlinOut: 'android/app/src/main/kotlin/com/dreamwalker/flutter_pigeon/Messages.g.kt', kotlinOptions: KotlinOptions(), javaOut: 'android/app/src/main/java/io/flutter/plugins/Messages.java', javaOptions: JavaOptions(), swiftOut: 'ios/Runner/Messages.g.swift', swiftOptions: SwiftOptions(), objcHeaderOut: 'macos/Runner/messages.g.h', objcSourceOut: 'macos/Runner/messages.g.m', objcOptions: ObjcOptions(prefix: 'PGN'), copyrightHeader: 'pigeons/copyright.txt', dartPackageName: 'flutter_pigeon', ))

Slide 72

Slide 72 text

72 @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', dartOptions: DartOptions(), cppOptions: CppOptions(namespace: 'flutter_pigeon'), cppHeaderOut: 'windows/runner/messages.g.h', cppSourceOut: 'windows/runner/messages.g.cpp', kotlinOut: 'android/app/src/main/kotlin/com/dreamwalker/flutter_pigeon/Messages.g.kt', kotlinOptions: KotlinOptions(), javaOut: 'android/app/src/main/java/io/flutter/plugins/Messages.java', javaOptions: JavaOptions(), swiftOut: 'ios/Runner/Messages.g.swift', swiftOptions: SwiftOptions(), objcHeaderOut: 'macos/Runner/messages.g.h', objcSourceOut: 'macos/Runner/messages.g.m', objcOptions: ObjcOptions(prefix: 'PGN'), copyrightHeader: 'pigeons/copyright.txt', dartPackageName: 'flutter_pigeon', )) com.dreamwalker.flutter_pigeon

Slide 73

Slide 73 text

73 @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', dartOptions: DartOptions(), cppOptions: CppOptions(namespace: 'flutter_pigeon'), cppHeaderOut: 'windows/runner/messages.g.h', cppSourceOut: 'windows/runner/messages.g.cpp', kotlinOut: 'android/app/src/main/kotlin/com/dreamwalker/flutter_pigeon/Messages.g.kt', kotlinOptions: KotlinOptions(), javaOut: 'android/app/src/main/java/io/flutter/plugins/Messages.java', javaOptions: JavaOptions(), swiftOut: 'ios/Runner/Messages.g.swift', swiftOptions: SwiftOptions(), objcHeaderOut: 'macos/Runner/messages.g.h', objcSourceOut: 'macos/Runner/messages.g.m', objcOptions: ObjcOptions(prefix: 'PGN'), copyrightHeader: 'pigeons/copyright.txt', dartPackageName: 'flutter_pigeon', ))

Slide 74

Slide 74 text

dart run pigeon --input ./pigeons/message.dart 74

Slide 75

Slide 75 text

75 / Pigeon Folder Structure

Slide 76

Slide 76 text

76 final CustomHostApi _api = CustomHostApi(); Future genSha2(String input) async { try { final v = await _api.generateHash( MessageData(type: AlgorithmType.sha2, data: input), ); } catch (e) { return "$e"; } } 5. Flutter Side

Slide 77

Slide 77 text

77 private class CustomApiImpl : CustomHostApi { override fun generateHash(message: MessageData): String { //code... } override fun encrypt(message: MessageData): String { TODO("Not yet implemented") } override fun decrypt(message: MessageData, callback: (Result) -> Unit) { TODO("Not yet implemented") } } 5. Android (Kotlin) CustomHostApi: Generated Code by pigeon

Slide 78

Slide 78 text

78 private class CustomApiImpl : CustomHostApi { override fun generateHash(message: MessageData): String { //code... } override fun encrypt(message: MessageData): String { TODO("Not yet implemented") } override fun decrypt(message: MessageData, callback: (Result) -> Unit) { TODO("Not yet implemented") } } 5. Android (Kotlin) @async

Slide 79

Slide 79 text

79 class MainActivity : FlutterActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) val api = CustomApiImplementation() CustomHostApi.setUp(flutterEngine.dartExecutor.binaryMessenger, api) } } 5. Android (Kotlin)

Slide 80

Slide 80 text

80 / Pigeon

Slide 81

Slide 81 text

81 / Pigeon Oops!

Slide 82

Slide 82 text

82 private class CustomApiImpl: CustomHostApi { func generateHash(message: MessageData) throws -> String { } func encrypt(message: MessageData) throws -> String { } func decrypt(message: MessageData, completion: @escaping (Result) -> Void) { } } 5. iOS (swift) Using CryptoKit

Slide 83

Slide 83 text

83 @main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) let controller = window?.rootViewController as! FlutterViewController let api = CustomApiImpl() CustomHostApiSetup.setUp(binaryMessenger: controller.binaryMessenger, api: api) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } 5. iOS (swift)

Slide 84

Slide 84 text

84 / Pigeon

Slide 85

Slide 85 text

Foreign Function Interface ffi 85

Slide 86

Slide 86 text

86 / Foreign Function Interface ● FFI stands for foreign function interface. ● Dart mobile, command-line, and server apps running on the Dart Native platform can use the dart:ffi library to call native C APIs, and to read, write, allocate, and deallocate native memory. ● ffigen only supports parsing C headers, not C++ headers.

Slide 87

Slide 87 text

87 / Foreign Function Interface ● Databases ● Graphic & Image processing ● OS (Native) API → Windows, Linux etc. ● C-based compiled libs

Slide 88

Slide 88 text

88 / Foreign Function Interface

Slide 89

Slide 89 text

89 fibonacci #ifndef FIBONACCI_H #define FIBONACCI_H int fibonacci(int n); #endif // FIBONACCI_H fibonacci.h @Source https://en.wikipedia.org/wiki/Fibonacci_sequence By Romain - Own work, CC BY-SA 4.0, https://commons.wikimedia.org/w/index.php?curid=114415511

Slide 90

Slide 90 text

90 fibonacci int fibonacci(int n) { if (n <= 0) { return 0; } else if (n == 1) { return 1; } else { return fibonacci(n - 1) + fibonacci(n - 2); } } fibonacci.c

Slide 91

Slide 91 text

91 fibonacci cmake_minimum_required(VERSION 3.10) project(FibonacciLibrary) include_directories(include) add_library(fibonacci SHARED fibonacci.c) add_executable(fibonacci_app fibonacci.c) target_link_libraries(fibonacci_app fibonacci) CMakeList.txt

Slide 92

Slide 92 text

92 fibonacci main.dart var libraryPath = path.join(Directory.current.path, 'lib/fibonacci', 'libfibonacci.dylib'); final dylib = ffi.DynamicLibrary.open(libraryPath);

Slide 93

Slide 93 text

93 ! Key Point final DynamicLibrary _dylib = () { if (Platform.isMacOS || Platform.isIOS) { return DynamicLibrary.open('$_libName.framework/$_libName'); } if (Platform.isAndroid || Platform.isLinux) { return DynamicLibrary.open('lib$_libName.so'); } if (Platform.isWindows) { return DynamicLibrary.open('$_libName.dll'); } throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}'); }();

Slide 94

Slide 94 text

94 fibonacci main.dart final fibo = dylib .lookup>('fibonacci') .asFunction(); lib(Native) Function Client (Dart) Function function name Data Type of parameters Return Data Type

Slide 95

Slide 95 text

95 / Foreign Function Interface ● Rust Programming Language ● Go Programming Language

Slide 96

Slide 96 text

96 / Foreign Function Interface

Slide 97

Slide 97 text

97 #[no_mangle] pub extern "C" fn fibonacci(n: i32) -> i32 { if n <= 0 { return 0; } else if n == 1 { return 1; } else { let mut a = 0; let mut b = 1; let mut c = 0; for _ in 2..=n { c = a + b; a = b; b = c; } return c; } cargo build --release --target=[target] Android cargo ndk -t [target] -o ./jniLibs build --release ● arm64-v8a ● armeabi-v7a ● x86 ● x86_64

Slide 98

Slide 98 text

98 / Foreign Function Interface fibonacci Rust based Android Rust logo © Rust Foundation, used under CC-BY 4.0.

Slide 99

Slide 99 text

99 _dylib = () { if (Platform.isAndroid || Platform.isLinux) { return ffi.DynamicLibrary.open('librust_lib.so'); } throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}'); }();

Slide 100

Slide 100 text

100 typedef FibonacciFunc = ffi.Int Function(ffi.Int); typedef Fibonacci = int Function(int); final Fibonacci fibonacci = _dylib .lookup>('fibonacci') .asFunction(); int n = 10; int result = fibonacci(n);

Slide 101

Slide 101 text

101 / Foreign Function Interface Generator for FFI bindings, using LibClang to parse C, Objective-C, and Swift files. ffigen

Slide 102

Slide 102 text

ffigen Add ffigen as a dev_dependency 102

Slide 103

Slide 103 text

103 ffigen name: NativeLibrary description: misc output: 'generated_bindings.dart' headers: entry-points: - 'lib/fibonacci/fibonacci.h' include-directives: - 'lib/fibonacci/fibonacci.h' ffigen.yaml Class Name

Slide 104

Slide 104 text

104 ffigen dart run ffigen --config ffigen.yaml

Slide 105

Slide 105 text

105 ffigen NativeLibrary nativeLibrary = NativeLibrary(dylib); print(nativeLibrary.fibonacci(10)); Generated a Class Using ffigen

Slide 106

Slide 106 text

106 / Summary ● Platform-Specific Code ○ Method Channel, Event Channel ○ plugin_platform_interface ○ ffi, ffigen, jnigen, pigeon, etc. ● Pigeon ○ Code generator tool ○ type-safe ● FFI ○ FFI stands for foreign function interface. ○ Lookup, asFunction ○ ffigen

Slide 107

Slide 107 text

Thank You ご清聴ありがとうございました Flutter Seoul GDG Golang Korea Dreamwalker (@jaichangpark) 107