Skip to content

Commit

Permalink
add event channels and sealed inheritance
Browse files Browse the repository at this point in the history
  • Loading branch information
tarrinneal committed Oct 25, 2024
1 parent e0c4f55 commit 959374c
Show file tree
Hide file tree
Showing 46 changed files with 4,093 additions and 311 deletions.
7 changes: 7 additions & 0 deletions packages/pigeon/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 22.7.0

* [swift] Adds Event Channel support.
* [swift] Adds `sealed` class inheritance support.
* [kotlin] Adds Event Channel support.
* [kotlin] Adds `sealed` class inheritance support.

## 22.6.0

* [swift] Adds `includeErrorClass` to `SwiftOptions`.
Expand Down
7 changes: 5 additions & 2 deletions packages/pigeon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Pigeon uses the `StandardMessageCodec` so it supports

Custom classes, nested datatypes, and enums are also supported.

Basic inheritance with empty `sealed` parent classes is allowed only on swift and kotlin.

Nullable enums in Objective-C generated code will be wrapped in a class to allow for nullability.

By default, custom classes in Swift are defined as structs.
Expand Down Expand Up @@ -104,8 +106,9 @@ to the api to allow for multiple instances to be created and operate in parallel
1) Method declarations on the API classes should have arguments and a return
value whose types are defined in the file, are supported datatypes, or are
`void`.
1) Generics are supported, but can currently only be used with nullable types
(example: `List<int?>`).
1) Event Channels are supported only on swift and kotlin generators.
1) Event Channel methods should be wrapped in an `abstract class` with the metadata `@EventChannelApi`.
1) Event Channel definitions should not include the `Stream` return type, just the type that is being streamed.
1) Objc and Swift have special naming conventions that can be utilized with the
`@ObjCSelector` and `@SwiftFunction` respectively.

