Skip to content

[pigeon]Support EventChannelApi in multiple interface files #161291

@taiju59

Description

@taiju59

Use case

In my project, I organize interface definitions across multiple files. I want to define EventChannelApi in multiple files using the @EventChannelApi() annotation.
However, this results in the generation of duplicate classes (e.g., PigeonEventChannelWrapper and PigeonEventSink in Swift), which causes build errors.

Code Example

Input files

a.dart

import 'package:pigeon/pigeon.dart';

@ConfigurePigeon(PigeonOptions(
  dartOut: 'lib/src/a.g.dart',
  dartOptions: DartOptions(),
  kotlinOut: 'android/app/src/main/kotlin/dev/flutter/pigeon_sample/A.g.kt',
  kotlinOptions: KotlinOptions(),
  swiftOut: 'ios/Runner/A.g.swift',
  swiftOptions: SwiftOptions(),
))
@EventChannelApi()
abstract class EventA {
  int streamA();
}

b.dart

import 'package:pigeon/pigeon.dart';

@ConfigurePigeon(PigeonOptions(
  dartOut: 'lib/src/b.g.dart',
  dartOptions: DartOptions(),
  kotlinOut: 'android/app/src/main/kotlin/dev/flutter/pigeon_sample/B.g.kt',
  kotlinOptions: KotlinOptions(includeErrorClass: false),
  swiftOut: 'ios/Runner/B.g.swift',
  swiftOptions: SwiftOptions(includeErrorClass: false),
))
@EventChannelApi()
abstract class EventB {
  int streamB();
}

Output files

A.g.swift

// Autogenerated from Pigeon (v22.7.2), do not edit directly.
// See also: https://pub.dev/packages/pigeon

import Foundation

#if os(iOS)
  import Flutter
#elseif os(macOS)
  import FlutterMacOS
#else
  #error("Unsupported platform.")
#endif

/// Error class for passing custom error details to Dart side.
final class PigeonError: Error {
  let code: String
  let message: String?
  let details: Any?

  init(code: String, message: String?, details: Any?) {
    self.code = code
    self.message = message
    self.details = details
  }

  var localizedDescription: String {
    return
      "PigeonError(code: \(code), message: \(message ?? "<nil>"), details: \(details ?? "<nil>")"
      }
}

private func isNullish(_ value: Any?) -> Bool {
  return value is NSNull || value == nil
}

private func nilOrValue<T>(_ value: Any?) -> T? {
  if value is NSNull { return nil }
  return value as! T?
}

private class APigeonCodecReader: FlutterStandardReader {
}

private class APigeonCodecWriter: FlutterStandardWriter {
}

private class APigeonCodecReaderWriter: FlutterStandardReaderWriter {
  override func reader(with data: Data) -> FlutterStandardReader {
    return APigeonCodecReader(data: data)
  }

  override func writer(with data: NSMutableData) -> FlutterStandardWriter {
    return APigeonCodecWriter(data: data)
  }
}

class APigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
  static let shared = APigeonCodec(readerWriter: APigeonCodecReaderWriter())
}

var aPigeonMethodCodec = FlutterStandardMethodCodec(readerWriter: APigeonCodecReaderWriter());


private class PigeonStreamHandler<ReturnType>: NSObject, FlutterStreamHandler {
  private let wrapper: PigeonEventChannelWrapper<ReturnType>
  private var pigeonSink: PigeonEventSink<ReturnType>? = nil

  init(wrapper: PigeonEventChannelWrapper<ReturnType>) {
    self.wrapper = wrapper
  }

  func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink)
    -> FlutterError?
  {
    pigeonSink = PigeonEventSink<ReturnType>(events)
    wrapper.onListen(withArguments: arguments, sink: pigeonSink!)
    return nil
  }

  func onCancel(withArguments arguments: Any?) -> FlutterError? {
    pigeonSink = nil
    wrapper.onCancel(withArguments: arguments)
    return nil
  }
}

class PigeonEventChannelWrapper<ReturnType> {
  func onListen(withArguments arguments: Any?, sink: PigeonEventSink<ReturnType>) {}
  func onCancel(withArguments arguments: Any?) {}
}

class PigeonEventSink<ReturnType> {
  private let sink: FlutterEventSink

  init(_ sink: @escaping FlutterEventSink) {
    self.sink = sink
  }

  func success(_ value: ReturnType) {
    sink(value)
  }

  func error(code: String, message: String?, details: Any?) {
    sink(FlutterError(code: code, message: message, details: details))
  }

  func endOfStream() {
    sink(FlutterEndOfEventStream)
  }

}

