Skip to content

Commit 0d2b6ea

Browse files
committed
WIP
1 parent 2f6e355 commit 0d2b6ea

File tree

11 files changed

+491
-215
lines changed

11 files changed

+491
-215
lines changed

example/FlutterKmpExample.podspec

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
Pod::Spec.new do |spec|
2+
spec.name = 'FlutterKmpExample'
3+
spec.version = '0.1.0'
4+
spec.homepage = 'https://github.com/voize-gmbh/flutter-kmp'
5+
spec.source = { :http=> ''}
6+
spec.authors = ''
7+
spec.license = ''
8+
spec.summary = 'Shared Kotlin code for flutter-kmp example'
9+
spec.vendored_frameworks = 'build/cocoapods/framework/shared.framework'
10+
spec.libraries = 'c++'
11+
12+
13+
14+
if !Dir.exist?('build/cocoapods/framework/shared.framework') || Dir.empty?('build/cocoapods/framework/shared.framework')
15+
raise "
16+
17+
Kotlin framework 'shared' doesn't exist yet, so a proper Xcode project can't be generated.
18+
'pod install' should be executed after running ':generateDummyFramework' Gradle task:
19+
20+
./gradlew :generateDummyFramework
21+
22+
Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)"
23+
end
24+
25+
spec.pod_target_xcconfig = {
26+
'KOTLIN_PROJECT_PATH' => '',
27+
'PRODUCT_MODULE_NAME' => 'shared',
28+
}
29+
30+
spec.script_phases = [
31+
{
32+
:name => 'Build FlutterKmpExample',
33+
:execution_position => :before_compile,
34+
:shell_path => '/bin/sh',
35+
:script => <<-SCRIPT
36+
if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
37+
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
38+
exit 0
39+
fi
40+
set -ev
41+
REPO_ROOT="$PODS_TARGET_SRCROOT"
42+
"$REPO_ROOT/gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
43+
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
44+
-Pkotlin.native.cocoapods.archs="$ARCHS" \
45+
-Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
46+
SCRIPT
47+
}
48+
]
49+
50+
end

