Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
erksch committed Jul 27, 2024
1 parent 2f6e355 commit 0d2b6ea
Show file tree
Hide file tree
Showing 11 changed files with 491 additions and 215 deletions.
50 changes: 50 additions & 0 deletions example/FlutterKmpExample.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
Pod::Spec.new do |spec|
spec.name = 'FlutterKmpExample'
spec.version = '0.1.0'
spec.homepage = 'https://github.com/voize-gmbh/flutter-kmp'
spec.source = { :http=> ''}
spec.authors = ''
spec.license = ''
spec.summary = 'Shared Kotlin code for flutter-kmp example'
spec.vendored_frameworks = 'build/cocoapods/framework/shared.framework'
spec.libraries = 'c++'



if !Dir.exist?('build/cocoapods/framework/shared.framework') || Dir.empty?('build/cocoapods/framework/shared.framework')
raise "
Kotlin framework 'shared' doesn't exist yet, so a proper Xcode project can't be generated.
'pod install' should be executed after running ':generateDummyFramework' Gradle task:
./gradlew :generateDummyFramework
Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)"
end

spec.pod_target_xcconfig = {
'KOTLIN_PROJECT_PATH' => '',
'PRODUCT_MODULE_NAME' => 'shared',
}

spec.script_phases = [
{
:name => 'Build FlutterKmpExample',
:execution_position => :before_compile,
:shell_path => '/bin/sh',
:script => <<-SCRIPT
if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
exit 0
fi
set -ev
REPO_ROOT="$PODS_TARGET_SRCROOT"
"$REPO_ROOT/gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
-Pkotlin.native.cocoapods.archs="$ARCHS" \
-Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
SCRIPT
}
]

