Skip to content

Commit

Permalink
added support of modern async api Swift/Kotlin
Browse files Browse the repository at this point in the history
  • Loading branch information
feduke-nukem committed Dec 23, 2024
1 parent 3515aba commit 583d6d7
Show file tree
Hide file tree
Showing 38 changed files with 976 additions and 213 deletions.
1 change: 1 addition & 0 deletions packages/pigeon/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## NEXT

* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4.
* Added support of modern asynchronous api for Swift (async) and Kotlin (suspend).

## 22.7.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ public interface ExampleHostApi {

void sendMessage(@NonNull MessageData message, @NonNull Result<Boolean> result);

void sendMessageModernAsync(@NonNull MessageData message, @NonNull Result<Boolean> result);

/** The codec used by ExampleHostApi. */
static @NonNull MessageCodec<Object> getCodec() {
return PigeonCodec.INSTANCE;
Expand Down Expand Up @@ -385,6 +387,38 @@ public void error(Throwable error) {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessageModernAsync"
+ messageChannelSuffix,
getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<>();
ArrayList<Object> args = (ArrayList<Object>) message;
MessageData messageArg = (MessageData) args.get(0);
Result<Boolean> resultCallback =
new Result<Boolean>() {
public void success(Boolean result) {
wrapped.add(0, result);
reply.reply(wrapped);
}

public void error(Throwable error) {
ArrayList<Object> wrappedError = wrapError(error);
reply.reply(wrappedError);
}
};

api.sendMessageModernAsync(messageArg, resultCallback);
});
} else {
channel.setMessageHandler(null);
}
}
}
}
/** Generated class from Pigeon that represents Flutter messages that can be called from Java. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import android.os.Looper
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.plugins.FlutterPlugin
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay

// #docregion kotlin-class
private class PigeonApiImplementation : ExampleHostApi {
Expand All @@ -39,6 +43,16 @@ private class PigeonApiImplementation : ExampleHostApi {
}
callback(Result.success(true))
}

override suspend fun sendMessageModernAsync(message: MessageData): Boolean {
if (message.code == Code.ONE) {
throw FlutterError("code", "message", "details")
}

delay(2000)

return true
}
}
// #enddocregion kotlin-class

Expand Down Expand Up @@ -111,7 +125,10 @@ class MainActivity : FlutterActivity() {
super.configureFlutterEngine(flutterEngine)

val api = PigeonApiImplementation()
ExampleHostApi.setUp(flutterEngine.dartExecutor.binaryMessenger, api)
ExampleHostApi.setUp(
flutterEngine.dartExecutor.binaryMessenger,
api,
coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob()))
// #docregion kotlin-init-event
val eventListener = EventListener()
StreamEventsStreamHandler.register(flutterEngine.dartExecutor.binaryMessenger, eventListener)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

private fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
Expand Down Expand Up @@ -120,6 +124,8 @@ interface ExampleHostApi {

fun sendMessage(message: MessageData, callback: (Result<Boolean>) -> Unit)

suspend fun sendMessageModernAsync(message: MessageData): Boolean

companion object {
/** The codec used by ExampleHostApi. */
val codec: MessageCodec<Any?> by lazy { MessagesPigeonCodec() }
Expand All @@ -128,7 +134,8 @@ interface ExampleHostApi {
fun setUp(
binaryMessenger: BinaryMessenger,
api: ExampleHostApi?,
messageChannelSuffix: String = ""
messageChannelSuffix: String = "",
coroutineScope: CoroutineScope
) {
val separatedMessageChannelSuffix =
if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
Expand Down Expand Up @@ -199,6 +206,30 @@ interface ExampleHostApi {
channel.setMessageHandler(null)
}
}
run {
val channel =
BasicMessageChannel<Any?>(
binaryMessenger,
"dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessageModernAsync$separatedMessageChannelSuffix",
codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val messageArg = args[0] as MessageData
coroutineScope.launch {
val wrapped: List<Any?> =
try {
listOf(api.sendMessageModernAsync(messageArg))
} catch (exception: Throwable) {
wrapError(exception)
}
withContext(Dispatchers.Main) { reply.reply(wrapped) }
}
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/pigeon/example/app/ios/Podfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '12.0'
platform :ios, '13.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
Expand Down
23 changes: 22 additions & 1 deletion packages/pigeon/example/app/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 60;
objectVersion = 54;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -146,6 +146,7 @@
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
C56908DE131A44BB42AA2CE1 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
Expand Down Expand Up @@ -263,6 +264,23 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
C56908DE131A44BB42AA2CE1 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
Expand Down Expand Up @@ -358,6 +376,7 @@
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down Expand Up @@ -486,6 +505,7 @@
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -508,6 +528,7 @@
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down
10 changes: 10 additions & 0 deletions packages/pigeon/example/app/ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ private class PigeonApiImplementation: ExampleHostApi {
}
completion(.success(true))
}

func sendMessageModernAsync(message: MessageData) async throws -> Bool {
try? await Task.sleep(nanoseconds: 2_000_000_000)

if message.code == Code.one {
throw PigeonError(code: "code", message: "message", details: "details")
}

return true
}
}
// #enddocregion swift-class

Expand Down
25 changes: 25 additions & 0 deletions packages/pigeon/example/app/ios/Runner/Messages.g.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ protocol ExampleHostApi {
func getHostLanguage() throws -> String
func add(_ a: Int64, to b: Int64) throws -> Int64
func sendMessage(message: MessageData, completion: @escaping (Result<Bool, Error>) -> Void)
func sendMessageModernAsync(message: MessageData) async throws -> Bool
}

/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
Expand Down Expand Up @@ -222,6 +223,30 @@ class ExampleHostApiSetup {
} else {
sendMessageChannel.setMessageHandler(nil)
}
let sendMessageModernAsyncChannel = FlutterBasicMessageChannel(
name:
"dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessageModernAsync\(channelSuffix)",
binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
sendMessageModernAsyncChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let messageArg = args[0] as! MessageData
Task {
do {
let result = try await api.sendMessageModernAsync(message: messageArg)
await MainActor.run {
reply(wrapResult(result))
}
} catch {
await MainActor.run {
reply(wrapError(error))
}
}
}
}
} else {
sendMessageModernAsyncChannel.setMessageHandler(nil)
}
}
}
/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift.
Expand Down
41 changes: 40 additions & 1 deletion packages/pigeon/example/app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ class _MyHomePageState extends State<MyHomePage> {
return Future<bool>(() => true);
}
}

Future<bool> sendMessageModernAsync(String messageText) {
final MessageData message = MessageData(
code: Code.two,
data: <String, String>{'header': 'this is a header'},
description: 'uri text',
);

return _api.sendMessageModernAsync(message);
}
// #enddocregion main-dart

// #docregion main-dart-event
Expand Down Expand Up @@ -146,7 +156,36 @@ class _MyHomePageState extends State<MyHomePage> {
},
)
else
const Text('event channels are not supported on this platform')
const Text('event channels are not supported on this platform'),
if (Platform.isAndroid || Platform.isIOS)
ElevatedButton(
onPressed: () async {
final ScaffoldMessengerState scaffoldMessenger =
ScaffoldMessenger.of(context);
scaffoldMessenger.hideCurrentSnackBar();

try {
final bool result = await sendMessageModernAsync('test');

scaffoldMessenger.showSnackBar(
SnackBar(
content: Text(
result.toString(),
),
),
);
} catch (e) {
scaffoldMessenger.showSnackBar(
SnackBar(
content: Text(
e.toString(),
),
),
);
}
},
child: const Text('Send message modern async'),
)
],
),
),
Expand Down
29 changes: 29 additions & 0 deletions packages/pigeon/example/app/lib/src/messages.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,35 @@ class ExampleHostApi {
return (pigeonVar_replyList[0] as bool?)!;
}
}

Future<bool> sendMessageModernAsync(MessageData message) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessageModernAsync$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_channel.send(<Object?>[message]) as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as bool?)!;
}
}
}

abstract class MessageFlutterApi {
Expand Down
Loading

0 comments on commit 583d6d7

Please sign in to comment.