example/build.gradle.kts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
plugins {
22
alias(libs.plugins.kotlinMultiplatform)
3+
alias(libs.plugins.kotlinCocoapods)
34
alias(libs.plugins.androidLibrary)
45
alias(libs.plugins.ksp)
56
alias(libs.plugins.kotlinSerialization)
@@ -8,7 +9,6 @@ plugins {
89
val flutterKmpVersion = "0.1.0-rc.0"
910

1011
kotlin {
11-
targetHierarchy.default()
1212
jvm()
1313
androidTarget {
1414
publishLibraryVariants("release")
@@ -18,9 +18,29 @@ kotlin {
1818
}
1919
}
2020
}
21-
iosX64()
22-
iosArm64()
23-
iosSimulatorArm64()
21+
22+
listOf(
23+
iosX64(),
24+
iosArm64(),
25+
iosSimulatorArm64()
26+
).forEach {
27+
it.binaries.framework {
28+
baseName = "shared"
29+
}
30+
}
31+
32+
applyDefaultHierarchyTemplate()
33+
34+
cocoapods {
35+
name = "FlutterKmpExample"
36+
version = "0.1.0"
37+
38+
framework {
39+
homepage = "https://github.com/voize-gmbh/flutter-kmp"
40+
summary = "Shared Kotlin code for flutter-kmp example"
41+
baseName = "shared"
42+
}
43+
}
2444

2545
sourceSets {
2646
val androidMain by getting {
@@ -41,6 +61,16 @@ kotlin {
4161
implementation(libs.kotlin.test)
4262
}
4363
}
64+
65+
val iosX64Main by getting {
66+
kotlin.srcDir("build/generated/ksp/iosX64/iosX64Main/kotlin")
67+
}
68+
val iosArm64Main by getting {
69+
kotlin.srcDir("build/generated/ksp/iosArm64/iosArm64Main/kotlin")
70+
}
71+
val iosSimulatorArm64Main by getting {
72+
kotlin.srcDir("build/generated/ksp/iosSimulatorArm64/iosSimulatorArm64Main/kotlin")
73+
}
4474
}
4575
}
4676

example/flutter/ios/Classes/FlutterKmpExamplePlugin.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import UIKit
33

44
public class FlutterKmpExamplePlugin: NSObject, FlutterPlugin {
55
public static func register(with registrar: FlutterPluginRegistrar) {
6+
7+
let myTestClass = MyTestClassIOS()
68
let channel = FlutterMethodChannel(name: "flutter_kmp_example", binaryMessenger: registrar.messenger())
79
let instance = FlutterKmpExamplePlugin()
810
registrar.addMethodCallDelegate(instance, channel: channel)

example/gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version
1515
[plugins]
1616
androidLibrary = { id = "com.android.library", version.ref = "agp" }
1717
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
18+
kotlinCocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" }
1819
kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
1920
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }

flutter-kmp-ksp/src/main/kotlin/de/voize/flutterkmp/ksp/processor/AndroidModuleGenerator.kt

Lines changed: 8 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -128,114 +128,6 @@ class AndroidModuleGenerator {
128128
fileSpec.writeTo(codeGenerator, false)
129129
}
130130

131-
private fun CodeBlock.Builder.addMethodCodeBlock(
132-
method: KSFunctionDeclaration,
133-
wrappedModuleVarName: String,
134-
) {
135-
beginControlFlow("%S ->", method.simpleName.asString())
136-
137-
if (method.parameters.isNotEmpty()) {
138-
//
139-
// val arguments = call.arguments as List<*>
140-
// val arg0 = arguments[0] as Arg0Type
141-
// ...
142-
//
143-
addStatement("val arguments = call.arguments as List<*>")
144-
method.parameters.forEachIndexed { index, parameter ->
145-
getKotlinDeserialization(
146-
parameter.type.resolve(),
147-
"arguments[$index]",
148-
"arg$index",
149-
)
150-
}
151-
}
152-
153-
fun withCoroutineScopeControlFlow(block: CodeBlock.Builder.() -> Unit) {
154-
beginControlFlow(
155-
"%T(%T.Default).%M",
156-
CoroutineScope,
157-
Dispatchers,
158-
launch,
159-
)
160-
block()
161-
endControlFlow()
162-
}
163-
164-
fun invokeUnitMethodStatement() {
165-
addStatement(
166-
"%N.%L(%L)",
167-
wrappedModuleVarName,
168-
method.simpleName.asString(),
169-
method.parameters.indices.joinToString(", ") { "arg$it" }
170-
)
171-
}
172-
173-
fun invokeMethodStatement() {
174-
addStatement(
175-
"val resultData = %N.%L(%L)",
176-
wrappedModuleVarName,
177-
method.simpleName.asString(),
178-
method.parameters.indices.joinToString(", ") { "arg$it" }
179-
)
180-
181-
val returnTypeDeclaration = (method.returnType ?: error("return type is null")).resolve().declaration
182-
183-
addStatement("val json = %T { encodeDefaults = true }", JsonClassName)
184-
getKotlinSerialization(returnTypeDeclaration, "resultData", "json")
185-
}
186-
187-
fun unitResultSuccessStatement() {
188-
addStatement("result.success(null)")
189-
}
190-
191-
if (method.returnsUnit()) {
192-
if (method.modifiers.contains(Modifier.SUSPEND)) {
193-
//
194-
// CoroutineScope(Dispatchers.Default).launch {
195-
// wrappedModule.method(arg0, arg1, ...)
196-
// result.success(null)
197-
// }
198-
//
199-
withCoroutineScopeControlFlow {
200-
invokeUnitMethodStatement()
201-
unitResultSuccessStatement()
202-
}
203-
204-
} else {
205-
//
206-
// wrappedModule.method(arg0, arg1, ...)
207-
// result.success(null)
208-
//
209-
invokeUnitMethodStatement()
210-
unitResultSuccessStatement()
211-
}
212-
} else {
213-
if (method.modifiers.contains(Modifier.SUSPEND)) {
214-
//
215-
// CoroutineScope(Dispatchers.Default).launch {
216-
// result.success(wrappedModule.method())
217-
// }
218-
//
219-
withCoroutineScopeControlFlow {
220-
invokeMethodStatement()
221-
}
222-
} else {
223-
//
224-
// result.success(wrappedModule.method())
225-
//
226-
invokeMethodStatement()
227-
}
228-
}
229-
endControlFlow()
230-
}
231-
232-
fun KSValueParameter.toParameterSpec(): ParameterSpec {
233-
return ParameterSpec.builder(
234-
this.name?.asString() ?: error("Parameter must have a name"),
235-
this.type.toTypeName()
236-
)
237-
.build()
238-
}
239131

240132

241133
/**
@@ -256,6 +148,7 @@ class AndroidModuleGenerator {
256148
private fun CodeBlock.Builder.addStateFlowCodeBlock(
257149
stateFlow: KSDeclaration,
258150
wrappedModuleVarName: String,
151+
resultStatement: (resultParameter: String) -> String = { "result.success($it)" },
259152
) {
260153
val flowTypeArgument = stateFlow.getStateFlowDeclarationFlowTypeArgument()
261154
val parameters = when (stateFlow) {
@@ -339,112 +232,23 @@ class AndroidModuleGenerator {
339232
}
340233
endControlFlow()
341234

342-
getKotlinSerialization(flowTypeArgument.declaration, "next", "json")
235+
getKotlinSerialization(
236+
flowTypeArgument.declaration,
237+
"next",
238+
"serializedNext",
239+
"json"
240+
)
241+
addStatement(resultStatement("serializedNext"))
343242

344243
endControlFlow()
345244
endControlFlow()
346245
}
347246
}
348247

349-
private fun CodeBlock.Builder.getKotlinSerialization(
350-
declaration: KSDeclaration,
351-
varName: String,
352-
jsonInstanceVarName: String? = null,
353-
) {
354-
if (declaration.requiresSerialization()) {
355-
require(jsonInstanceVarName != null)
356-
addStatement(
357-
"result.success(%L.%M(%L))",
358-
jsonInstanceVarName,
359-
encodeToString,
360-
varName,
361-
)
362-
} else when (declaration.qualifiedName?.asString()) {
363-
"kotlinx.datetime.Instant",
364-
"kotlinx.datetime.LocalDateTime",
365-
"kotlinx.datetime.LocalTime",
366-
"kotlinx.datetime.LocalDate" -> addStatement("result.success(%L.toString())", varName)
367-
"kotlin.time.Duration" -> addStatement("result.success(%L.toIsoString())", varName)
368-
else -> addStatement("result.success(%L)", varName)
369-
}
370-
}
371-
372-
private fun CodeBlock.Builder.getKotlinDeserialization(type: KSType, varName: String, assignTo: String) {
373-
if (type.declaration.requiresSerialization()) {
374-
addStatement(
375-
"val %L = %T.decodeFromString<%T>(%L as String)",
376-
assignTo,
377-
JsonClassName,
378-
type.toTypeName(),
379-
varName,
380-
)
381-
} else when (type.declaration.qualifiedName?.asString()) {
382-
"kotlinx.datetime.Instant" -> {
383-
addStatement(
384-
"val %L = %T.parse(%L as String)",
385-
assignTo,
386-
Instant,
387-
varName,
388-
)
389-
}
390-
"kotlinx.datetime.LocalDateTime" -> {
391-
addStatement(
392-
"val %L = %T.parse(%L as String)",
393-
assignTo,
394-
LocalDateTime,
395-
varName,
396-
)
397-
}
398-
"kotlinx.datetime.LocalDate" -> {
399-
addStatement(
400-
"val %L = %T.parse(%L as String)",
401-
assignTo,
402-
LocalDate,
403-
varName,
404-
)
405-
}
406-
"kotlinx.datetime.LocalTime" -> {
407-
addStatement(
408-
"val %L = %T.parse(%L as String)",
409-
assignTo,
410-
LocalTime,
411-
varName,
412-
)
413-
}
414-
"kotlin.time.Duration" -> {
415-
addStatement(
416-
"val %L = %T.parse(%L as String)",
417-
assignTo,
418-
Duration,
419-
varName,
420-
)
421-
}
422-
else -> addStatement(
423-
"val %L = %L as %T",
424-
assignTo,
425-
varName,
426-
type.toTypeName(),
427-
)
428-
}
429-
}
430-
431248
private val MethodCallHandler = ClassName("io.flutter.plugin.common.MethodChannel", "MethodCallHandler")
432249
private val BinaryMessenger = ClassName("io.flutter.plugin.common", "BinaryMessenger")
433250
private val MessageChannel = ClassName("io.flutter.plugin.common", "MethodChannel")
434251
private val EventChannel = ClassName("io.flutter.plugin.common", "EventChannel")
435252
private val MethodCall = ClassName("io.flutter.plugin.common", "MethodCall")
436253
private val MethodChannelResult = ClassName("io.flutter.plugin.common.MethodChannel", "Result")
437254
private val toEventStreamHandler = MemberName(flutterKmpPackageName, "toEventStreamHandler")
438-
439-
private val Duration = ClassName("kotlin.time", "Duration")
440-
private val Instant = ClassName("kotlinx.datetime", "Instant")
441-
private val LocalDate = ClassName("kotlinx.datetime", "LocalDate")
442-
private val LocalTime = ClassName("kotlinx.datetime", "LocalTime")
443-
private val LocalDateTime = ClassName("kotlinx.datetime", "LocalDateTime")
444-
private val Dispatchers = ClassName("kotlinx.coroutines", "Dispatchers")
445-
private val launch = MemberName("kotlinx.coroutines", "launch")
446-
private val CoroutineScope = ClassName("kotlinx.coroutines", "CoroutineScope")
447-
private val ListOfMember = MemberName("kotlin.collections", "listOf")
448-
private val JsonClassName = ClassName("kotlinx.serialization.json", "Json")
449-
private val encodeToString = MemberName("kotlinx.serialization", "encodeToString")
450-
private val first = MemberName("kotlinx.coroutines.flow", "first")

flutter-kmp-ksp/src/main/kotlin/de/voize/flutterkmp/ksp/processor/FlutterKMPSymbolProcessor.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,11 @@ class FlutterKMPSymbolProcessor(
156156
codeGenerator,
157157
)
158158
}
159-
160159
if (JvmPlatform !in platformNames && NativePlatform in platformNames) {
161-
IOSModuleGenerator().generateModule(
160+
IOSKotlinModuleGenerator().generateModule(
162161
flutterModule,
163162
primaryConstructorParameters,
163+
constructorInvocation,
164164
codeGenerator,
165165
)
166166
}

0 commit comments

Comments
 (0)