end
38 changes: 34 additions & 4 deletions example/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinCocoapods)
alias(libs.plugins.androidLibrary)
alias(libs.plugins.ksp)
alias(libs.plugins.kotlinSerialization)
Expand All @@ -8,7 +9,6 @@ plugins {
val flutterKmpVersion = "0.1.0-rc.0"

kotlin {
targetHierarchy.default()
jvm()
androidTarget {
publishLibraryVariants("release")
Expand All @@ -18,9 +18,29 @@ kotlin {
}
}
}
iosX64()
iosArm64()
iosSimulatorArm64()

listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach {
it.binaries.framework {
baseName = "shared"
}
}

applyDefaultHierarchyTemplate()

cocoapods {
name = "FlutterKmpExample"
version = "0.1.0"

framework {
homepage = "https://github.com/voize-gmbh/flutter-kmp"
summary = "Shared Kotlin code for flutter-kmp example"
baseName = "shared"
}
}

sourceSets {
val androidMain by getting {
Expand All @@ -41,6 +61,16 @@ kotlin {
implementation(libs.kotlin.test)
}
}

val iosX64Main by getting {
kotlin.srcDir("build/generated/ksp/iosX64/iosX64Main/kotlin")
}
val iosArm64Main by getting {
kotlin.srcDir("build/generated/ksp/iosArm64/iosArm64Main/kotlin")
}
val iosSimulatorArm64Main by getting {
kotlin.srcDir("build/generated/ksp/iosSimulatorArm64/iosSimulatorArm64Main/kotlin")
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions example/flutter/ios/Classes/FlutterKmpExamplePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import UIKit

public class FlutterKmpExamplePlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {

let myTestClass = MyTestClassIOS()
let channel = FlutterMethodChannel(name: "flutter_kmp_example", binaryMessenger: registrar.messenger())
let instance = FlutterKmpExamplePlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
Expand Down
1 change: 1 addition & 0 deletions example/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version
[plugins]
androidLibrary = { id = "com.android.library", version.ref = "agp" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlinCocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" }
kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
Original file line number Diff line number Diff line change
Expand Up @@ -128,114 +128,6 @@ class AndroidModuleGenerator {
fileSpec.writeTo(codeGenerator, false)
}

private fun CodeBlock.Builder.addMethodCodeBlock(
method: KSFunctionDeclaration,
wrappedModuleVarName: String,
) {
beginControlFlow("%S ->", method.simpleName.asString())

if (method.parameters.isNotEmpty()) {
//
// val arguments = call.arguments as List<*>
// val arg0 = arguments[0] as Arg0Type
// ...
//
addStatement("val arguments = call.arguments as List<*>")
method.parameters.forEachIndexed { index, parameter ->
getKotlinDeserialization(
parameter.type.resolve(),
"arguments[$index]",
"arg$index",
)
}
}

fun withCoroutineScopeControlFlow(block: CodeBlock.Builder.() -> Unit) {
beginControlFlow(
"%T(%T.Default).%M",
CoroutineScope,
Dispatchers,
launch,
)
block()
endControlFlow()
}

fun invokeUnitMethodStatement() {
addStatement(
"%N.%L(%L)",
wrappedModuleVarName,
method.simpleName.asString(),
method.parameters.indices.joinToString(", ") { "arg$it" }
)
}

fun invokeMethodStatement() {
addStatement(
"val resultData = %N.%L(%L)",
wrappedModuleVarName,
method.simpleName.asString(),
method.parameters.indices.joinToString(", ") { "arg$it" }
)

val returnTypeDeclaration = (method.returnType ?: error("return type is null")).resolve().declaration

addStatement("val json = %T { encodeDefaults = true }", JsonClassName)
getKotlinSerialization(returnTypeDeclaration, "resultData", "json")
}

fun unitResultSuccessStatement() {
addStatement("result.success(null)")
}

if (method.returnsUnit()) {
if (method.modifiers.contains(Modifier.SUSPEND)) {
//
// CoroutineScope(Dispatchers.Default).launch {
// wrappedModule.method(arg0, arg1, ...)
// result.success(null)
// }
//
withCoroutineScopeControlFlow {
invokeUnitMethodStatement()
unitResultSuccessStatement()
}

} else {
//
// wrappedModule.method(arg0, arg1, ...)
// result.success(null)
//
invokeUnitMethodStatement()
unitResultSuccessStatement()
}
} else {
if (method.modifiers.contains(Modifier.SUSPEND)) {
//
// CoroutineScope(Dispatchers.Default).launch {
// result.success(wrappedModule.method())
// }
//
withCoroutineScopeControlFlow {
invokeMethodStatement()
}
} else {
//
// result.success(wrappedModule.method())
//
invokeMethodStatement()
}
}
endControlFlow()
}

fun KSValueParameter.toParameterSpec(): ParameterSpec {
return ParameterSpec.builder(
this.name?.asString() ?: error("Parameter must have a name"),
this.type.toTypeName()
)
.build()
}


/**
Expand All @@ -256,6 +148,7 @@ class AndroidModuleGenerator {
private fun CodeBlock.Builder.addStateFlowCodeBlock(
stateFlow: KSDeclaration,
wrappedModuleVarName: String,
resultStatement: (resultParameter: String) -> String = { "result.success($it)" },
) {
val flowTypeArgument = stateFlow.getStateFlowDeclarationFlowTypeArgument()
val parameters = when (stateFlow) {
Expand Down Expand Up @@ -339,112 +232,23 @@ class AndroidModuleGenerator {
}
endControlFlow()

getKotlinSerialization(flowTypeArgument.declaration, "next", "json")
getKotlinSerialization(
flowTypeArgument.declaration,
"next",
"serializedNext",
"json"
)
addStatement(resultStatement("serializedNext"))

endControlFlow()
endControlFlow()
}
}

private fun CodeBlock.Builder.getKotlinSerialization(
declaration: KSDeclaration,
varName: String,
jsonInstanceVarName: String? = null,
) {
if (declaration.requiresSerialization()) {
require(jsonInstanceVarName != null)
addStatement(
"result.success(%L.%M(%L))",
jsonInstanceVarName,
encodeToString,
varName,
)
} else when (declaration.qualifiedName?.asString()) {
"kotlinx.datetime.Instant",
"kotlinx.datetime.LocalDateTime",
"kotlinx.datetime.LocalTime",
"kotlinx.datetime.LocalDate" -> addStatement("result.success(%L.toString())", varName)
"kotlin.time.Duration" -> addStatement("result.success(%L.toIsoString())", varName)
else -> addStatement("result.success(%L)", varName)
}
}

private fun CodeBlock.Builder.getKotlinDeserialization(type: KSType, varName: String, assignTo: String) {
if (type.declaration.requiresSerialization()) {
addStatement(
"val %L = %T.decodeFromString<%T>(%L as String)",
assignTo,
JsonClassName,
type.toTypeName(),
varName,
)
} else when (type.declaration.qualifiedName?.asString()) {
"kotlinx.datetime.Instant" -> {
addStatement(
"val %L = %T.parse(%L as String)",
assignTo,
Instant,
varName,
)
}
"kotlinx.datetime.LocalDateTime" -> {
addStatement(
"val %L = %T.parse(%L as String)",
assignTo,
LocalDateTime,
varName,
)
}
"kotlinx.datetime.LocalDate" -> {
addStatement(
"val %L = %T.parse(%L as String)",
assignTo,
LocalDate,
varName,
)
}
"kotlinx.datetime.LocalTime" -> {
addStatement(
"val %L = %T.parse(%L as String)",
assignTo,
LocalTime,
varName,
)
}
"kotlin.time.Duration" -> {
addStatement(
"val %L = %T.parse(%L as String)",
assignTo,
Duration,
varName,
)
}
else -> addStatement(
"val %L = %L as %T",
assignTo,
varName,
type.toTypeName(),
)
}
}

private val MethodCallHandler = ClassName("io.flutter.plugin.common.MethodChannel", "MethodCallHandler")
private val BinaryMessenger = ClassName("io.flutter.plugin.common", "BinaryMessenger")
private val MessageChannel = ClassName("io.flutter.plugin.common", "MethodChannel")
private val EventChannel = ClassName("io.flutter.plugin.common", "EventChannel")
private val MethodCall = ClassName("io.flutter.plugin.common", "MethodCall")
private val MethodChannelResult = ClassName("io.flutter.plugin.common.MethodChannel", "Result")
private val toEventStreamHandler = MemberName(flutterKmpPackageName, "toEventStreamHandler")

private val Duration = ClassName("kotlin.time", "Duration")
private val Instant = ClassName("kotlinx.datetime", "Instant")
private val LocalDate = ClassName("kotlinx.datetime", "LocalDate")
private val LocalTime = ClassName("kotlinx.datetime", "LocalTime")
private val LocalDateTime = ClassName("kotlinx.datetime", "LocalDateTime")
private val Dispatchers = ClassName("kotlinx.coroutines", "Dispatchers")
private val launch = MemberName("kotlinx.coroutines", "launch")
private val CoroutineScope = ClassName("kotlinx.coroutines", "CoroutineScope")
private val ListOfMember = MemberName("kotlin.collections", "listOf")
private val JsonClassName = ClassName("kotlinx.serialization.json", "Json")
private val encodeToString = MemberName("kotlinx.serialization", "encodeToString")
private val first = MemberName("kotlinx.coroutines.flow", "first")
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,11 @@ class FlutterKMPSymbolProcessor(
codeGenerator,
)
}

if (JvmPlatform !in platformNames && NativePlatform in platformNames) {
IOSModuleGenerator().generateModule(
IOSKotlinModuleGenerator().generateModule(
flutterModule,
primaryConstructorParameters,
constructorInvocation,
codeGenerator,
)
}
Expand Down
Loading

0 comments on commit 0d2b6ea

Please sign in to comment.