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 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
959374c
add event channels and sealed inheritance
tarrinneal Oct 25, 2024
36ccab4
tools
tarrinneal Oct 28, 2024
b630ae2
missed file
tarrinneal Oct 28, 2024
94d39cf
more ios files
tarrinneal Oct 28, 2024
972f522
test fix?
tarrinneal Oct 30, 2024
741f59d
maybe this?
tarrinneal Oct 30, 2024
a1f2b6a
adding prints to diagnose
tarrinneal Oct 30, 2024
1aeb65a
I'm sure this one will work
tarrinneal Oct 31, 2024
b0262a1
add bad formatting to check for failure
tarrinneal Nov 11, 2024
e306b46
reorder file format
tarrinneal Nov 11, 2024
2cb6973
revert change generators to test
tarrinneal Nov 12, 2024
a0f37e3
requested changes
tarrinneal Nov 12, 2024
6f60717
fix swift channel name bug and update examples to be clearer
tarrinneal Nov 13, 2024
5850199
less targeted podspec skip
tarrinneal Nov 13, 2024
2d4d262
old school cool swift if let
tarrinneal Nov 13, 2024
7e300bc
Merge branch 'main' of github.com:flutter/packages into event-channels
tarrinneal Nov 13, 2024
516e14e
excerpts and podspec attempt
tarrinneal Nov 13, 2024
a8b07fe
nits and such
tarrinneal Nov 22, 2024
3376871
Merge branch 'main' of github.com:flutter/packages into event-channels
tarrinneal Nov 22, 2024
2b6f619
change examples to not crash on windows/linux
tarrinneal Nov 26, 2024
8e6f4cb
remove final wrapper
tarrinneal Nov 26, 2024
9b4a27b
one too many
tarrinneal Nov 26, 2024
ab4aa48
rename and more doc comments
tarrinneal Nov 27, 2024
060ae65
fix example class name
tarrinneal Nov 27, 2024
7a79da6
missed one
tarrinneal Dec 3, 2024
8bf12a7
Merge branch 'main' of github.com:flutter/packages into event-channels
tarrinneal Dec 3, 2024
a24d5b4
format
tarrinneal Dec 3, 2024
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
5 changes: 5 additions & 0 deletions packages/pigeon/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 22.7.0

* [swift, kotlin] Adds event channel support.
* [swift, kotlin] Adds `sealed` class inheritance support.

## 22.6.1

* [gobject] Moves class declarations to the header to work around a bug in some
Expand Down
9 changes: 6 additions & 3 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 in the Swift, Kotlin, and Dart generators.

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,9 +106,10 @@ 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) Objc and Swift have special naming conventions that can be utilized with the
1) Event channels are supported only on the Swift, Kotlin, and Dart 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) Objective-C and Swift have special naming conventions that can be utilized with the
`@ObjCSelector` and `@SwiftFunction` respectively.

### Flutter calling into iOS steps
Expand Down
104 changes: 98 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,98 @@ 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.

### Dart input

<?code-excerpt "pigeons/event_channel_messages.dart (event-definitions)"?>
```dart
@EventChannelApi()
abstract class PlatformEvent {
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.
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
Stream<String> getEventStream() async* {
final Stream<SealedBaseClass> events = streamEvents();
await for (final SealedBaseClass event in events) {
switch (event) {
case IntEvent():
final int intData = event.data;
yield '$intData, ';
case StringEvent():
final String stringData = event.data;
yield '$stringData, ';
}
}
}
```

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

<?code-excerpt "ios/Runner/AppDelegate.swift (swift-class-event)"?>
```swift
class EventListener: StreamEventsStreamHandler {
var eventSink: PigeonEventSink<SealedBaseClass>?

override func onListen(withArguments arguments: Any?, sink: PigeonEventSink<SealedBaseClass>) {
eventSink = sink
}

func onIntEvent(event: Int64) {
if let eventSink = eventSink {
eventSink.success(IntEvent(data: event))
}
}

func onStringEvent(event: String) {
if let eventSink = eventSink {
eventSink.success(StringEvent(data: event))
}
}

func onEventsDone() {
eventSink?.endOfStream()
eventSink = nil
}
}
```

### Kotlin

<?code-excerpt "android/app/src/main/kotlin/dev/flutter/pigeon_example_app/MainActivity.kt (kotlin-class-event)"?>
```kotlin
class EventListener : StreamEventsStreamHandler() {
private var eventSink: PigeonEventSink<SealedBaseClass>? = null

override fun onListen(p0: Any?, sink: PigeonEventSink<SealedBaseClass>) {
eventSink = sink
}

fun onIntEvent(event: Long) {
eventSink?.success(IntEvent(data = event))
}

fun onStringEvent(event: String) {
eventSink?.success(StringEvent(data = event))
}

fun onEventsDone() {
eventSink?.endOfStream()
eventSink = null
}
}
```

## 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,134 @@
// 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.PlatformEvent.streamEvents"
if (instanceName.isNotEmpty()) {
channelName += ".$instanceName"
}
val streamHandler = PigeonStreamHandler<SealedBaseClass>(wrapper)
EventChannel(messenger, channelName, EventChannelMessagesPigeonMethodCodec)
.setStreamHandler(streamHandler)
}
}
}
Loading