-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Open
Labels
P2Important issues not at the top of the work listImportant issues not at the top of the work listc: new featureNothing broken; request for a new capabilityNothing broken; request for a new capabilityc: proposalA detailed proposal for a change to FlutterA detailed proposal for a change to Flutterp: pigeonrelated to pigeon messaging codegen toolrelated to pigeon messaging codegen toolpackageflutter/packages repository. See also p: labels.flutter/packages repository. See also p: labels.team-ecosystemOwned by Ecosystem teamOwned by Ecosystem teamtriaged-ecosystemTriaged by Ecosystem teamTriaged by Ecosystem team
Description
Use case
I would like to be able to generate code in the format suitable for Swift Concurrency with pigeon.
When targeting the latest Swift version and iOS 13 or above, I want to generate an interface with Flutter in the form of new APIs that conform to MainActor, Sendable, async/await, and ExistentialAny.
related issue
Proposal
Assuming there is code targeted for generation as follows:
@HostApi()
abstract class SampleApi {
@async
Sample fetchSampleAsync(SampleParameter parameter);
Sample fetchSampleSync(SampleParameter parameter);
@async
Object? objectSampleAsync(Object? parameter);
Object? objectSampleSync(Object? parameter);
}
@FlutterApi()
abstract class CallFromNative {
Sample fetchSample(SampleParameter parameter);
Object? objectSample(Object? parameter);
}When generating from this code, I would like to generate the following protocols:
@available(iOS 13, *)
@MainActor
protocol SampleApi {
func fetchSampleAsync(parameter: SampleParameter) async -> Result<Sample, any Error>
func fetchSampleSync(parameter: SampleParameter) throws -> Sample
func objectSampleAsync(parameter: Any?) async -> Result<Any?, any Error>
func objectSampleSync(parameter: Any?) throws -> Any?
}
@available(iOS, obsoleted: 13.0, renamed: "SampleApi")
protocol SampleApiLegacy {
func fetchSampleAsync(parameter: SampleParameter, completion: @escaping (Result<Sample, any Error>) -> Void)
func fetchSampleSync(parameter: SampleParameter) throws -> Sample
func objectSampleAsync(parameter: Any?, completion: @escaping (Result<Any?, any Error>) -> Void)
func objectSampleSync(parameter: Any?) throws -> Any?
}
@available(iOS 13, *)
@MainActor
protocol CallFromNativeProtocol {
func fetchSample(parameter parameterArg: SampleParameter) async -> Result<Sample, FlutterError>
func objectSample(parameter parameterArg: Any?) async -> Result<Any?, FlutterError>
}
@available(iOS, obsoleted: 13.0, renamed: "CallFromNativeProtocol")
protocol CallFromNativeProtocolLegacy {
func fetchSample(parameter parameterArg: SampleParameter, completion: @escaping (Result<Sample, FlutterError>) -> Void)
func objectSample(parameter parameterArg: Any?, completion: @escaping (Result<Any?, FlutterError>) -> Void)
}Since I think it might be confusing to switch between Swift versions and iOS versions, I am attaching the expected generated code that I have implemented below.
All expected generated swift code
import Foundation
#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif
#if swift(>=5.10)
private func wrapResult(_ result: Any?) -> [Any?] {
return [result]
}
private func wrapError(_ error: Any) -> [Any?] {
if let flutterError = error as? FlutterError {
return [
flutterError.code,
flutterError.message,
flutterError.details,
]
}
return [
"\(error)",
"\(type(of: error))",
"Stacktrace: \(Thread.callStackSymbols)",
]
}
private func createConnectionError(withChannelName channelName: String) -> FlutterError {
return FlutterError(code: "channel-error", message: "Unable to establish connection on channel: '\(channelName)'.", details: "")
}
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?
}
/// Generated class from Pigeon that represents data sent in messages.
struct Sample: Sendable {
var text: String
var id: Int64
static func fromList(_ list: [Any?]) -> Sample? {
let text = list[0] as! String
let id = list[1] is Int64 ? list[1] as! Int64 : Int64(list[1] as! Int32)
return Sample(
text: text,
id: id
)
}
func toList() -> [Any?] {
return [
text,
id,
]
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct SampleParameter: Sendable {
var text: String
var id: Int64
static func fromList(_ list: [Any?]) -> SampleParameter? {
let text = list[0] as! String
let id = list[1] is Int64 ? list[1] as! Int64 : Int64(list[1] as! Int32)
return SampleParameter(
text: text,
id: id
)
}
func toList() -> [Any?] {
return [
text,
id,
]
}
}
private class SampleApiCodecReader: FlutterStandardReader {
override func readValue(ofType type: UInt8) -> Any? {
switch type {
case 128:
return Sample.fromList(self.readValue() as! [Any?])
case 129:
return SampleParameter.fromList(self.readValue() as! [Any?])
default:
return super.readValue(ofType: type)
}
}
}
private class SampleApiCodecWriter: FlutterStandardWriter {
override func writeValue(_ value: Any) {
if let value = value as? Sample {
super.writeByte(128)
super.writeValue(value.toList())
} else if let value = value as? SampleParameter {
super.writeByte(129)
super.writeValue(value.toList())
} else {
super.writeValue(value)
}
}
}
private class SampleApiCodecReaderWriter: FlutterStandardReaderWriter {
override func reader(with data: Data) -> FlutterStandardReader {
return SampleApiCodecReader(data: data)
}
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
return SampleApiCodecWriter(data: data)
}
}
class SampleApiCodec: FlutterStandardMessageCodec, @unchecked Sendable {
static let shared = SampleApiCodec(readerWriter: SampleApiCodecReaderWriter())
}
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
@available(iOS 13, *)
@MainActor
protocol SampleApi {
func fetchSampleAsync(parameter: SampleParameter) async -> Result<Sample, any Error>
func fetchSampleSync(parameter: SampleParameter) throws -> Sample
func objectSampleAsync(parameter: Any?) async -> Result<Any?, any Error>
func objectSampleSync(parameter: Any?) throws -> Any?
}
@available(iOS, obsoleted: 13.0, renamed: "SampleApi")
protocol SampleApiLegacy {
func fetchSampleAsync(parameter: SampleParameter, completion: @escaping (Result<Sample, any Error>) -> Void)
func fetchSampleSync(parameter: SampleParameter) throws -> Sample
func objectSampleAsync(parameter: Any?, completion: @escaping (Result<Any?, any Error>) -> Void)
func objectSampleSync(parameter: Any?) throws -> Any?
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
class SampleApiSetup {
/// The codec used by SampleApi.
static var codec: FlutterStandardMessageCodec { SampleApiCodec.shared }
/// Sets up an instance of `SampleApi` to handle messages through the `binaryMessenger`.
@available(iOS 13, *)
static func setUp(binaryMessenger: any FlutterBinaryMessenger, api: (any SampleApi)?, messageChannelSuffix: String = "") {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
let fetchSampleAsyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.modern_swift.SampleApi.fetchSampleAsync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
fetchSampleAsyncChannel.setMessageHandler { message, reply in
Task { @MainActor in
let args = message as! [Any?]
let parameterArg = args[0] as! SampleParameter
let result = await api.fetchSampleAsync(parameter: parameterArg)
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
fetchSampleAsyncChannel.setMessageHandler(nil)
}
let fetchSampleSyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.modern_swift.SampleApi.fetchSampleSync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
fetchSampleSyncChannel.setMessageHandler { message, reply in
MainActor.assumeIsolated {
let args = message as! [Any?]
let parameterArg = args[0] as! SampleParameter
do {
let result = try api.fetchSampleSync(parameter: parameterArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
}
} else {
fetchSampleSyncChannel.setMessageHandler(nil)
}
let objectSampleAsyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.modern_swift.SampleApi.objectSampleAsync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
objectSampleAsyncChannel.setMessageHandler { message, reply in
Task { @MainActor in
let args = message as! [Any?]
let parameterArg: Any? = args[0]
let result = await api.objectSampleAsync(parameter: parameterArg)
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
objectSampleAsyncChannel.setMessageHandler(nil)
}
let objectSampleSyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.modern_swift.SampleApi.objectSampleSync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
objectSampleSyncChannel.setMessageHandler { message, reply in
MainActor.assumeIsolated {
let args = message as! [Any?]
let parameterArg: Any? = args[0]
do {
let result = try api.objectSampleSync(parameter: parameterArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
}
} else {
objectSampleSyncChannel.setMessageHandler(nil)
}
}
/// Sets up an instance of `SampleApiLegacy` to handle messages through the `binaryMessenger`.
@available(iOS, obsoleted: 13.0)
static func setUp(binaryMessenger: any FlutterBinaryMessenger, api: (any SampleApiLegacy)?, messageChannelSuffix: String = "") {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
let fetchSampleAsyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.modern_swift.SampleApi.fetchSampleAsync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
fetchSampleAsyncChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let parameterArg = args[0] as! SampleParameter
api.fetchSampleAsync(parameter: parameterArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
fetchSampleAsyncChannel.setMessageHandler(nil)
}
let fetchSampleSyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.modern_swift.SampleApi.fetchSampleSync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
fetchSampleSyncChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let parameterArg = args[0] as! SampleParameter
do {
let result = try api.fetchSampleSync(parameter: parameterArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
fetchSampleSyncChannel.setMessageHandler(nil)
}
let objectSampleAsyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.modern_swift.SampleApi.objectSampleAsync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
objectSampleAsyncChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let parameterArg: Any? = args[0]
api.objectSampleAsync(parameter: parameterArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
objectSampleAsyncChannel.setMessageHandler(nil)
}
let objectSampleSyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.modern_swift.SampleApi.objectSampleSync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
objectSampleSyncChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let parameterArg: Any? = args[0]
do {
let result = try api.objectSampleSync(parameter: parameterArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
objectSampleSyncChannel.setMessageHandler(nil)
}
}
}
private class CallFromNativeCodecReader: FlutterStandardReader {
override func readValue(ofType type: UInt8) -> Any? {
switch type {
case 128:
return Sample.fromList(self.readValue() as! [Any?])
case 129:
return SampleParameter.fromList(self.readValue() as! [Any?])
default:
return super.readValue(ofType: type)
}
}
}
private class CallFromNativeCodecWriter: FlutterStandardWriter {
override func writeValue(_ value: Any) {
if let value = value as? Sample {
super.writeByte(128)
super.writeValue(value.toList())
} else if let value = value as? SampleParameter {
super.writeByte(129)
super.writeValue(value.toList())
} else {
super.writeValue(value)
}
}
}
private class CallFromNativeCodecReaderWriter: FlutterStandardReaderWriter {
override func reader(with data: Data) -> FlutterStandardReader {
return CallFromNativeCodecReader(data: data)
}
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
return CallFromNativeCodecWriter(data: data)
}
}
class CallFromNativeCodec: FlutterStandardMessageCodec, @unchecked Sendable {
static let shared = CallFromNativeCodec(readerWriter: CallFromNativeCodecReaderWriter())
}
/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift.
@available(iOS 13, *)
@MainActor
protocol CallFromNativeProtocol {
func fetchSample(parameter parameterArg: SampleParameter) async -> Result<Sample, FlutterError>
func objectSample(parameter parameterArg: Any?) async -> Result<Any?, FlutterError>
}
/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift.
@available(iOS, obsoleted: 13.0)
protocol CallFromNativeProtocolLegacy {
func fetchSample(parameter parameterArg: SampleParameter, completion: @escaping (Result<Sample, FlutterError>) -> Void)
func objectSample(parameter parameterArg: Any?, completion: @escaping (Result<Any?, FlutterError>) -> Void)
}
@available(iOS 13, *)
final class CallFromNative: CallFromNativeProtocol {
private let binaryMessenger: any FlutterBinaryMessenger
private let messageChannelSuffix: String
init(binaryMessenger: any FlutterBinaryMessenger, messageChannelSuffix: String = "") {
self.binaryMessenger = binaryMessenger
self.messageChannelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
}
var codec: FlutterStandardMessageCodec {
return CallFromNativeCodec.shared
}
func fetchSample(parameter parameterArg: SampleParameter) async -> Result<Sample, FlutterError> {
await withCheckedContinuation { continuation in
let channelName: String = "dev.flutter.pigeon.modern_swift.CallFromNative.fetchSample\(messageChannelSuffix)"
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage([parameterArg] as [Any?]) { response in
MainActor.assumeIsolated {
guard let listResponse = response as? [Any?] else {
continuation.resume(returning: .failure(createConnectionError(withChannelName: channelName)))
return
}
if listResponse.count > 1 {
let code: String = listResponse[0] as! String
let message: String? = nilOrValue(listResponse[1])
let details: String? = nilOrValue(listResponse[2])
continuation.resume(returning: .failure(FlutterError(code: code, message: message, details: details)))
} else if listResponse[0] == nil {
continuation.resume(returning: .failure(FlutterError(code: "null-error", message: "Flutter api returned null value for non-null return value.", details: "")))
} else {
let result = listResponse[0] as! Sample
continuation.resume(returning: .success(result))
}
}
}
}
}
func objectSample(parameter parameterArg: Any?) async -> Result<Any?, FlutterError> {
await withCheckedContinuation { continuation in
let channelName: String = "dev.flutter.pigeon.modern_swift.CallFromNative.objectSample\(messageChannelSuffix)"
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage([parameterArg] as [Any?]) { response in
guard let listResponse = response as? [Any?] else {
continuation.resume(returning: .failure(createConnectionError(withChannelName: channelName)))
return
}
if listResponse.count > 1 {
let code: String = listResponse[0] as! String
let message: String? = nilOrValue(listResponse[1])
let details: String? = nilOrValue(listResponse[2])
continuation.resume(returning: .failure(FlutterError(code: code, message: message, details: details)))
} else {
let result: Any? = listResponse[0]
continuation.resume(returning: .success(result))
}
}
}
}
}
@available(iOS, obsoleted: 13.0)
class CallFromNativeLegacy: CallFromNativeProtocolLegacy {
private let binaryMessenger: any FlutterBinaryMessenger
private let messageChannelSuffix: String
init(binaryMessenger: any FlutterBinaryMessenger, messageChannelSuffix: String = "") {
self.binaryMessenger = binaryMessenger
self.messageChannelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
}
var codec: FlutterStandardMessageCodec {
return CallFromNativeCodec.shared
}
func fetchSample(parameter parameterArg: SampleParameter, completion: @escaping (Result<Sample, FlutterError>) -> Void) {
let channelName: String = "dev.flutter.pigeon.modern_swift.CallFromNative.fetchSample\(messageChannelSuffix)"
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage([parameterArg] as [Any?]) { response in
guard let listResponse = response as? [Any?] else {
completion(.failure(createConnectionError(withChannelName: channelName)))
return
}
if listResponse.count > 1 {
let code: String = listResponse[0] as! String
let message: String? = nilOrValue(listResponse[1])
let details: String? = nilOrValue(listResponse[2])
completion(.failure(FlutterError(code: code, message: message, details: details)))
} else if listResponse[0] == nil {
completion(.failure(FlutterError(code: "null-error", message: "Flutter api returned null value for non-null return value.", details: "")))
} else {
let result = listResponse[0] as! Sample
completion(.success(result))
}
}
}
func objectSample(parameter parameterArg: Any?, completion: @escaping (Result<Any?, FlutterError>) -> Void) {
let channelName: String = "dev.flutter.pigeon.modern_swift.CallFromNative.objectSample\(messageChannelSuffix)"
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage([parameterArg] as [Any?]) { response in
guard let listResponse = response as? [Any?] else {
completion(.failure(createConnectionError(withChannelName: channelName)))
return
}
if listResponse.count > 1 {
let code: String = listResponse[0] as! String
let message: String? = nilOrValue(listResponse[1])
let details: String? = nilOrValue(listResponse[2])
completion(.failure(FlutterError(code: code, message: message, details: details)))
} else {
let result: Any? = listResponse[0]
completion(.success(result))
}
}
}
}
#else
private func wrapResult(_ result: Any?) -> [Any?] {
return [result]
}
private func wrapError(_ error: Any) -> [Any?] {
if let flutterError = error as? FlutterError {
return [
flutterError.code,
flutterError.message,
flutterError.details,
]
}
return [
"\(error)",
"\(type(of: error))",
"Stacktrace: \(Thread.callStackSymbols)",
]
}
private func createConnectionError(withChannelName channelName: String) -> FlutterError {
return FlutterError(code: "channel-error", message: "Unable to establish connection on channel: '\(channelName)'.", details: "")
}
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?
}
/// Generated class from Pigeon that represents data sent in messages.
struct Sample {
var text: String
var id: Int64
static func fromList(_ list: [Any?]) -> Sample? {
let text = list[0] as! String
let id = list[1] is Int64 ? list[1] as! Int64 : Int64(list[1] as! Int32)
return Sample(
text: text,
id: id
)
}
func toList() -> [Any?] {
return [
text,
id,
]
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct SampleParameter {
var text: String
var id: Int64
static func fromList(_ list: [Any?]) -> SampleParameter? {
let text = list[0] as! String
let id = list[1] is Int64 ? list[1] as! Int64 : Int64(list[1] as! Int32)
return SampleParameter(
text: text,
id: id
)
}
func toList() -> [Any?] {
return [
text,
id,
]
}
}
private class SampleApiCodecReader: FlutterStandardReader {
override func readValue(ofType type: UInt8) -> Any? {
switch type {
case 128:
return Sample.fromList(self.readValue() as! [Any?])
case 129:
return SampleParameter.fromList(self.readValue() as! [Any?])
default:
return super.readValue(ofType: type)
}
}
}
private class SampleApiCodecWriter: FlutterStandardWriter {
override func writeValue(_ value: Any) {
if let value = value as? Sample {
super.writeByte(128)
super.writeValue(value.toList())
} else if let value = value as? SampleParameter {
super.writeByte(129)
super.writeValue(value.toList())
} else {
super.writeValue(value)
}
}
}
private class SampleApiCodecReaderWriter: FlutterStandardReaderWriter {
override func reader(with data: Data) -> FlutterStandardReader {
return SampleApiCodecReader(data: data)
}
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
return SampleApiCodecWriter(data: data)
}
}
class SampleApiCodec: FlutterStandardMessageCodec {
static let shared = SampleApiCodec(readerWriter: SampleApiCodecReaderWriter())
}
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol SampleApi {
func fetchSampleAsync(parameter: SampleParameter, completion: @escaping (Result<Sample, Error>) -> Void)
func fetchSampleSync(parameter: SampleParameter) throws -> Sample
func objectSampleAsync(parameter: Any?, completion: @escaping (Result<Any?, Error>) -> Void)
func objectSampleSync(parameter: Any?) throws -> Any?
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
class SampleApiSetup {
/// The codec used by SampleApi.
static var codec: FlutterStandardMessageCodec { SampleApiCodec.shared }
/// Sets up an instance of `SampleApi` to handle messages through the `binaryMessenger`.
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: SampleApi?, messageChannelSuffix: String = "") {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
let fetchSampleAsyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.modern_swift.SampleApi.fetchSampleAsync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
fetchSampleAsyncChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let parameterArg = args[0] as! SampleParameter
api.fetchSampleAsync(parameter: parameterArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
fetchSampleAsyncChannel.setMessageHandler(nil)
}
let fetchSampleSyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.modern_swift.SampleApi.fetchSampleSync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
fetchSampleSyncChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let parameterArg = args[0] as! SampleParameter
do {
let result = try api.fetchSampleSync(parameter: parameterArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
fetchSampleSyncChannel.setMessageHandler(nil)
}
let objectSampleAsyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.modern_swift.SampleApi.objectSampleAsync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
objectSampleAsyncChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let parameterArg: Any? = args[0]
api.objectSampleAsync(parameter: parameterArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
objectSampleAsyncChannel.setMessageHandler(nil)
}
let objectSampleSyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.modern_swift.SampleApi.objectSampleSync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
objectSampleSyncChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let parameterArg: Any? = args[0]
do {
let result = try api.objectSampleSync(parameter: parameterArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
objectSampleSyncChannel.setMessageHandler(nil)
}
}
}
private class CallFromNativeCodecReader: FlutterStandardReader {
override func readValue(ofType type: UInt8) -> Any? {
switch type {
case 128:
return Sample.fromList(self.readValue() as! [Any?])
case 129:
return SampleParameter.fromList(self.readValue() as! [Any?])
default:
return super.readValue(ofType: type)
}
}
}
private class CallFromNativeCodecWriter: FlutterStandardWriter {
override func writeValue(_ value: Any) {
if let value = value as? Sample {
super.writeByte(128)
super.writeValue(value.toList())
} else if let value = value as? SampleParameter {
super.writeByte(129)
super.writeValue(value.toList())
} else {
super.writeValue(value)
}
}
}
private class CallFromNativeCodecReaderWriter: FlutterStandardReaderWriter {
override func reader(with data: Data) -> FlutterStandardReader {
return CallFromNativeCodecReader(data: data)
}
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
return CallFromNativeCodecWriter(data: data)
}
}
class CallFromNativeCodec: FlutterStandardMessageCodec {
static let shared = CallFromNativeCodec(readerWriter: CallFromNativeCodecReaderWriter())
}
/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift.
protocol CallFromNativeProtocol {
func fetchSample(parameter parameterArg: SampleParameter, completion: @escaping (Result<Sample, FlutterError>) -> Void)
func objectSample(parameter parameterArg: Any?, completion: @escaping (Result<Any?, FlutterError>) -> Void)
}
class CallFromNative: CallFromNativeProtocol {
private let binaryMessenger: FlutterBinaryMessenger
private let messageChannelSuffix: String
init(binaryMessenger: FlutterBinaryMessenger, messageChannelSuffix: String = "") {
self.binaryMessenger = binaryMessenger
self.messageChannelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
}
var codec: FlutterStandardMessageCodec {
return CallFromNativeCodec.shared
}
func fetchSample(parameter parameterArg: SampleParameter, completion: @escaping (Result<Sample, FlutterError>) -> Void) {
let channelName: String = "dev.flutter.pigeon.modern_swift.CallFromNative.fetchSample\(messageChannelSuffix)"
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage([parameterArg] as [Any?]) { response in
guard let listResponse = response as? [Any?] else {
completion(.failure(createConnectionError(withChannelName: channelName)))
return
}
if listResponse.count > 1 {
let code: String = listResponse[0] as! String
let message: String? = nilOrValue(listResponse[1])
let details: String? = nilOrValue(listResponse[2])
completion(.failure(FlutterError(code: code, message: message, details: details)))
} else if listResponse[0] == nil {
completion(.failure(FlutterError(code: "null-error", message: "Flutter api returned null value for non-null return value.", details: "")))
} else {
let result = listResponse[0] as! Sample
completion(.success(result))
}
}
}
func objectSample(parameter parameterArg: Any?, completion: @escaping (Result<Any?, FlutterError>) -> Void) {
let channelName: String = "dev.flutter.pigeon.modern_swift.CallFromNative.objectSample\(messageChannelSuffix)"
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage([parameterArg] as [Any?]) { response in
guard let listResponse = response as? [Any?] else {
completion(.failure(createConnectionError(withChannelName: channelName)))
return
}
if listResponse.count > 1 {
let code: String = listResponse[0] as! String
let message: String? = nilOrValue(listResponse[1])
let details: String? = nilOrValue(listResponse[2])
completion(.failure(FlutterError(code: code, message: message, details: details)))
} else {
let result: Any? = listResponse[0]
completion(.success(result))
}
}
}
}
#endif use case sample code
// in AppDelegate
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
guard let controller: FlutterViewController = window?.rootViewController as? FlutterViewController else {
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
GeneratedPluginRegistrant.register(with: self)
let binaryMessanger = controller.binaryMessenger
if #available(iOS 13, *) {
let callFromNative = CallFromNative(binaryMessenger: binaryMessanger)
let sampleApi = SampleApiImpl(callFromNative: callFromNative)
SampleApiSetup.setUp(binaryMessenger: binaryMessanger, api: sampleApi)
} else {
let callFromNative = CallFromNativeLegacy(binaryMessenger: binaryMessanger)
let sampleApi = SampleApiLegacyImpl(callFromNative: callFromNative)
SampleApiSetup.setUp(binaryMessenger: binaryMessanger, api: sampleApi)
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
// in sample api implement class
extension FlutterError: Error, @unchecked Sendable {}
@available(iOS 13, *)
@MainActor
final class SampleApiImpl: SampleApi {
let fetcher = SampleDataFetcher()
let callFromNative: CallFromNative
init(callFromNative: CallFromNative) {
self.callFromNative = callFromNative
}
func fetchSampleAsync(parameter: SampleParameter) async -> Result<Sample, any Error> {
let sample = await fetcher.fetchSampleAsync()
let result = await callFromNative.fetchSample(parameter: .init(text: sample.text, id: sample.id))
switch result {
case .success(let newSample):
return .success(newSample)
case .failure(let failure):
return .failure(failure)
}
}
func fetchSampleSync(parameter: SampleParameter) throws -> Sample {
let sample = fetcher.fetchSampleSync()
print("fetchSampleSync test")
return sample
}
func objectSampleAsync(parameter: Any?) async -> Result<Any?, any Error> {
let testResult = await ObjectSampleTest.testAsync(parameter: parameter)
return .success(parameter)
}
func objectSampleSync(parameter: Any?) throws -> Any? {
parameter
}
}
enum ObjectSampleTest {
nonisolated static func testAsync(parameter: Any?) async -> Any? {
parameter
}
}
@available(iOS, obsoleted: 13.0)
final class SampleApiLegacyImpl: SampleApiLegacy {
let callFromNative: CallFromNativeLegacy
init(callFromNative: CallFromNativeLegacy) {
self.callFromNative = callFromNative
}
func fetchSampleAsync(parameter: SampleParameter, completion: @escaping (Result<Sample, any Error>) -> Void) {
let sample = Sample(text: "test", id: 1)
callFromNative.fetchSample(
parameter: .init(text: sample.text, id: sample.id)) { result in
switch result {
case .success(let newSample):
completion(.success(newSample))
case .failure(let failure):
completion(.failure(failure))
}
}
}
func fetchSampleSync(parameter: SampleParameter) throws -> Sample {
return Sample(text: "test3", id: 3)
}
func objectSampleAsync(parameter: Any?, completion: @escaping (Result<Any?, any Error>) -> Void) {
completion(.success(parameter))
}
func objectSampleSync(parameter: Any?) throws -> Any? {
parameter
}
}
@MainActor
final class SampleDataFetcher {
nonisolated func fetchSampleAsync() async -> Sample {
Sample(text: "test", id: 1)
}
func fetchSampleSync() -> Sample {
Sample(text: "test2", id: 2)
}
}iwasawa-tadayasu-dmm, 1mash0, satorbs, natebytes, orestesgaolin and 1 more
Metadata
Metadata
Assignees
Labels
P2Important issues not at the top of the work listImportant issues not at the top of the work listc: new featureNothing broken; request for a new capabilityNothing broken; request for a new capabilityc: proposalA detailed proposal for a change to FlutterA detailed proposal for a change to Flutterp: pigeonrelated to pigeon messaging codegen toolrelated to pigeon messaging codegen toolpackageflutter/packages repository. See also p: labels.flutter/packages repository. See also p: labels.team-ecosystemOwned by Ecosystem teamOwned by Ecosystem teamtriaged-ecosystemTriaged by Ecosystem teamTriaged by Ecosystem team