Skip to content

Commit 71a2e70

Browse files
authored
[pigeon] adds event channel support for kotlin and swift (#7892)
adds event channel support for kotlin and swift work towards flutter/flutter#66711 adds sealed classes with extensions (empty base classes only) fixes flutter/flutter#155859 (Fix a small inconsistency with Pigeon docs) adds some convenience methods to Root fixes generation/format tests to include test pigeons Makes swift codec class names upper camel case
1 parent ea90218 commit 71a2e70

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+4144
-283
lines changed

packages/pigeon/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 22.7.0
2+
3+
* [swift, kotlin] Adds event channel support.
4+
* [swift, kotlin] Adds `sealed` class inheritance support.
5+
* [swift] Updates codec class names to be upper camel case.
6+
17
## 22.6.4
28

39
* [swift] Fixes the channel names of the named constructors of ProxyApis.

packages/pigeon/README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ Pigeon uses the `StandardMessageCodec` so it supports
2626

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

29+
Basic inheritance with empty `sealed` parent classes is allowed only in the Swift, Kotlin, and Dart generators.
30+
2931
Nullable enums in Objective-C generated code will be wrapped in a class to allow for nullability.
3032

3133
By default, custom classes in Swift are defined as structs.
@@ -104,9 +106,10 @@ to the api to allow for multiple instances to be created and operate in parallel
104106
1) Method declarations on the API classes should have arguments and a return
105107
value whose types are defined in the file, are supported datatypes, or are
106108
`void`.
107-
1) Generics are supported, but can currently only be used with nullable types
108-
(example: `List<int?>`).
109-
1) Objc and Swift have special naming conventions that can be utilized with the
109+
1) Event channels are supported only on the Swift, Kotlin, and Dart generators.
110+
1) Event channel methods should be wrapped in an `abstract class` with the metadata `@EventChannelApi`.
111+
1) Event channel definitions should not include the `Stream` return type, just the type that is being streamed.
112+
1) Objective-C and Swift have special naming conventions that can be utilized with the
110113
`@ObjCSelector` and `@SwiftFunction` respectively.
111114

112115
### Flutter calling into iOS steps

