Skip to content

Commit db28ed3

Browse files
committed
Support for flows
1 parent df0940d commit db28ed3

File tree

7 files changed

+149
-70
lines changed

7 files changed

+149
-70
lines changed

example/flutter/example/ios/Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@ SPEC CHECKSUMS:
3030
FlutterKmpExample: 602f302c485b58e590be3d655f462b4962cdc4e3
3131
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
3232

33-
PODFILE CHECKSUM: 5f87884a088ccef5303b5a817b583138a43ec3e0
33+
PODFILE CHECKSUM: e7404f0a25823bf8f7692369a15b9da345aa36ad
3434

3535
COCOAPODS: 1.16.2

example/flutter/ios/Classes/FlutterKmpExamplePlugin.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public class FlutterKmpExamplePlugin: NSObject, FlutterPlugin {
99
public static func register(with registrar: FlutterPluginRegistrar) {
1010
let instance = FlutterKmpExamplePlugin()
1111

12-
MyTestClassIOS.companion.register(
12+
myTestClass.register(
1313
registrar: registrar,
1414
pluginInstance: instance
1515
)

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

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,16 @@ import com.google.devtools.ksp.symbol.KSDeclaration
55
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
66
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
77
import com.google.devtools.ksp.symbol.KSValueParameter
8-
import com.google.devtools.ksp.symbol.Modifier
98
import com.squareup.kotlinpoet.ClassName
109
import com.squareup.kotlinpoet.CodeBlock
1110
import com.squareup.kotlinpoet.FileSpec
1211
import com.squareup.kotlinpoet.FunSpec
1312
import com.squareup.kotlinpoet.KModifier
13+
import com.squareup.kotlinpoet.MemberName
1414
import com.squareup.kotlinpoet.PropertySpec
1515
import com.squareup.kotlinpoet.TypeSpec
1616
import com.squareup.kotlinpoet.ksp.toTypeName
1717
import com.squareup.kotlinpoet.ksp.writeTo
18-
import io.outfoxx.swiftpoet.DeclaredTypeName
1918

2019
class IOSKotlinModuleGenerator {
2120
private fun String.iosModuleClassName() = this + "IOS"
@@ -32,7 +31,7 @@ class IOSKotlinModuleGenerator {
3231
val wrappedModuleVarName = "wrappedModule"
3332
val pluginInstanceConstructorArgName = "pluginInstance"
3433
val registrarConstructorArgName = "registrar"
35-
val channelVarName = "channel"
34+
val methodChannelVarName = "methodChannel"
3635

3736
val classSpec = TypeSpec.classBuilder(className).apply {
3837
addAnnotation(ExperimentalForeignApi)
@@ -50,34 +49,42 @@ class IOSKotlinModuleGenerator {
5049
ClassName(packageName, wrappedClassName)
5150
).addModifiers(KModifier.PRIVATE).initializer(constructorInvocation).build()
5251
)
53-
.addType(
54-
TypeSpec.companionObjectBuilder()
55-
.addFunction(
56-
FunSpec.builder("register")
57-
.addParameter(
58-
registrarConstructorArgName,
59-
FlutterPluginRegistrar,
52+
.addFunction(
53+
FunSpec.builder("register")
54+
.addParameter(
55+
registrarConstructorArgName,
56+
FlutterPluginRegistrar,
57+
)
58+
.addParameter(pluginInstanceConstructorArgName, FlutterPlugin)
59+
.addCode(
60+
CodeBlock.builder().apply {
61+
addStatement(
62+
"val $methodChannelVarName = %T(%S, %L, %T.sharedInstance())",
63+
FlutterMethodChannel,
64+
flutterModule.moduleName,
65+
"$registrarConstructorArgName.messenger()",
66+
FlutterStandardMethodCodec
6067
)
61-
.addParameter(pluginInstanceConstructorArgName, FlutterPlugin)
62-
.addCode(
63-
CodeBlock.builder().apply {
64-
addStatement(
65-
"val $channelVarName = %T(%S, %L, %T.sharedInstance())",
66-
FlutterMethodChannel,
67-
flutterModule.moduleName,
68-
"$registrarConstructorArgName.messenger()",
69-
FlutterStandardMethodCodec
70-
)
71-
addStatement(
72-
"%L.addMethodCallDelegate(%L as %T, %L)",
73-
registrarConstructorArgName,
74-
pluginInstanceConstructorArgName,
75-
NSObject,
76-
channelVarName,
77-
)
78-
}.build()
68+
addStatement(
69+
"%L.addMethodCallDelegate(%L as %T, %L)",
70+
registrarConstructorArgName,
71+
pluginInstanceConstructorArgName,
72+
NSObject,
73+
methodChannelVarName,
7974
)
80-
.build()
75+
flutterModule.flutterFlows.forEach {
76+
addStatement(
77+
"%T(%S, %L, %T.sharedInstance()).setStreamHandler(%N.%M.%M())",
78+
FlutterEventChannel,
79+
"${flutterModule.moduleName}_${it.simpleName.asString()}",
80+
"$registrarConstructorArgName.messenger()",
81+
FlutterStandardMethodCodec,
82+
wrappedModuleVarName,
83+
MemberName(packageName, it.simpleName.asString()),
84+
toEventStreamHandler,
85+
)
86+
}
87+
}.build()
8188
)
8289
.build()
8390
)
@@ -244,6 +251,8 @@ private val NSObject = ClassName("platform.darwin", "NSObject")
244251
private val FlutterResult = ClassName("cocoapods.Flutter", "FlutterResult")
245252
private val FlutterMethodCall = ClassName("cocoapods.Flutter", "FlutterMethodCall")
246253
private val FlutterMethodChannel = ClassName("cocoapods.Flutter", "FlutterMethodChannel")
254+
private val FlutterEventChannel = ClassName("cocoapods.Flutter", "FlutterEventChannel")
247255
private val FlutterPluginRegistrar = ClassName("cocoapods.Flutter", "FlutterPluginRegistrarProtocol")
248256
private val FlutterPlugin = ClassName("cocoapods.Flutter", "FlutterPluginProtocol")
249257
private val FlutterStandardMethodCodec = ClassName("cocoapods.Flutter", "FlutterStandardMethodCodec")
258+
private val toEventStreamHandler = MemberName(flutterKmpPackageName, "toEventStreamHandler")

flutter-kmp/build.gradle.kts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
plugins {
22
kotlin("multiplatform")
33
kotlin("plugin.serialization")
4+
kotlin("native.cocoapods")
45
id("com.android.library")
56
}
67

@@ -29,6 +30,12 @@ kotlin {
2930
publishLibraryVariants("release")
3031
}
3132

33+
cocoapods {
34+
pod("Flutter")
35+
noPodspec()
36+
}
37+
38+
3239
iosX64()
3340
iosArm64()
3441
iosSimulatorArm64()

flutter-kmp/src/androidMain/kotlin/de/voize/flutterkmp/utils.kt

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -13,45 +13,6 @@ import kotlinx.serialization.json.*
1313
import kotlinx.serialization.modules.serializersModuleOf
1414
import kotlinx.serialization.serializer
1515

16-
private class DynamicLookupSerializer : KSerializer<Any> {
17-
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Any")
18-
19-
override fun serialize(encoder: Encoder, value: Any) {
20-
val jsonEncoder = encoder as JsonEncoder
21-
val jsonElement = serializeAny(value)
22-
jsonEncoder.encodeJsonElement(jsonElement)
23-
}
24-
25-
private fun serializeAny(value: Any?): JsonElement = when (value) {
26-
null -> JsonNull
27-
is Map<*, *> -> {
28-
val mapContents = value.entries.associate { mapEntry ->
29-
mapEntry.key.toString() to serializeAny(mapEntry.value)
30-
}
31-
JsonObject(mapContents)
32-
}
33-
34-
is List<*> -> {
35-
val arrayContents = value.map { listEntry -> serializeAny(listEntry) }
36-
JsonArray(arrayContents)
37-
}
38-
39-
is Number -> JsonPrimitive(value)
40-
is Boolean -> JsonPrimitive(value)
41-
is String -> JsonPrimitive(value)
42-
else -> error("Unsupported type ${value::class}")
43-
}
44-
45-
override fun deserialize(decoder: Decoder): Any {
46-
error("Unsupported")
47-
}
48-
}
49-
50-
private val flowToFlutterJson = Json {
51-
serializersModule = serializersModuleOf(DynamicLookupSerializer())
52-
encodeDefaults = true
53-
}
54-
5516
inline fun <reified T> Flow<T>.toEventStreamHandler(): EventChannel.StreamHandler =
5617
toEventStreamHandler(serializer<T>())
5718

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package de.voize.flutterkmp
2+
3+
import kotlinx.serialization.KSerializer
4+
import kotlinx.serialization.descriptors.SerialDescriptor
5+
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
6+
import kotlinx.serialization.encoding.Decoder
7+
import kotlinx.serialization.encoding.Encoder
8+
import kotlinx.serialization.json.Json
9+
import kotlinx.serialization.json.JsonArray
10+
import kotlinx.serialization.json.JsonElement
11+
import kotlinx.serialization.json.JsonEncoder
12+
import kotlinx.serialization.json.JsonNull
13+
import kotlinx.serialization.json.JsonObject
14+
import kotlinx.serialization.json.JsonPrimitive
15+
import kotlinx.serialization.modules.serializersModuleOf
16+
17+
internal class DynamicLookupSerializer : KSerializer<Any> {
18+
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Any")
19+
20+
override fun serialize(encoder: Encoder, value: Any) {
21+
val jsonEncoder = encoder as JsonEncoder
22+
val jsonElement = serializeAny(value)
23+
jsonEncoder.encodeJsonElement(jsonElement)
24+
}
25+
26+
private fun serializeAny(value: Any?): JsonElement = when (value) {
27+
null -> JsonNull
28+
is Map<*, *> -> {
29+
val mapContents = value.entries.associate { mapEntry ->
30+
mapEntry.key.toString() to serializeAny(mapEntry.value)
31+
}
32+
JsonObject(mapContents)
33+
}
34+
35+
is List<*> -> {
36+
val arrayContents = value.map { listEntry -> serializeAny(listEntry) }
37+
JsonArray(arrayContents)
38+
}
39+
40+
is Number -> JsonPrimitive(value)
41+
is Boolean -> JsonPrimitive(value)
42+
is String -> JsonPrimitive(value)
43+
else -> error("Unsupported type ${value::class}")
44+
}
45+
46+
override fun deserialize(decoder: Decoder): Any {
47+
error("Unsupported")
48+
}
49+
}
50+
51+
internal val flowToFlutterJson = Json {
52+
serializersModule = serializersModuleOf(DynamicLookupSerializer())
53+
encodeDefaults = true
54+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package de.voize.flutterkmp
2+
3+
import cocoapods.Flutter.FlutterError
4+
import cocoapods.Flutter.FlutterEventSink
5+
import kotlinx.coroutines.flow.Flow
6+
import cocoapods.Flutter.FlutterStreamHandlerProtocol
7+
import kotlinx.coroutines.CoroutineScope
8+
import kotlinx.coroutines.Dispatchers
9+
import kotlinx.coroutines.Job
10+
import kotlinx.coroutines.launch
11+
import kotlinx.coroutines.withContext
12+
import kotlinx.serialization.SerializationStrategy
13+
import kotlinx.serialization.serializer
14+
import platform.darwin.NSObject
15+
16+
17+
inline fun <reified T> Flow<T>.toEventStreamHandler(): NSObject =
18+
toEventStreamHandler(serializer<T>())
19+
20+
fun <T> Flow<T>.toEventStreamHandler(serializer: SerializationStrategy<T>): NSObject {
21+
return object : FlutterStreamHandlerProtocol, NSObject() {
22+
var job: Job? = null
23+
24+
override fun onListenWithArguments(
25+
arguments: Any?,
26+
eventSink: FlutterEventSink,
27+
): FlutterError? {
28+
if (eventSink == null) {
29+
return FlutterError.errorWithCode("no_event_sink", "No event sink available", null)
30+
}
31+
32+
job = CoroutineScope(Dispatchers.Default).launch {
33+
this@toEventStreamHandler.collect {
34+
withContext(Dispatchers.Main) {
35+
eventSink(flowToFlutterJson.encodeToString(serializer, it))
36+
}
37+
}
38+
}
39+
return null
40+
}
41+
42+
override fun onCancelWithArguments(arguments: Any?): FlutterError? {
43+
job?.cancel()
44+
job = null
45+
return null
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)