Expand Down
87 changes: 87 additions & 0 deletions packages/pigeon/example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,93 @@ pigeon_example_package_message_flutter_api_flutter_method(
self->flutter_api, "hello", nullptr, flutter_method_cb, self);
```
## Event Channel Example
This example gives a basic overview of how to use Pigeon to set up an Event Channel method.
### Dart input
<?code-excerpt "pigeons/messages.dart (event-definitions)"?>
```dart
@EventChannelApi()
abstract class EventApi {
SealedBaseClass streamEvents();
}
```

### Dart

The generated dart code will include a method that returns a `Stream` when invoked.
This `Stream` can then be used as any other `Stream` would be in dart.

<?code-excerpt "lib/main.dart (main-dart-event)"?>
```dart
final Stream<SealedBaseClass> events = streamEvents();
```

### Swift

<?code-excerpt "ios/Runner/AppDelegate.swift (swift-class-event)"?>
```swift
class SendEvents: StreamEventsStreamHandler {
var timerActive = false

override func onListen(withArguments arguments: Any?, sink: PigeonEventSink<SealedBaseClass>) {
var count = 0
if !timerActive {
timerActive = true
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
if count >= 10 {
sink.endOfStream()
} else {
if (count % 2) == 0 {
sink.success(IntEvent(data: Int64(count)))
} else {
sink.success(StringEvent(data: String(count)))
}
count += 1
}
}
}
}
}
```

### Kotlin

<?code-excerpt "android/app/src/main/kotlin/dev/flutter/pigeon_example_app/MainActivity.kt (kotlin-class-event)"?>
```kotlin
object SendClass : StreamEventsStreamHandler() {
val handler = Handler(Looper.getMainLooper())

override fun onListen(p0: Any?, sink: PigeonEventSink<SealedBaseClass>) {
var count: Int = 0
val r: Runnable =
object : Runnable {
override fun run() {
if (count >= 10) {
sink.endOfStream()
} else {
if (count % 2 == 0) {
handler.post {
sink.success(IntEvent(count.toLong()))
count++
}
} else {
handler.post {
sink.success(StringEvent(count.toString()))
count++
}
}
handler.postDelayed(this, 1000)
}
}
}
handler.postDelayed(r, 1000)
}
}
```

## Swift / Kotlin Plugin Example

A downloadable example of using Pigeon to create a Flutter Plugin with Swift and
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Autogenerated from Pigeon, do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")

import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.common.StandardMethodCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer

/**
* Generated class from Pigeon that represents data sent in messages. This class should not be
* extended by any user class outside of the generated file.
*/
sealed class SealedBaseClass
/** Generated class from Pigeon that represents data sent in messages. */
data class IntEvent(val data: Long) : SealedBaseClass() {
companion object {
fun fromList(pigeonVar_list: List<Any?>): IntEvent {
val data = pigeonVar_list[0] as Long
return IntEvent(data)
}
}

fun toList(): List<Any?> {
return listOf(
data,
)
}
}

/** Generated class from Pigeon that represents data sent in messages. */
data class StringEvent(val data: String) : SealedBaseClass() {
companion object {
fun fromList(pigeonVar_list: List<Any?>): StringEvent {
val data = pigeonVar_list[0] as String
return StringEvent(data)
}
}

fun toList(): List<Any?> {
return listOf(
data,
)
}
}

private open class EventChannelMessagesPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
129.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { IntEvent.fromList(it) }
}
130.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { StringEvent.fromList(it) }
}
else -> super.readValueOfType(type, buffer)
}
}

override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is IntEvent -> {
stream.write(129)
writeValue(stream, value.toList())
}
is StringEvent -> {
stream.write(130)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
}

val EventChannelMessagesPigeonMethodCodec = StandardMethodCodec(EventChannelMessagesPigeonCodec())

private class PigeonStreamHandler<T>(val wrapper: PigeonEventChannelWrapper<T>) :
EventChannel.StreamHandler {
var pigeonSink: PigeonEventSink<T>? = null

override fun onListen(p0: Any?, sink: EventChannel.EventSink) {
pigeonSink = PigeonEventSink<T>(sink)
wrapper.onListen(p0, pigeonSink!!)
}

override fun onCancel(p0: Any?) {
pigeonSink = null
wrapper.onCancel(p0)
}
}

interface PigeonEventChannelWrapper<T> {
open fun onListen(p0: Any?, sink: PigeonEventSink<T>) {}

open fun onCancel(p0: Any?) {}
}

class PigeonEventSink<T>(private val sink: EventChannel.EventSink) {
fun success(value: T) {
sink.success(value)
}

fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
sink.error(errorCode, errorMessage, errorDetails)
}

fun endOfStream() {
sink.endOfStream()
}
}

abstract class StreamEventsStreamHandler : PigeonEventChannelWrapper<SealedBaseClass> {
companion object {
fun register(
messenger: BinaryMessenger,
wrapper: StreamEventsStreamHandler,
instanceName: String = ""
) {
var channelName: String = "dev.flutter.pigeon.pigeon_example_package.EventApi.streamEvents"
if (instanceName.isNotEmpty()) {
channelName += ".$instanceName"
}
val streamHandler = PigeonStreamHandler<SealedBaseClass>(wrapper)
EventChannel(messenger, channelName, EventChannelMessagesPigeonMethodCodec)
.setStreamHandler(streamHandler)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ package dev.flutter.pigeon_example_app

import ExampleHostApi
import FlutterError
import IntEvent
import MessageData
import MessageFlutterApi
import androidx.annotation.NonNull
import PigeonEventSink
import SealedBaseClass
import StreamEventsStreamHandler
import StringEvent
import android.os.Handler
import android.os.Looper
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.plugins.FlutterPlugin
Expand Down Expand Up @@ -37,25 +43,58 @@ private class PigeonApiImplementation : ExampleHostApi {
// #enddocregion kotlin-class

// #docregion kotlin-class-flutter
private class PigeonFlutterApi {
private class PigeonFlutterApi(binding: FlutterPlugin.FlutterPluginBinding) {

var flutterApi: MessageFlutterApi? = null

constructor(binding: FlutterPlugin.FlutterPluginBinding) {
flutterApi = MessageFlutterApi(binding.getBinaryMessenger())
init {
flutterApi = MessageFlutterApi(binding.binaryMessenger)
}

fun callFlutterMethod(aString: String, callback: (Result<String>) -> Unit) {
flutterApi!!.flutterMethod(aString) { echo -> callback(Result.success(echo)) }
flutterApi!!.flutterMethod(aString) { echo -> callback(echo) }
}
}
// #enddocregion kotlin-class-flutter

// #docregion kotlin-class-event
object SendClass : StreamEventsStreamHandler() {
val handler = Handler(Looper.getMainLooper())

override fun onListen(p0: Any?, sink: PigeonEventSink<SealedBaseClass>) {
var count: Int = 0
val r: Runnable =
object : Runnable {
override fun run() {
if (count >= 10) {
sink.endOfStream()
} else {
if (count % 2 == 0) {
handler.post {
sink.success(IntEvent(count.toLong()))
count++
}
} else {
handler.post {
sink.success(StringEvent(count.toString()))
count++
}
}
handler.postDelayed(this, 1000)
}
}
}
handler.postDelayed(r, 1000)
}
}
// #enddocregion kotlin-class-event

class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)

val api = PigeonApiImplementation()
ExampleHostApi.setUp(flutterEngine.dartExecutor.binaryMessenger, api)
StreamEventsStreamHandler.register(flutterEngine.dartExecutor.binaryMessenger, SendClass)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3368472729F02D040090029A /* Messages.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3368472629F02D040090029A /* Messages.g.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
477F5F842CCC1D8D006725C4 /* EventChannelMessages.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = 477F5F832CCC1D8D006725C4 /* EventChannelMessages.g.swift */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
Expand All @@ -34,6 +35,7 @@
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3368472629F02D040090029A /* Messages.g.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Messages.g.swift; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
477F5F832CCC1D8D006725C4 /* EventChannelMessages.g.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventChannelMessages.g.swift; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
Expand Down Expand Up @@ -88,6 +90,7 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
477F5F832CCC1D8D006725C4 /* EventChannelMessages.g.swift */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
Expand Down Expand Up @@ -213,6 +216,7 @@
3368472729F02D040090029A /* Messages.g.swift in Sources */,
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
477F5F842CCC1D8D006725C4 /* EventChannelMessages.g.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
Loading

0 comments on commit 959374c

Please sign in to comment.