Skip to content

Commit

Permalink
Use cinterop with stub headers instead of Flutter cocoapod dependency
Browse files Browse the repository at this point in the history
  • Loading branch information
erksch committed Feb 28, 2025
1 parent c99002b commit 35ceceb
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 37 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions example/FlutterKmpExample.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
Expand Down
13 changes: 4 additions & 9 deletions example/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
26 changes: 12 additions & 14 deletions flutter-kmp/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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")
}

Expand Down Expand Up @@ -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()
Expand Down
9 changes: 4 additions & 5 deletions flutter-kmp/src/iosMain/kotlin/EventStreamHandlerUtils.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 <reified T> Flow<T>.toEventStreamHandler(): NSObject =
toEventStreamHandler(serializer<T>())
Expand Down Expand Up @@ -45,4 +44,4 @@ fun <T> Flow<T>.toEventStreamHandler(serializer: SerializationStrategy<T>): NSOb
return null
}
}
}
}
74 changes: 74 additions & 0 deletions flutter-kmp/src/nativeInterop/cinterop/Flutter/FlutterPlugin.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@

// https://github.com/flutter/engine/blob/3.22.2/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h

#import <Foundation/Foundation.h>

// https://github.com/flutter/engine/blob/3.22.2/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h#L49C1-L49C44
@protocol FlutterBinaryMessenger <NSObject>
@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<FlutterBinaryMessenger>*)messenger
codec:(NSObject<FlutterMessageCodec>*)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 <NSObject>
- (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<FlutterBinaryMessenger>*)messenger
codec:(NSObject<FlutterMethodCodec>*)codec;
- (void)setStreamHandler:(NSObject<FlutterStreamHandler>* _Nullable)handler;
@end

// https://github.com/flutter/engine/blob/3.22.2/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h#L189
@protocol FlutterPlugin <NSObject>
@end

// https://github.com/flutter/engine/blob/3.22.2/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h#L283
@protocol FlutterPluginRegistrar <NSObject>
- (NSObject<FlutterBinaryMessenger>*)messenger;
- (void)addMethodCallDelegate:(NSObject<FlutterPlugin>*)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 <FlutterMethodCodec>
@end
3 changes: 3 additions & 0 deletions flutter-kmp/src/nativeInterop/cinterop/flutter.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
language = Objective-C
headers = Flutter/FlutterPlugin.h
headerFilter = Flutter/*

0 comments on commit 35ceceb

Please sign in to comment.