Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pigeon] adds event channel support for kotlin and swift #7892

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
tarrinneal marked this conversation as resolved.
Show resolved Hide resolved

## 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.
tarrinneal marked this conversation as resolved.
Show resolved Hide resolved

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.
tarrinneal marked this conversation as resolved.
Show resolved Hide resolved
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
tarrinneal marked this conversation as resolved.
Show resolved Hide resolved
`@ObjCSelector` and `@SwiftFunction` respectively.

Expand Down
99 changes: 93 additions & 6 deletions packages/pigeon/example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,10 +281,10 @@ private class PigeonFlutterApi {
}

func callFlutterMethod(
aString aStringArg: String?, completion: @escaping (Result<String, Error>) -> Void
aString aStringArg: String?, completion: @escaping (Result<String, PigeonError>) -> Void
) {
flutterAPI.flutterMethod(aString: aStringArg) {
completion(.success($0))
completion($0)
}
}
}
Expand All @@ -294,16 +294,16 @@ private class PigeonFlutterApi {

<?code-excerpt "android/app/src/main/kotlin/dev/flutter/pigeon_example_app/MainActivity.kt (kotlin-class-flutter)"?>
```kotlin
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) }
}
}
```
Expand Down 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.
tarrinneal marked this conversation as resolved.
Show resolved Hide resolved

### Dart input

<?code-excerpt "pigeons/event_channel_messages.dart (event-definitions)"?>
```dart
@EventChannelApi()
abstract class EventApi {
SealedBaseClass streamEvents();
tarrinneal marked this conversation as resolved.
Show resolved Hide resolved
}
```

### Dart

The generated dart code will include a method that returns a `Stream` when invoked.
tarrinneal marked this conversation as resolved.
Show resolved Hide resolved
This `Stream` can then be used as any other `Stream` would be in dart.
tarrinneal marked this conversation as resolved.
Show resolved Hide resolved

<?code-excerpt "lib/main.dart (main-dart-event)"?>
```dart
final Stream<SealedBaseClass> events = streamEvents();
tarrinneal marked this conversation as resolved.
Show resolved Hide resolved
```

### Swift
tarrinneal marked this conversation as resolved.
Show resolved Hide resolved

<?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
tarrinneal marked this conversation as resolved.
Show resolved Hide resolved
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
2 changes: 2 additions & 0 deletions packages/pigeon/example/app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/

# IntelliJ related
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,
tarrinneal marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>11.0</string>
<string>12.0</string>
</dict>
</plist>
Loading