packages/pigeon/example/README.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,119 @@ pigeon_example_package_message_flutter_api_flutter_method(
360360
self->flutter_api, "hello", nullptr, flutter_method_cb, self);
361361
```
362362
363+
## Event Channel Example
364+
365+
This example gives a basic overview of how to use Pigeon to set up an event channel.
366+
367+
### Dart input
368+
369+
<?code-excerpt "pigeons/event_channel_messages.dart (event-definitions)"?>
370+
```dart
371+
@EventChannelApi()
372+
abstract class EventChannelMethods {
373+
PlatformEvent streamEvents();
374+
}
375+
```
376+
377+
### Dart
378+
379+
The generated Dart code will include a method that returns a `Stream` when invoked.
380+
381+
<?code-excerpt "lib/main.dart (main-dart-event)"?>
382+
```dart
383+
Stream<String> getEventStream() async* {
384+
final Stream<PlatformEvent> events = streamEvents();
385+
await for (final PlatformEvent event in events) {
386+
switch (event) {
387+
case IntEvent():
388+
final int intData = event.data;
389+
yield '$intData, ';
390+
case StringEvent():
391+
final String stringData = event.data;
392+
yield '$stringData, ';
393+
}
394+
}
395+
}
396+
```
397+
398+
### Swift
399+
400+
Define the stream handler class that will handle the events.
401+
402+
<?code-excerpt "ios/Runner/AppDelegate.swift (swift-class-event)"?>
403+
```swift
404+
class EventListener: StreamEventsStreamHandler {
405+
var eventSink: PigeonEventSink<PlatformEvent>?
406+
407+
override func onListen(withArguments arguments: Any?, sink: PigeonEventSink<PlatformEvent>) {
408+
eventSink = sink
409+
}
410+
411+
func onIntEvent(event: Int64) {
412+
if let eventSink = eventSink {
413+
eventSink.success(IntEvent(data: event))
414+
}
415+
}
416+
417+
func onStringEvent(event: String) {
418+
if let eventSink = eventSink {
419+
eventSink.success(StringEvent(data: event))
420+
}
421+
}
422+
423+
func onEventsDone() {
424+
eventSink?.endOfStream()
425+
eventSink = nil
426+
}
427+
}
428+
```
429+
430+
Register the handler with the generated method.
431+
432+
<?code-excerpt "ios/Runner/AppDelegate.swift (swift-init-event)"?>
433+
```swift
434+
let eventListener = EventListener()
435+
StreamEventsStreamHandler.register(
436+
with: controller.binaryMessenger, streamHandler: eventListener)
437+
```
438+
439+
### Kotlin
440+
441+
Define the stream handler class that will handle the events.
442+
443+
<?code-excerpt "android/app/src/main/kotlin/dev/flutter/pigeon_example_app/MainActivity.kt (kotlin-class-event)"?>
444+
```kotlin
445+
class EventListener : StreamEventsStreamHandler() {
446+
private var eventSink: PigeonEventSink<PlatformEvent>? = null
447+
448+
override fun onListen(p0: Any?, sink: PigeonEventSink<PlatformEvent>) {
449+
eventSink = sink
450+
}
451+
452+
fun onIntEvent(event: Long) {
453+
eventSink?.success(IntEvent(data = event))
454+
}
455+
456+
fun onStringEvent(event: String) {
457+
eventSink?.success(StringEvent(data = event))
458+
}
459+
460+
fun onEventsDone() {
461+
eventSink?.endOfStream()
462+
eventSink = null
463+
}
464+
}
465+
```
466+
467+
468+
Register the handler with the generated method.
469+
470+
<?code-excerpt "android/app/src/main/kotlin/dev/flutter/pigeon_example_app/MainActivity.kt (kotlin-init-event)"?>
471+
```kotlin
472+
val eventListener = EventListener()
473+
StreamEventsStreamHandler.register(flutterEngine.dartExecutor.binaryMessenger, eventListener)
474+
```
475+
363476
## Swift / Kotlin Plugin Example
364477

365478
A downloadable example of using Pigeon to create a Flutter Plugin with Swift and
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
// Autogenerated from Pigeon, do not edit directly.
5+
// See also: https://pub.dev/packages/pigeon
6+
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
7+
8+
import io.flutter.plugin.common.BinaryMessenger
9+
import io.flutter.plugin.common.EventChannel
10+
import io.flutter.plugin.common.StandardMessageCodec
11+
import io.flutter.plugin.common.StandardMethodCodec
12+
import java.io.ByteArrayOutputStream
13+
import java.nio.ByteBuffer
14+
15+
/**
16+
* Generated class from Pigeon that represents data sent in messages. This class should not be
17+
* extended by any user class outside of the generated file.
18+
*/
19+
sealed class PlatformEvent
20+
/** Generated class from Pigeon that represents data sent in messages. */
21+
data class IntEvent(val data: Long) : PlatformEvent() {
22+
companion object {
23+
fun fromList(pigeonVar_list: List<Any?>): IntEvent {
24+
val data = pigeonVar_list[0] as Long
25+
return IntEvent(data)
26+
}
27+
}
28+
29+
fun toList(): List<Any?> {
30+
return listOf(
31+
data,
32+
)
33+
}
34+
}
35+
36+
/** Generated class from Pigeon that represents data sent in messages. */
37+
data class StringEvent(val data: String) : PlatformEvent() {
38+
companion object {
39+
fun fromList(pigeonVar_list: List<Any?>): StringEvent {
40+
val data = pigeonVar_list[0] as String
41+
return StringEvent(data)
42+
}
43+
}
44+
45+
fun toList(): List<Any?> {
46+
return listOf(
47+
data,
48+
)
49+
}
50+
}
51+
52+
private open class EventChannelMessagesPigeonCodec : StandardMessageCodec() {
53+
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
54+
return when (type) {
55+
129.toByte() -> {
56+
return (readValue(buffer) as? List<Any?>)?.let { IntEvent.fromList(it) }
57+
}
58+
130.toByte() -> {
59+
return (readValue(buffer) as? List<Any?>)?.let { StringEvent.fromList(it) }
60+
}
61+
else -> super.readValueOfType(type, buffer)
62+
}
63+
}
64+
65+
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
66+
when (value) {
67+
is IntEvent -> {
68+
stream.write(129)
69+
writeValue(stream, value.toList())
70+
}
71+
is StringEvent -> {
72+
stream.write(130)
73+
writeValue(stream, value.toList())
74+
}
75+
else -> super.writeValue(stream, value)
76+
}
77+
}
78+
}
79+
80+
val EventChannelMessagesPigeonMethodCodec = StandardMethodCodec(EventChannelMessagesPigeonCodec())
81+
82+
private class PigeonStreamHandler<T>(val wrapper: PigeonEventChannelWrapper<T>) :
83+
EventChannel.StreamHandler {
84+
var pigeonSink: PigeonEventSink<T>? = null
85+
86+
override fun onListen(p0: Any?, sink: EventChannel.EventSink) {
87+
pigeonSink = PigeonEventSink<T>(sink)
88+
wrapper.onListen(p0, pigeonSink!!)
89+
}
90+
91+
override fun onCancel(p0: Any?) {
92+
pigeonSink = null
93+
wrapper.onCancel(p0)
94+
}
95+
}
96+
97+
interface PigeonEventChannelWrapper<T> {
98+
open fun onListen(p0: Any?, sink: PigeonEventSink<T>) {}
99+
100+
open fun onCancel(p0: Any?) {}
101+
}
102+
103+
class PigeonEventSink<T>(private val sink: EventChannel.EventSink) {
104+
fun success(value: T) {
105+
sink.success(value)
106+
}
107+
108+
fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
109+
sink.error(errorCode, errorMessage, errorDetails)
110+
}
111+
112+
fun endOfStream() {
113+
sink.endOfStream()
114+
}
115+
}
116+
117+
abstract class StreamEventsStreamHandler : PigeonEventChannelWrapper<PlatformEvent> {
118+
companion object {
119+
fun register(
120+
messenger: BinaryMessenger,
121+
streamHandler: StreamEventsStreamHandler,
122+
instanceName: String = ""
123+
) {
124+
var channelName: String =
125+
"dev.flutter.pigeon.pigeon_example_package.EventChannelMethods.streamEvents"
126+
if (instanceName.isNotEmpty()) {
127+
channelName += ".$instanceName"
128+
}
129+
val internalStreamHandler = PigeonStreamHandler<PlatformEvent>(streamHandler)
130+
EventChannel(messenger, channelName, EventChannelMessagesPigeonMethodCodec)
131+
.setStreamHandler(internalStreamHandler)
132+
}
133+
}
134+
}

packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/MainActivity.kt

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,15 @@ package dev.flutter.pigeon_example_app
66

77
import ExampleHostApi
88
import FlutterError
9+
import IntEvent
910
import MessageData
1011
import MessageFlutterApi
12+
import PigeonEventSink
13+
import PlatformEvent
14+
import StreamEventsStreamHandler
15+
import StringEvent
16+
import android.os.Handler
17+
import android.os.Looper
1118
import io.flutter.embedding.android.FlutterActivity
1219
import io.flutter.embedding.engine.FlutterEngine
1320
import io.flutter.embedding.engine.plugins.FlutterPlugin
@@ -49,11 +56,66 @@ private class PigeonFlutterApi(binding: FlutterPlugin.FlutterPluginBinding) {
4956
}
5057
// #enddocregion kotlin-class-flutter
5158

59+
// #docregion kotlin-class-event
60+
class EventListener : StreamEventsStreamHandler() {
61+
private var eventSink: PigeonEventSink<PlatformEvent>? = null
62+
63+
override fun onListen(p0: Any?, sink: PigeonEventSink<PlatformEvent>) {
64+
eventSink = sink
65+
}
66+
67+
fun onIntEvent(event: Long) {
68+
eventSink?.success(IntEvent(data = event))
69+
}
70+
71+
fun onStringEvent(event: String) {
72+
eventSink?.success(StringEvent(data = event))
73+
}
74+
75+
fun onEventsDone() {
76+
eventSink?.endOfStream()
77+
eventSink = null
78+
}
79+
}
80+
// #enddocregion kotlin-class-event
81+
82+
fun sendEvents(eventListener: EventListener) {
83+
val handler = Handler(Looper.getMainLooper())
84+
var count: Int = 0
85+
val r: Runnable =
86+
object : Runnable {
87+
override fun run() {
88+
if (count >= 100) {
89+
handler.post { eventListener.onEventsDone() }
90+
} else {
91+
if (count % 2 == 0) {
92+
handler.post {
93+
eventListener.onIntEvent(count.toLong())
94+
count++
95+
}
96+
} else {
97+
handler.post {
98+
eventListener.onStringEvent(count.toString())
99+
count++
100+
}
101+
}
102+
handler.postDelayed(this, 1000)
103+
}
104+
}
105+
}
106+
handler.post(r)
107+
}
108+
52109
class MainActivity : FlutterActivity() {
53110
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
54111
super.configureFlutterEngine(flutterEngine)
55112

56113
val api = PigeonApiImplementation()
57114
ExampleHostApi.setUp(flutterEngine.dartExecutor.binaryMessenger, api)
115+
// #docregion kotlin-init-event
116+
val eventListener = EventListener()
117+
StreamEventsStreamHandler.register(flutterEngine.dartExecutor.binaryMessenger, eventListener)
118+
// #enddocregion kotlin-init-event
119+
sendEvents(eventListener)
58120
}
59121
}

0 commit comments

Comments
 (0)