From 35cecebf85e607c026cc2950204880123b9b6de0 Mon Sep 17 00:00:00 2001 From: Erik Ziegler Date: Fri, 28 Feb 2025 09:45:23 +0100 Subject: [PATCH] Use cinterop with stub headers instead of Flutter cocoapod dependency --- CHANGELOG.md | 2 + example/FlutterKmpExample.podspec | 4 +- example/build.gradle.kts | 13 +--- .../ksp/processor/IOSKotlinModuleGenerator.kt | 14 ++-- flutter-kmp/build.gradle.kts | 26 +++---- .../iosMain/kotlin/EventStreamHandlerUtils.kt | 9 +-- .../cinterop/Flutter/FlutterPlugin.h | 74 +++++++++++++++++++ .../src/nativeInterop/cinterop/flutter.def | 3 + 8 files changed, 108 insertions(+), 37 deletions(-) create mode 100644 flutter-kmp/src/nativeInterop/cinterop/Flutter/FlutterPlugin.h create mode 100644 flutter-kmp/src/nativeInterop/cinterop/flutter.def diff --git a/CHANGELOG.md b/CHANGELOG.md index e61113b..694ca79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## unreleased +- Use cinterop with stub headers instead of Flutter cocoapod dependency + ## v0.1.0-rc.3 - Fix conflicts with same method names across different modules on iOS by prefixing method names diff --git a/example/FlutterKmpExample.podspec b/example/FlutterKmpExample.podspec index 3825888..a3aa1a2 100644 --- a/example/FlutterKmpExample.podspec +++ b/example/FlutterKmpExample.podspec @@ -8,8 +8,8 @@ Pod::Spec.new do |spec| spec.summary = 'Shared Kotlin code for flutter-kmp example' spec.vendored_frameworks = 'build/cocoapods/framework/flutterkmpexample.framework' spec.libraries = 'c++' - spec.ios.deployment_target = '11.0' - spec.dependency 'Flutter' + + if !Dir.exist?('build/cocoapods/framework/flutterkmpexample.framework') || Dir.empty?('build/cocoapods/framework/flutterkmpexample.framework') raise " diff --git a/example/build.gradle.kts b/example/build.gradle.kts index 724607b..a1b9cba 100644 --- a/example/build.gradle.kts +++ b/example/build.gradle.kts @@ -33,16 +33,11 @@ kotlin { homepage = "https://github.com/voize-gmbh/flutter-kmp" summary = "Shared Kotlin code for flutter-kmp example" baseName = "flutterkmpexample" - } - - // We can not use a specific version here, because the podspec generated by this KMP project - // will be referenced by the Flutter plugin podspec and therefore the Flutter target app. - // The target app also depends on Flutter but the Flutter.podspec it uses has a stub version (1.0.0). - // So specifying a version here will create a conflict and instead we have to rely on the assumption - // that the interop for the Flutter in the Cocoapods registry is compatible with the Flutter version used in the target app. - pod("Flutter") - ios.deploymentTarget = "11.0" + // without setting the framework to static, you will get errors during linking + // that symbols e.g. for _OBJC_CLASS_$_FlutterError are missing + isStatic = true + } } sourceSets { diff --git a/flutter-kmp-ksp/src/main/kotlin/de/voize/flutterkmp/ksp/processor/IOSKotlinModuleGenerator.kt b/flutter-kmp-ksp/src/main/kotlin/de/voize/flutterkmp/ksp/processor/IOSKotlinModuleGenerator.kt index f2de61d..296f073 100644 --- a/flutter-kmp-ksp/src/main/kotlin/de/voize/flutterkmp/ksp/processor/IOSKotlinModuleGenerator.kt +++ b/flutter-kmp-ksp/src/main/kotlin/de/voize/flutterkmp/ksp/processor/IOSKotlinModuleGenerator.kt @@ -138,11 +138,11 @@ class IOSKotlinModuleGenerator { private val ExperimentalForeignApi = ClassName("kotlinx.cinterop", "ExperimentalForeignApi") private val NSObject = ClassName("platform.darwin", "NSObject") -private val FlutterResult = ClassName("cocoapods.Flutter", "FlutterResult") -private val FlutterMethodCall = ClassName("cocoapods.Flutter", "FlutterMethodCall") -private val FlutterMethodChannel = ClassName("cocoapods.Flutter", "FlutterMethodChannel") -private val FlutterEventChannel = ClassName("cocoapods.Flutter", "FlutterEventChannel") -private val FlutterPluginRegistrar = ClassName("cocoapods.Flutter", "FlutterPluginRegistrarProtocol") -private val FlutterPlugin = ClassName("cocoapods.Flutter", "FlutterPluginProtocol") -private val FlutterStandardMethodCodec = ClassName("cocoapods.Flutter", "FlutterStandardMethodCodec") +private val FlutterResult = ClassName("flutter", "FlutterResult") +private val FlutterMethodCall = ClassName("flutter", "FlutterMethodCall") +private val FlutterMethodChannel = ClassName("flutter", "FlutterMethodChannel") +private val FlutterEventChannel = ClassName("flutter", "FlutterEventChannel") +private val FlutterPluginRegistrar = ClassName("flutter", "FlutterPluginRegistrarProtocol") +private val FlutterPlugin = ClassName("flutter", "FlutterPluginProtocol") +private val FlutterStandardMethodCodec = ClassName("flutter", "FlutterStandardMethodCodec") private val toEventStreamHandler = MemberName(flutterKmpPackageName, "toEventStreamHandler") \ No newline at end of file diff --git a/flutter-kmp/build.gradle.kts b/flutter-kmp/build.gradle.kts index 39f2f27..c3974de 100644 --- a/flutter-kmp/build.gradle.kts +++ b/flutter-kmp/build.gradle.kts @@ -1,7 +1,8 @@ +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget + plugins { kotlin("multiplatform") kotlin("plugin.serialization") - kotlin("native.cocoapods") id("com.android.library") } @@ -30,21 +31,18 @@ kotlin { publishLibraryVariants("release") } - cocoapods { - // this cocoapods dependency is needed for the iOS EventStreamHandler utils - pod("Flutter") - - // we do not need to create a podspec file because the KMP projects that use this library - // must produce a podspec that contains the Flutter cocoapod dependency - noPodspec() - - ios.deploymentTarget = "11.0" + fun KotlinNativeTarget.configureFlutterInterop() { + val main by compilations.getting { + val flutter by cinterops.creating { + includeDirs("src/nativeInterop/cinterop/") + packageName("flutter") + } + } } - - iosX64() - iosArm64() - iosSimulatorArm64() + iosX64 { configureFlutterInterop() } + iosArm64 { configureFlutterInterop() } + iosSimulatorArm64 { configureFlutterInterop() } wasmJs { nodejs() } applyDefaultHierarchyTemplate() diff --git a/flutter-kmp/src/iosMain/kotlin/EventStreamHandlerUtils.kt b/flutter-kmp/src/iosMain/kotlin/EventStreamHandlerUtils.kt index fb60e9f..2187c27 100644 --- a/flutter-kmp/src/iosMain/kotlin/EventStreamHandlerUtils.kt +++ b/flutter-kmp/src/iosMain/kotlin/EventStreamHandlerUtils.kt @@ -1,9 +1,6 @@ package de.voize.flutterkmp -import cocoapods.Flutter.FlutterError -import cocoapods.Flutter.FlutterEventSink import kotlinx.coroutines.flow.Flow -import cocoapods.Flutter.FlutterStreamHandlerProtocol import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -12,7 +9,9 @@ import kotlinx.coroutines.withContext import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.serializer import platform.darwin.NSObject - +import flutter.FlutterStreamHandlerProtocol +import flutter.FlutterEventSink +import flutter.FlutterError inline fun Flow.toEventStreamHandler(): NSObject = toEventStreamHandler(serializer()) @@ -45,4 +44,4 @@ fun Flow.toEventStreamHandler(serializer: SerializationStrategy): NSOb return null } } -} \ No newline at end of file +} diff --git a/flutter-kmp/src/nativeInterop/cinterop/Flutter/FlutterPlugin.h b/flutter-kmp/src/nativeInterop/cinterop/Flutter/FlutterPlugin.h new file mode 100644 index 0000000..e341198 --- /dev/null +++ b/flutter-kmp/src/nativeInterop/cinterop/Flutter/FlutterPlugin.h @@ -0,0 +1,74 @@ + +// https://github.com/flutter/engine/blob/3.22.2/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h + +#import + +// https://github.com/flutter/engine/blob/3.22.2/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h#L49C1-L49C44 +@protocol FlutterBinaryMessenger +@end + +// https://github.com/flutter/engine/blob/3.22.2/shell/platform/darwin/common/framework/Headers/FlutterChannels.h#L194 +typedef void (^FlutterResult)(id _Nullable result); + +// https://github.com/flutter/engine/blob/3.22.2/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h#L391 +@protocol FlutterMethodCodec ++ (instancetype)sharedInstance; +@end + +// https://github.com/flutter/engine/blob/3.22.2/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h#L18C1-L18C30 +@protocol FlutterMessageCodec ++ (instancetype)sharedInstance; +@end + +// https://github.com/flutter/engine/blob/3.22.2/shell/platform/darwin/common/framework/Headers/FlutterChannels.h#L220 +@interface FlutterMethodChannel : NSObject +- (instancetype)initWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger + codec:(NSObject*)codec; +@end + +// https://github.com/flutter/engine/blob/3.22.2/shell/platform/darwin/common/framework/Headers/FlutterChannels.h#L350 +typedef void (^FlutterEventSink)(id _Nullable event); + +// https://github.com/flutter/engine/blob/3.22.2/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h#L246C1-L246C35 +@interface FlutterError : NSObject ++ (instancetype)errorWithCode:(NSString*)code + message:(NSString* _Nullable)message + details:(id _Nullable)details; +@end + +// https://github.com/flutter/engine/blob/3.22.2/shell/platform/darwin/common/framework/Headers/FlutterChannels.h#L356 +@protocol FlutterStreamHandler +- (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments + eventSink:(FlutterEventSink)events; +- (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments; +@end + +// https://github.com/flutter/engine/blob/3.22.2/shell/platform/darwin/common/framework/Headers/FlutterChannels.h#L400C1-L400C42 +@interface FlutterEventChannel : NSObject +- (instancetype)initWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger + codec:(NSObject*)codec; +- (void)setStreamHandler:(NSObject* _Nullable)handler; +@end + +// https://github.com/flutter/engine/blob/3.22.2/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h#L189 +@protocol FlutterPlugin +@end + +// https://github.com/flutter/engine/blob/3.22.2/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h#L283 +@protocol FlutterPluginRegistrar +- (NSObject*)messenger; +- (void)addMethodCallDelegate:(NSObject*)delegate + channel:(FlutterMethodChannel*)channel; +@end + +// https://github.com/flutter/engine/blob/3.22.2/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h#L220 +@interface FlutterMethodCall : NSObject +@property(readonly, nonatomic) NSString* method; +@property(readonly, nonatomic, nullable) id arguments; +@end + +// https://github.com/flutter/engine/blob/3.22.2/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h#L469 +@interface FlutterStandardMethodCodec : NSObject +@end diff --git a/flutter-kmp/src/nativeInterop/cinterop/flutter.def b/flutter-kmp/src/nativeInterop/cinterop/flutter.def new file mode 100644 index 0000000..99a6836 --- /dev/null +++ b/flutter-kmp/src/nativeInterop/cinterop/flutter.def @@ -0,0 +1,3 @@ +language = Objective-C +headers = Flutter/FlutterPlugin.h +headerFilter = Flutter/* \ No newline at end of file