class StreamAStreamHandler: PigeonEventChannelWrapper<Int64> {
  static func register(with messenger: FlutterBinaryMessenger, 
                      instanceName: String = "",
                      streamHandler: StreamAStreamHandler) {
    var channelName = "dev.flutter.pigeon.pigeon_sample.EventA.streamA"
    if !instanceName.isEmpty {
      channelName += ".\(instanceName)"
    }
    let internalStreamHandler = PigeonStreamHandler<Int64>(wrapper: streamHandler)
    let channel = FlutterEventChannel(name: channelName, binaryMessenger: messenger, codec: aPigeonMethodCodec)
    channel.setStreamHandler(internalStreamHandler)
  }
}
      

B.g.swift

// Autogenerated from Pigeon (v22.7.2), do not edit directly.
// See also: https://pub.dev/packages/pigeon

import Foundation

#if os(iOS)
  import Flutter
#elseif os(macOS)
  import FlutterMacOS
#else
  #error("Unsupported platform.")
#endif

private func isNullish(_ value: Any?) -> Bool {
  return value is NSNull || value == nil
}

private func nilOrValue<T>(_ value: Any?) -> T? {
  if value is NSNull { return nil }
  return value as! T?
}

private class BPigeonCodecReader: FlutterStandardReader {
}

private class BPigeonCodecWriter: FlutterStandardWriter {
}

private class BPigeonCodecReaderWriter: FlutterStandardReaderWriter {
  override func reader(with data: Data) -> FlutterStandardReader {
    return BPigeonCodecReader(data: data)
  }

  override func writer(with data: NSMutableData) -> FlutterStandardWriter {
    return BPigeonCodecWriter(data: data)
  }
}

class BPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
  static let shared = BPigeonCodec(readerWriter: BPigeonCodecReaderWriter())
}

var bPigeonMethodCodec = FlutterStandardMethodCodec(readerWriter: BPigeonCodecReaderWriter());


private class PigeonStreamHandler<ReturnType>: NSObject, FlutterStreamHandler {
  private let wrapper: PigeonEventChannelWrapper<ReturnType>
  private var pigeonSink: PigeonEventSink<ReturnType>? = nil

  init(wrapper: PigeonEventChannelWrapper<ReturnType>) {
    self.wrapper = wrapper
  }

  func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink)
    -> FlutterError?
  {
    pigeonSink = PigeonEventSink<ReturnType>(events)
    wrapper.onListen(withArguments: arguments, sink: pigeonSink!)
    return nil
  }

  func onCancel(withArguments arguments: Any?) -> FlutterError? {
    pigeonSink = nil
    wrapper.onCancel(withArguments: arguments)
    return nil
  }
}

class PigeonEventChannelWrapper<ReturnType> {
  func onListen(withArguments arguments: Any?, sink: PigeonEventSink<ReturnType>) {}
  func onCancel(withArguments arguments: Any?) {}
}

class PigeonEventSink<ReturnType> {
  private let sink: FlutterEventSink

  init(_ sink: @escaping FlutterEventSink) {
    self.sink = sink
  }

  func success(_ value: ReturnType) {
    sink(value)
  }

  func error(code: String, message: String?, details: Any?) {
    sink(FlutterError(code: code, message: message, details: details))
  }

  func endOfStream() {
    sink(FlutterEndOfEventStream)
  }

}

class StreamBStreamHandler: PigeonEventChannelWrapper<Int64> {
  static func register(with messenger: FlutterBinaryMessenger, 
                      instanceName: String = "",
                      streamHandler: StreamBStreamHandler) {
    var channelName = "dev.flutter.pigeon.pigeon_sample.EventB.streamB"
    if !instanceName.isEmpty {
      channelName += ".\(instanceName)"
    }
    let internalStreamHandler = PigeonStreamHandler<Int64>(wrapper: streamHandler)
    let channel = FlutterEventChannel(name: channelName, binaryMessenger: messenger, codec: bPigeonMethodCodec)
    channel.setStreamHandler(internalStreamHandler)
  }
}
      

Proposal

I propose introducing a new configuration option in SwiftOptions and KotlinOptions to control the generation of shared utility classes (e.g., PigeonEventChannelWrapper and PigeonEventSink) similar to the includeErrorClass option.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work listc: proposalA detailed proposal for a change to Flutterp: pigeonrelated to pigeon messaging codegen toolpackageflutter/packages repository. See also p: labels.r: fixedIssue is closed as already fixed in a newer versionteam-ecosystemOwned by Ecosystem teamtriaged-ecosystemTriaged by Ecosystem team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions