diff --git a/packages/url_launcher/url_launcher_linux/CHANGELOG.md b/packages/url_launcher/url_launcher_linux/CHANGELOG.md index 94d7e39a70c..9b5c7d9037f 100644 --- a/packages/url_launcher/url_launcher_linux/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_linux/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 3.2.0 +* Updates platform channels to use Pigeon. * Updates minimum supported SDK version to Flutter 3.19/Dart 3.3. ## 3.1.1 diff --git a/packages/url_launcher/url_launcher_linux/lib/src/messages.g.dart b/packages/url_launcher/url_launcher_linux/lib/src/messages.g.dart new file mode 100644 index 00000000000..ed74617c7e0 --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/lib/src/messages.g.dart @@ -0,0 +1,94 @@ +// 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 (v21.1.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); +} + +class UrlLauncherApi { + /// Constructor for [UrlLauncherApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + UrlLauncherApi( + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : __pigeon_binaryMessenger = binaryMessenger, + __pigeon_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? __pigeon_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String __pigeon_messageChannelSuffix; + + /// Returns true if the URL can definitely be launched. + Future canLaunchUrl(String url) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.url_launcher_linux.UrlLauncherApi.canLaunchUrl$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([url]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + + /// Opens the URL externally, returning an error string on failure. + Future launchUrl(String url) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.url_launcher_linux.UrlLauncherApi.launchUrl$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([url]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as String?); + } + } +} diff --git a/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart b/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart index ed425a0357a..b5c85043927 100644 --- a/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart +++ b/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart @@ -4,26 +4,32 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:url_launcher_platform_interface/link.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; -const MethodChannel _channel = - MethodChannel('plugins.flutter.io/url_launcher_linux'); +import 'src/messages.g.dart'; /// An implementation of [UrlLauncherPlatform] for Linux. class UrlLauncherLinux extends UrlLauncherPlatform { + /// Creates a new URL launcher instance. + UrlLauncherLinux({@visibleForTesting UrlLauncherApi? api}) + : _hostApi = api ?? UrlLauncherApi(); + /// Registers this class as the default instance of [UrlLauncherPlatform]. static void registerWith() { UrlLauncherPlatform.instance = UrlLauncherLinux(); } + final UrlLauncherApi _hostApi; + @override final LinkDelegate? linkDelegate = null; @override Future canLaunch(String url) async { - return (await _channel.invokeMethod('canLaunch', url)) ?? false; + return _hostApi.canLaunchUrl(url); } @override @@ -44,7 +50,15 @@ class UrlLauncherLinux extends UrlLauncherPlatform { @override Future launchUrl(String url, LaunchOptions options) async { - return (await _channel.invokeMethod('launch', url)) ?? false; + final String? error = await _hostApi.launchUrl(url); + if (error != null) { + // TODO(stuartmorgan): Standardize errors across the entire plugin, + // instead of using PlatformException. This preserves the pre-Pigeon + // behavior of the C code returning this error response. + throw PlatformException( + code: 'Launch Error', message: 'Failed to launch URL: $error'); + } + return true; } @override diff --git a/packages/url_launcher/url_launcher_linux/linux/CMakeLists.txt b/packages/url_launcher/url_launcher_linux/linux/CMakeLists.txt index 58e770653d8..a52bd5adcc1 100644 --- a/packages/url_launcher/url_launcher_linux/linux/CMakeLists.txt +++ b/packages/url_launcher/url_launcher_linux/linux/CMakeLists.txt @@ -7,6 +7,7 @@ cmake_policy(VERSION 3.10...3.24) set(PLUGIN_NAME "${PROJECT_NAME}_plugin") list(APPEND PLUGIN_SOURCES + "messages.g.cc" "url_launcher_plugin.cc" ) diff --git a/packages/url_launcher/url_launcher_linux/linux/messages.g.cc b/packages/url_launcher/url_launcher_linux/linux/messages.g.cc new file mode 100644 index 00000000000..6c047397fbc --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/linux/messages.g.cc @@ -0,0 +1,306 @@ +// 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 (v21.1.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +#include "messages.g.h" + +G_DECLARE_FINAL_TYPE(FulMessageCodec, ful_message_codec, FUL, MESSAGE_CODEC, + FlStandardMessageCodec) + +struct _FulMessageCodec { + FlStandardMessageCodec parent_instance; +}; + +G_DEFINE_TYPE(FulMessageCodec, ful_message_codec, + fl_standard_message_codec_get_type()) + +static gboolean ful_message_codec_write_value(FlStandardMessageCodec* codec, + GByteArray* buffer, + FlValue* value, GError** error) { + if (fl_value_get_type(value) == FL_VALUE_TYPE_CUSTOM) { + switch (fl_value_get_custom_type(value)) {} + } + + return FL_STANDARD_MESSAGE_CODEC_CLASS(ful_message_codec_parent_class) + ->write_value(codec, buffer, value, error); +} + +static FlValue* ful_message_codec_read_value_of_type( + FlStandardMessageCodec* codec, GBytes* buffer, size_t* offset, int type, + GError** error) { + switch (type) { + default: + return FL_STANDARD_MESSAGE_CODEC_CLASS(ful_message_codec_parent_class) + ->read_value_of_type(codec, buffer, offset, type, error); + } +} + +static void ful_message_codec_init(FulMessageCodec* self) {} + +static void ful_message_codec_class_init(FulMessageCodecClass* klass) { + FL_STANDARD_MESSAGE_CODEC_CLASS(klass)->write_value = + ful_message_codec_write_value; + FL_STANDARD_MESSAGE_CODEC_CLASS(klass)->read_value_of_type = + ful_message_codec_read_value_of_type; +} + +static FulMessageCodec* ful_message_codec_new() { + FulMessageCodec* self = + FUL_MESSAGE_CODEC(g_object_new(ful_message_codec_get_type(), nullptr)); + return self; +} + +struct _FulUrlLauncherApiCanLaunchUrlResponse { + GObject parent_instance; + + FlValue* value; +}; + +G_DEFINE_TYPE(FulUrlLauncherApiCanLaunchUrlResponse, + ful_url_launcher_api_can_launch_url_response, G_TYPE_OBJECT) + +static void ful_url_launcher_api_can_launch_url_response_dispose( + GObject* object) { + FulUrlLauncherApiCanLaunchUrlResponse* self = + FUL_URL_LAUNCHER_API_CAN_LAUNCH_URL_RESPONSE(object); + g_clear_pointer(&self->value, fl_value_unref); + G_OBJECT_CLASS(ful_url_launcher_api_can_launch_url_response_parent_class) + ->dispose(object); +} + +static void ful_url_launcher_api_can_launch_url_response_init( + FulUrlLauncherApiCanLaunchUrlResponse* self) {} + +static void ful_url_launcher_api_can_launch_url_response_class_init( + FulUrlLauncherApiCanLaunchUrlResponseClass* klass) { + G_OBJECT_CLASS(klass)->dispose = + ful_url_launcher_api_can_launch_url_response_dispose; +} + +FulUrlLauncherApiCanLaunchUrlResponse* +ful_url_launcher_api_can_launch_url_response_new(gboolean return_value) { + FulUrlLauncherApiCanLaunchUrlResponse* self = + FUL_URL_LAUNCHER_API_CAN_LAUNCH_URL_RESPONSE(g_object_new( + ful_url_launcher_api_can_launch_url_response_get_type(), nullptr)); + self->value = fl_value_new_list(); + fl_value_append_take(self->value, fl_value_new_bool(return_value)); + return self; +} + +FulUrlLauncherApiCanLaunchUrlResponse* +ful_url_launcher_api_can_launch_url_response_new_error(const gchar* code, + const gchar* message, + FlValue* details) { + FulUrlLauncherApiCanLaunchUrlResponse* self = + FUL_URL_LAUNCHER_API_CAN_LAUNCH_URL_RESPONSE(g_object_new( + ful_url_launcher_api_can_launch_url_response_get_type(), nullptr)); + self->value = fl_value_new_list(); + fl_value_append_take(self->value, fl_value_new_string(code)); + fl_value_append_take(self->value, + fl_value_new_string(message != nullptr ? message : "")); + fl_value_append_take(self->value, details != nullptr ? fl_value_ref(details) + : fl_value_new_null()); + return self; +} + +struct _FulUrlLauncherApiLaunchUrlResponse { + GObject parent_instance; + + FlValue* value; +}; + +G_DEFINE_TYPE(FulUrlLauncherApiLaunchUrlResponse, + ful_url_launcher_api_launch_url_response, G_TYPE_OBJECT) + +static void ful_url_launcher_api_launch_url_response_dispose(GObject* object) { + FulUrlLauncherApiLaunchUrlResponse* self = + FUL_URL_LAUNCHER_API_LAUNCH_URL_RESPONSE(object); + g_clear_pointer(&self->value, fl_value_unref); + G_OBJECT_CLASS(ful_url_launcher_api_launch_url_response_parent_class) + ->dispose(object); +} + +static void ful_url_launcher_api_launch_url_response_init( + FulUrlLauncherApiLaunchUrlResponse* self) {} + +static void ful_url_launcher_api_launch_url_response_class_init( + FulUrlLauncherApiLaunchUrlResponseClass* klass) { + G_OBJECT_CLASS(klass)->dispose = + ful_url_launcher_api_launch_url_response_dispose; +} + +FulUrlLauncherApiLaunchUrlResponse* +ful_url_launcher_api_launch_url_response_new(const gchar* return_value) { + FulUrlLauncherApiLaunchUrlResponse* self = + FUL_URL_LAUNCHER_API_LAUNCH_URL_RESPONSE(g_object_new( + ful_url_launcher_api_launch_url_response_get_type(), nullptr)); + self->value = fl_value_new_list(); + fl_value_append_take(self->value, return_value != nullptr + ? fl_value_new_string(return_value) + : fl_value_new_null()); + return self; +} + +FulUrlLauncherApiLaunchUrlResponse* +ful_url_launcher_api_launch_url_response_new_error(const gchar* code, + const gchar* message, + FlValue* details) { + FulUrlLauncherApiLaunchUrlResponse* self = + FUL_URL_LAUNCHER_API_LAUNCH_URL_RESPONSE(g_object_new( + ful_url_launcher_api_launch_url_response_get_type(), nullptr)); + self->value = fl_value_new_list(); + fl_value_append_take(self->value, fl_value_new_string(code)); + fl_value_append_take(self->value, + fl_value_new_string(message != nullptr ? message : "")); + fl_value_append_take(self->value, details != nullptr ? fl_value_ref(details) + : fl_value_new_null()); + return self; +} + +G_DECLARE_FINAL_TYPE(FulUrlLauncherApi, ful_url_launcher_api, FUL, + URL_LAUNCHER_API, GObject) + +struct _FulUrlLauncherApi { + GObject parent_instance; + + const FulUrlLauncherApiVTable* vtable; + gpointer user_data; + GDestroyNotify user_data_free_func; +}; + +G_DEFINE_TYPE(FulUrlLauncherApi, ful_url_launcher_api, G_TYPE_OBJECT) + +static void ful_url_launcher_api_dispose(GObject* object) { + FulUrlLauncherApi* self = FUL_URL_LAUNCHER_API(object); + if (self->user_data != nullptr) { + self->user_data_free_func(self->user_data); + } + self->user_data = nullptr; + G_OBJECT_CLASS(ful_url_launcher_api_parent_class)->dispose(object); +} + +static void ful_url_launcher_api_init(FulUrlLauncherApi* self) {} + +static void ful_url_launcher_api_class_init(FulUrlLauncherApiClass* klass) { + G_OBJECT_CLASS(klass)->dispose = ful_url_launcher_api_dispose; +} + +static FulUrlLauncherApi* ful_url_launcher_api_new( + const FulUrlLauncherApiVTable* vtable, gpointer user_data, + GDestroyNotify user_data_free_func) { + FulUrlLauncherApi* self = FUL_URL_LAUNCHER_API( + g_object_new(ful_url_launcher_api_get_type(), nullptr)); + self->vtable = vtable; + self->user_data = user_data; + self->user_data_free_func = user_data_free_func; + return self; +} + +static void ful_url_launcher_api_can_launch_url_cb( + FlBasicMessageChannel* channel, FlValue* message_, + FlBasicMessageChannelResponseHandle* response_handle, gpointer user_data) { + FulUrlLauncherApi* self = FUL_URL_LAUNCHER_API(user_data); + + if (self->vtable == nullptr || self->vtable->can_launch_url == nullptr) { + return; + } + + FlValue* value0 = fl_value_get_list_value(message_, 0); + const gchar* url = fl_value_get_string(value0); + g_autoptr(FulUrlLauncherApiCanLaunchUrlResponse) response = + self->vtable->can_launch_url(url, self->user_data); + if (response == nullptr) { + g_warning("No response returned to %s.%s", "UrlLauncherApi", + "canLaunchUrl"); + return; + } + + g_autoptr(GError) error = NULL; + if (!fl_basic_message_channel_respond(channel, response_handle, + response->value, &error)) { + g_warning("Failed to send response to %s.%s: %s", "UrlLauncherApi", + "canLaunchUrl", error->message); + } +} + +static void ful_url_launcher_api_launch_url_cb( + FlBasicMessageChannel* channel, FlValue* message_, + FlBasicMessageChannelResponseHandle* response_handle, gpointer user_data) { + FulUrlLauncherApi* self = FUL_URL_LAUNCHER_API(user_data); + + if (self->vtable == nullptr || self->vtable->launch_url == nullptr) { + return; + } + + FlValue* value0 = fl_value_get_list_value(message_, 0); + const gchar* url = fl_value_get_string(value0); + g_autoptr(FulUrlLauncherApiLaunchUrlResponse) response = + self->vtable->launch_url(url, self->user_data); + if (response == nullptr) { + g_warning("No response returned to %s.%s", "UrlLauncherApi", "launchUrl"); + return; + } + + g_autoptr(GError) error = NULL; + if (!fl_basic_message_channel_respond(channel, response_handle, + response->value, &error)) { + g_warning("Failed to send response to %s.%s: %s", "UrlLauncherApi", + "launchUrl", error->message); + } +} + +void ful_url_launcher_api_set_method_handlers( + FlBinaryMessenger* messenger, const gchar* suffix, + const FulUrlLauncherApiVTable* vtable, gpointer user_data, + GDestroyNotify user_data_free_func) { + g_autofree gchar* dot_suffix = + suffix != nullptr ? g_strdup_printf(".%s", suffix) : g_strdup(""); + g_autoptr(FulUrlLauncherApi) api_data = + ful_url_launcher_api_new(vtable, user_data, user_data_free_func); + + g_autoptr(FulMessageCodec) codec = ful_message_codec_new(); + g_autofree gchar* can_launch_url_channel_name = g_strdup_printf( + "dev.flutter.pigeon.url_launcher_linux.UrlLauncherApi.canLaunchUrl%s", + dot_suffix); + g_autoptr(FlBasicMessageChannel) can_launch_url_channel = + fl_basic_message_channel_new(messenger, can_launch_url_channel_name, + FL_MESSAGE_CODEC(codec)); + fl_basic_message_channel_set_message_handler( + can_launch_url_channel, ful_url_launcher_api_can_launch_url_cb, + g_object_ref(api_data), g_object_unref); + g_autofree gchar* launch_url_channel_name = g_strdup_printf( + "dev.flutter.pigeon.url_launcher_linux.UrlLauncherApi.launchUrl%s", + dot_suffix); + g_autoptr(FlBasicMessageChannel) launch_url_channel = + fl_basic_message_channel_new(messenger, launch_url_channel_name, + FL_MESSAGE_CODEC(codec)); + fl_basic_message_channel_set_message_handler( + launch_url_channel, ful_url_launcher_api_launch_url_cb, + g_object_ref(api_data), g_object_unref); +} + +void ful_url_launcher_api_clear_method_handlers(FlBinaryMessenger* messenger, + const gchar* suffix) { + g_autofree gchar* dot_suffix = + suffix != nullptr ? g_strdup_printf(".%s", suffix) : g_strdup(""); + + g_autoptr(FulMessageCodec) codec = ful_message_codec_new(); + g_autofree gchar* can_launch_url_channel_name = g_strdup_printf( + "dev.flutter.pigeon.url_launcher_linux.UrlLauncherApi.canLaunchUrl%s", + dot_suffix); + g_autoptr(FlBasicMessageChannel) can_launch_url_channel = + fl_basic_message_channel_new(messenger, can_launch_url_channel_name, + FL_MESSAGE_CODEC(codec)); + fl_basic_message_channel_set_message_handler(can_launch_url_channel, nullptr, + nullptr, nullptr); + g_autofree gchar* launch_url_channel_name = g_strdup_printf( + "dev.flutter.pigeon.url_launcher_linux.UrlLauncherApi.launchUrl%s", + dot_suffix); + g_autoptr(FlBasicMessageChannel) launch_url_channel = + fl_basic_message_channel_new(messenger, launch_url_channel_name, + FL_MESSAGE_CODEC(codec)); + fl_basic_message_channel_set_message_handler(launch_url_channel, nullptr, + nullptr, nullptr); +} diff --git a/packages/url_launcher/url_launcher_linux/linux/messages.g.h b/packages/url_launcher/url_launcher_linux/linux/messages.g.h new file mode 100644 index 00000000000..bf6bcf67819 --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/linux/messages.g.h @@ -0,0 +1,115 @@ +// 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 (v21.1.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +#ifndef PIGEON_MESSAGES_G_H_ +#define PIGEON_MESSAGES_G_H_ + +#include + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(FulUrlLauncherApiCanLaunchUrlResponse, + ful_url_launcher_api_can_launch_url_response, FUL, + URL_LAUNCHER_API_CAN_LAUNCH_URL_RESPONSE, GObject) + +/** + * ful_url_launcher_api_can_launch_url_response_new: + * + * Creates a new response to UrlLauncherApi.canLaunchUrl. + * + * Returns: a new #FulUrlLauncherApiCanLaunchUrlResponse + */ +FulUrlLauncherApiCanLaunchUrlResponse* +ful_url_launcher_api_can_launch_url_response_new(gboolean return_value); + +/** + * ful_url_launcher_api_can_launch_url_response_new_error: + * @code: error code. + * @message: error message. + * @details: (allow-none): error details or %NULL. + * + * Creates a new error response to UrlLauncherApi.canLaunchUrl. + * + * Returns: a new #FulUrlLauncherApiCanLaunchUrlResponse + */ +FulUrlLauncherApiCanLaunchUrlResponse* +ful_url_launcher_api_can_launch_url_response_new_error(const gchar* code, + const gchar* message, + FlValue* details); + +G_DECLARE_FINAL_TYPE(FulUrlLauncherApiLaunchUrlResponse, + ful_url_launcher_api_launch_url_response, FUL, + URL_LAUNCHER_API_LAUNCH_URL_RESPONSE, GObject) + +/** + * ful_url_launcher_api_launch_url_response_new: + * + * Creates a new response to UrlLauncherApi.launchUrl. + * + * Returns: a new #FulUrlLauncherApiLaunchUrlResponse + */ +FulUrlLauncherApiLaunchUrlResponse* +ful_url_launcher_api_launch_url_response_new(const gchar* return_value); + +/** + * ful_url_launcher_api_launch_url_response_new_error: + * @code: error code. + * @message: error message. + * @details: (allow-none): error details or %NULL. + * + * Creates a new error response to UrlLauncherApi.launchUrl. + * + * Returns: a new #FulUrlLauncherApiLaunchUrlResponse + */ +FulUrlLauncherApiLaunchUrlResponse* +ful_url_launcher_api_launch_url_response_new_error(const gchar* code, + const gchar* message, + FlValue* details); + +/** + * FulUrlLauncherApiVTable: + * + * Table of functions exposed by UrlLauncherApi to be implemented by the API + * provider. + */ +typedef struct { + FulUrlLauncherApiCanLaunchUrlResponse* (*can_launch_url)(const gchar* url, + gpointer user_data); + FulUrlLauncherApiLaunchUrlResponse* (*launch_url)(const gchar* url, + gpointer user_data); +} FulUrlLauncherApiVTable; + +/** + * ful_url_launcher_api_set_method_handlers: + * + * @messenger: an #FlBinaryMessenger. + * @suffix: (allow-none): a suffix to add to the API or %NULL for none. + * @vtable: implementations of the methods in this API. + * @user_data: (closure): user data to pass to the functions in @vtable. + * @user_data_free_func: (allow-none): a function which gets called to free + * @user_data, or %NULL. + * + * Connects the method handlers in the UrlLauncherApi API. + */ +void ful_url_launcher_api_set_method_handlers( + FlBinaryMessenger* messenger, const gchar* suffix, + const FulUrlLauncherApiVTable* vtable, gpointer user_data, + GDestroyNotify user_data_free_func); + +/** + * ful_url_launcher_api_clear_method_handlers: + * + * @messenger: an #FlBinaryMessenger. + * @suffix: (allow-none): a suffix to add to the API or %NULL for none. + * + * Clears the method handlers in the UrlLauncherApi API. + */ +void ful_url_launcher_api_clear_method_handlers(FlBinaryMessenger* messenger, + const gchar* suffix); + +G_END_DECLS + +#endif // PIGEON_MESSAGES_G_H_ diff --git a/packages/url_launcher/url_launcher_linux/linux/test/url_launcher_linux_test.cc b/packages/url_launcher/url_launcher_linux/linux/test/url_launcher_linux_test.cc index 7d48008a6eb..f5a5fdbfde2 100644 --- a/packages/url_launcher/url_launcher_linux/linux/test/url_launcher_linux_test.cc +++ b/packages/url_launcher/url_launcher_linux/linux/test/url_launcher_linux_test.cc @@ -11,65 +11,74 @@ #include "include/url_launcher_linux/url_launcher_plugin.h" #include "url_launcher_plugin_private.h" +// Re-declare the opaque struct as a temporary workaround for the lack of +// APIs for reading host API response objects. +// TODO(stuartmorgan): Remove this once the following is fixed: +// https://github.com/flutter/flutter/issues/152166. +struct _FulUrlLauncherApiCanLaunchUrlResponse { + GObject parent_instance; + + FlValue* value; +}; + namespace url_launcher_plugin { namespace test { TEST(UrlLauncherPlugin, CanLaunchSuccess) { - g_autoptr(FlValue) args = fl_value_new_string("https://flutter.dev"); - g_autoptr(FlMethodResponse) response = can_launch(nullptr, args); + g_autoptr(FulUrlLauncherApiCanLaunchUrlResponse) response = + handle_can_launch_url("https://flutter.dev", nullptr); ASSERT_NE(response, nullptr); - ASSERT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); + ASSERT_TRUE(fl_value_get_type(response->value) == FL_VALUE_TYPE_LIST); + ASSERT_TRUE(fl_value_get_length(response->value) == 1); g_autoptr(FlValue) expected = fl_value_new_bool(true); - EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( - FL_METHOD_SUCCESS_RESPONSE(response)), - expected)); + EXPECT_TRUE( + fl_value_equal(fl_value_get_list_value(response->value, 0), expected)); } TEST(UrlLauncherPlugin, CanLaunchFailureUnhandled) { - g_autoptr(FlValue) args = fl_value_new_string("madeup:scheme"); - g_autoptr(FlMethodResponse) response = can_launch(nullptr, args); + g_autoptr(FulUrlLauncherApiCanLaunchUrlResponse) response = + handle_can_launch_url("madeup:scheme", nullptr); ASSERT_NE(response, nullptr); - ASSERT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); + ASSERT_TRUE(fl_value_get_type(response->value) == FL_VALUE_TYPE_LIST); + ASSERT_TRUE(fl_value_get_length(response->value) == 1); g_autoptr(FlValue) expected = fl_value_new_bool(false); - EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( - FL_METHOD_SUCCESS_RESPONSE(response)), - expected)); + EXPECT_TRUE( + fl_value_equal(fl_value_get_list_value(response->value, 0), expected)); } TEST(UrlLauncherPlugin, CanLaunchFileSuccess) { - g_autoptr(FlValue) args = fl_value_new_string("file:///"); - g_autoptr(FlMethodResponse) response = can_launch(nullptr, args); + g_autoptr(FulUrlLauncherApiCanLaunchUrlResponse) response = + handle_can_launch_url("file:///", nullptr); ASSERT_NE(response, nullptr); - ASSERT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); + ASSERT_TRUE(fl_value_get_type(response->value) == FL_VALUE_TYPE_LIST); + ASSERT_TRUE(fl_value_get_length(response->value) == 1); g_autoptr(FlValue) expected = fl_value_new_bool(true); - EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( - FL_METHOD_SUCCESS_RESPONSE(response)), - expected)); + EXPECT_TRUE( + fl_value_equal(fl_value_get_list_value(response->value, 0), expected)); } TEST(UrlLauncherPlugin, CanLaunchFailureInvalidFileExtension) { - g_autoptr(FlValue) args = - fl_value_new_string("file:///madeup.madeupextension"); - g_autoptr(FlMethodResponse) response = can_launch(nullptr, args); + g_autoptr(FulUrlLauncherApiCanLaunchUrlResponse) response = + handle_can_launch_url("file:///madeup.madeupextension", nullptr); ASSERT_NE(response, nullptr); - ASSERT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); + ASSERT_TRUE(fl_value_get_type(response->value) == FL_VALUE_TYPE_LIST); + ASSERT_TRUE(fl_value_get_length(response->value) == 1); g_autoptr(FlValue) expected = fl_value_new_bool(false); - EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( - FL_METHOD_SUCCESS_RESPONSE(response)), - expected)); + EXPECT_TRUE( + fl_value_equal(fl_value_get_list_value(response->value, 0), expected)); } // For consistency with the established mobile implementations, // an invalid URL should return false, not an error. TEST(UrlLauncherPlugin, CanLaunchFailureInvalidUrl) { - g_autoptr(FlValue) args = fl_value_new_string(""); - g_autoptr(FlMethodResponse) response = can_launch(nullptr, args); + g_autoptr(FulUrlLauncherApiCanLaunchUrlResponse) response = + handle_can_launch_url("", nullptr); ASSERT_NE(response, nullptr); - ASSERT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); + ASSERT_TRUE(fl_value_get_type(response->value) == FL_VALUE_TYPE_LIST); + ASSERT_TRUE(fl_value_get_length(response->value) == 1); g_autoptr(FlValue) expected = fl_value_new_bool(false); - EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( - FL_METHOD_SUCCESS_RESPONSE(response)), - expected)); + EXPECT_TRUE( + fl_value_equal(fl_value_get_list_value(response->value, 0), expected)); } } // namespace test diff --git a/packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin.cc b/packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin.cc index b12b394516e..0b814f8bad5 100644 --- a/packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin.cc +++ b/packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin.cc @@ -9,21 +9,13 @@ #include +#include "messages.g.h" #include "url_launcher_plugin_private.h" -// See url_launcher_channel.dart for documentation. -const char kChannelName[] = "plugins.flutter.io/url_launcher_linux"; -const char kLaunchError[] = "Launch Error"; -const char kCanLaunchMethod[] = "canLaunch"; -const char kLaunchMethod[] = "launch"; - struct _FlUrlLauncherPlugin { GObject parent_instance; FlPluginRegistrar* registrar; - - // Connection to Flutter engine. - FlMethodChannel* channel; }; G_DEFINE_TYPE(FlUrlLauncherPlugin, fl_url_launcher_plugin, g_object_get_type()) @@ -38,9 +30,9 @@ static gboolean can_launch_uri_with_file_resource(FlUrlLauncherPlugin* self, return app_info != nullptr; } -// Called to check if a URL can be launched. -FlMethodResponse* can_launch(FlUrlLauncherPlugin* self, FlValue* args) { - const gchar* url = fl_value_get_string(args); +FulUrlLauncherApiCanLaunchUrlResponse* handle_can_launch_url( + const gchar* url, gpointer user_data) { + FlUrlLauncherPlugin* self = FL_URL_LAUNCHER_PLUGIN(user_data); gboolean is_launchable = FALSE; g_autofree gchar* scheme = g_uri_parse_scheme(url); @@ -54,13 +46,13 @@ FlMethodResponse* can_launch(FlUrlLauncherPlugin* self, FlValue* args) { } } - g_autoptr(FlValue) result = fl_value_new_bool(is_launchable); - return FL_METHOD_RESPONSE(fl_method_success_response_new(result)); + return ful_url_launcher_api_can_launch_url_response_new(is_launchable); } // Called when a URL should launch. -static FlMethodResponse* launch(FlUrlLauncherPlugin* self, FlValue* args) { - const gchar* url = fl_value_get_string(args); +static FulUrlLauncherApiLaunchUrlResponse* handle_launch_url( + const gchar* url, gpointer user_data) { + FlUrlLauncherPlugin* self = FL_URL_LAUNCHER_PLUGIN(user_data); FlView* view = fl_plugin_registrar_get_view(self->registrar); g_autoptr(GError) error = nullptr; @@ -72,42 +64,18 @@ static FlMethodResponse* launch(FlUrlLauncherPlugin* self, FlValue* args) { launched = g_app_info_launch_default_for_uri(url, nullptr, &error); } if (!launched) { - g_autofree gchar* message = - g_strdup_printf("Failed to launch URL: %s", error->message); - return FL_METHOD_RESPONSE( - fl_method_error_response_new(kLaunchError, message, nullptr)); + return ful_url_launcher_api_launch_url_response_new(error->message); } - g_autoptr(FlValue) result = fl_value_new_bool(TRUE); - return FL_METHOD_RESPONSE(fl_method_success_response_new(result)); -} - -// Called when a method call is received from Flutter. -static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, - gpointer user_data) { - FlUrlLauncherPlugin* self = FL_URL_LAUNCHER_PLUGIN(user_data); - - const gchar* method = fl_method_call_get_name(method_call); - FlValue* args = fl_method_call_get_args(method_call); - - g_autoptr(FlMethodResponse) response = nullptr; - if (strcmp(method, kCanLaunchMethod) == 0) - response = can_launch(self, args); - else if (strcmp(method, kLaunchMethod) == 0) - response = launch(self, args); - else - response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); - - g_autoptr(GError) error = nullptr; - if (!fl_method_call_respond(method_call, response, &error)) - g_warning("Failed to send method call response: %s", error->message); + return ful_url_launcher_api_launch_url_response_new(nullptr); } static void fl_url_launcher_plugin_dispose(GObject* object) { FlUrlLauncherPlugin* self = FL_URL_LAUNCHER_PLUGIN(object); + ful_url_launcher_api_clear_method_handlers( + fl_plugin_registrar_get_messenger(self->registrar), nullptr); g_clear_object(&self->registrar); - g_clear_object(&self->channel); G_OBJECT_CLASS(fl_url_launcher_plugin_parent_class)->dispose(object); } @@ -122,12 +90,13 @@ FlUrlLauncherPlugin* fl_url_launcher_plugin_new(FlPluginRegistrar* registrar) { self->registrar = FL_PLUGIN_REGISTRAR(g_object_ref(registrar)); - g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); - self->channel = - fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar), - kChannelName, FL_METHOD_CODEC(codec)); - fl_method_channel_set_method_call_handler(self->channel, method_call_cb, - g_object_ref(self), g_object_unref); + static FulUrlLauncherApiVTable api_vtable = { + .can_launch_url = handle_can_launch_url, + .launch_url = handle_launch_url, + }; + ful_url_launcher_api_set_method_handlers( + fl_plugin_registrar_get_messenger(registrar), nullptr, &api_vtable, + g_object_ref(self), g_object_unref); return self; } diff --git a/packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin_private.h b/packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin_private.h index cde5242a8f4..9284ea80962 100644 --- a/packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin_private.h +++ b/packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin_private.h @@ -5,10 +5,8 @@ #include #include "include/url_launcher_linux/url_launcher_plugin.h" +#include "messages.g.h" -// TODO(stuartmorgan): Remove this private header and change the below back to -// a static function once https://github.com/flutter/flutter/issues/88724 -// is fixed, and test through the public API instead. - -// Handles the canLaunch method call. -FlMethodResponse* can_launch(FlUrlLauncherPlugin* self, FlValue* args); +// Called to check if a URL can be launched. +FulUrlLauncherApiCanLaunchUrlResponse* handle_can_launch_url( + const gchar* url, gpointer user_data); diff --git a/packages/url_launcher/url_launcher_linux/pigeons/copyright.txt b/packages/url_launcher/url_launcher_linux/pigeons/copyright.txt new file mode 100644 index 00000000000..1236b63caf3 --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/pigeons/copyright.txt @@ -0,0 +1,3 @@ +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. diff --git a/packages/url_launcher/url_launcher_linux/pigeons/messages.dart b/packages/url_launcher/url_launcher_linux/pigeons/messages.dart new file mode 100644 index 00000000000..56d30d75833 --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/pigeons/messages.dart @@ -0,0 +1,21 @@ +// 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. + +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon(PigeonOptions( + dartOut: 'lib/src/messages.g.dart', + gobjectHeaderOut: 'linux/messages.g.h', + gobjectSourceOut: 'linux/messages.g.cc', + gobjectOptions: GObjectOptions(module: 'Ful'), + copyrightHeader: 'pigeons/copyright.txt', +)) +@HostApi() +abstract class UrlLauncherApi { + /// Returns true if the URL can definitely be launched. + bool canLaunchUrl(String url); + + /// Opens the URL externally, returning an error string on failure. + String? launchUrl(String url); +} diff --git a/packages/url_launcher/url_launcher_linux/pubspec.yaml b/packages/url_launcher/url_launcher_linux/pubspec.yaml index 0e3cededf6e..61ddf111c5b 100644 --- a/packages/url_launcher/url_launcher_linux/pubspec.yaml +++ b/packages/url_launcher/url_launcher_linux/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_linux description: Linux implementation of the url_launcher plugin. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_linux issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 3.1.1 +version: 3.2.0 environment: sdk: ^3.3.0 @@ -24,6 +24,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + pigeon: ^21.1.0 test: ^1.16.3 topics: diff --git a/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart b/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart index 7a16d9fc0ed..bf2a7019554 100644 --- a/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart +++ b/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart @@ -4,71 +4,42 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:url_launcher_linux/src/messages.g.dart'; import 'package:url_launcher_linux/url_launcher_linux.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - group('UrlLauncherLinux', () { - const MethodChannel channel = - MethodChannel('plugins.flutter.io/url_launcher_linux'); - final List log = []; - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(channel, (MethodCall methodCall) async { - log.add(methodCall); - - // Return null explicitly instead of relying on the implicit null - // returned by the method channel if no return statement is specified. - return null; - }); - - tearDown(() { - log.clear(); - }); - test('registers instance', () { UrlLauncherLinux.registerWith(); expect(UrlLauncherPlatform.instance, isA()); }); - test('canLaunch', () async { - final UrlLauncherLinux launcher = UrlLauncherLinux(); - await launcher.canLaunch('http://example.com/'); - expect( - log, - [isMethodCall('canLaunch', arguments: 'http://example.com/')], - ); + test('canLaunch passes true', () async { + final _FakeUrlLauncherApi api = _FakeUrlLauncherApi(); + final UrlLauncherLinux launcher = UrlLauncherLinux(api: api); + + final bool canLaunch = await launcher.canLaunch('http://example.com/'); + + expect(canLaunch, true); }); - test('canLaunch should return false if platform returns null', () async { - final UrlLauncherLinux launcher = UrlLauncherLinux(); + test('canLaunch passes false', () async { + final _FakeUrlLauncherApi api = _FakeUrlLauncherApi(canLaunch: false); + final UrlLauncherLinux launcher = UrlLauncherLinux(api: api); + final bool canLaunch = await launcher.canLaunch('http://example.com/'); expect(canLaunch, false); }); test('launch', () async { - final UrlLauncherLinux launcher = UrlLauncherLinux(); - await launcher.launch( - 'http://example.com/', - useSafariVC: true, - useWebView: false, - enableJavaScript: false, - enableDomStorage: false, - universalLinksOnly: false, - headers: const {}, - ); - expect( - log, - [isMethodCall('launch', arguments: 'http://example.com/')], - ); - }); + final _FakeUrlLauncherApi api = _FakeUrlLauncherApi(); + final UrlLauncherLinux launcher = UrlLauncherLinux(api: api); + const String url = 'http://example.com/'; - test('launch should return false if platform returns null', () async { - final UrlLauncherLinux launcher = UrlLauncherLinux(); final bool launched = await launcher.launch( - 'http://example.com/', + url, useSafariVC: true, useWebView: false, enableJavaScript: false, @@ -77,25 +48,53 @@ void main() { headers: const {}, ); - expect(launched, false); + expect(launched, true); + expect(api.argument, url); + }); + + test('launch should throw if platform returns an error', () async { + final _FakeUrlLauncherApi api = _FakeUrlLauncherApi(error: 'An error'); + final UrlLauncherLinux launcher = UrlLauncherLinux(api: api); + + await expectLater( + launcher.launch( + 'http://example.com/', + useSafariVC: true, + useWebView: false, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: const {}, + ), + throwsA(isA() + .having((PlatformException e) => e.code, 'code', 'Launch Error') + .having((PlatformException e) => e.message, 'message', + contains('Failed to launch URL: An error')))); }); group('launchUrl', () { test('passes URL', () async { - final UrlLauncherLinux launcher = UrlLauncherLinux(); - await launcher.launchUrl('http://example.com/', const LaunchOptions()); - expect( - log, - [isMethodCall('launch', arguments: 'http://example.com/')], - ); + final _FakeUrlLauncherApi api = _FakeUrlLauncherApi(); + final UrlLauncherLinux launcher = UrlLauncherLinux(api: api); + const String url = 'http://example.com/'; + + final bool launched = + await launcher.launchUrl(url, const LaunchOptions()); + + expect(launched, true); + expect(api.argument, url); }); - test('returns false if platform returns null', () async { - final UrlLauncherLinux launcher = UrlLauncherLinux(); - final bool launched = await launcher.launchUrl( - 'http://example.com/', const LaunchOptions()); + test('throws if platform returns an error', () async { + final _FakeUrlLauncherApi api = _FakeUrlLauncherApi(error: 'An error'); + final UrlLauncherLinux launcher = UrlLauncherLinux(api: api); - expect(launched, false); + await expectLater( + launcher.launchUrl('http://example.com/', const LaunchOptions()), + throwsA(isA() + .having((PlatformException e) => e.code, 'code', 'Launch Error') + .having((PlatformException e) => e.message, 'message', + contains('Failed to launch URL: An error')))); }); }); @@ -141,3 +140,28 @@ void main() { }); }); } + +class _FakeUrlLauncherApi implements UrlLauncherApi { + _FakeUrlLauncherApi({this.canLaunch = true, this.error}); + + /// The value to return from canLaunch. + final bool canLaunch; + + /// The error to return from launchUrl, if any. + final String? error; + + /// The argument that was passed to an API call. + String? argument; + + @override + Future canLaunchUrl(String url) async { + argument = url; + return canLaunch; + } + + @override + Future launchUrl(String url) async { + argument = url; + return error; + } +}