Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
App-facing updates
  • Loading branch information
stuartmorgan-g committed Oct 12, 2023
commit 177207db1aee05800a109457e2d251758d693435
14 changes: 14 additions & 0 deletions packages/url_launcher/url_launcher/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
## 6.2.0

* Adds `supportsLaunchMode` for checking whether the current platform supports a
given launch mode, to allow clients that will only work with specific modes
to avoid fallback to a different mode.
* Adds `supportsCloseForLaunchMode` to allow checking programatically if a
launched URL will be able to be closed. Previously the documented behvaior was
that it worked only with the `inAppWebView` launch mode, but this is no longer
true on all platforms with the addition of `inAppBrowserView`.
* Updates the documention for `launchUrl` to clarify that clients should not
rely on any specific behavior of the `platformDefault` launch mode. Changes
to the handling of `platformDefault`, such as Android's recent change from
`inAppBrowserView` to the new `inAppBrowserView`, are not considered breaking.

## 6.1.14

* Updates documentation to mention support for Android Custom Tabs.
Expand Down
6 changes: 3 additions & 3 deletions packages/url_launcher/url_launcher/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,15 @@ class _MyHomePageState extends State<MyHomePage> {
}
}

Future<void> _launchUniversalLinkIos(Uri url) async {
Future<void> _launchUniversalLinkIOS(Uri url) async {
final bool nativeAppLaunchSucceeded = await launchUrl(
url,
mode: LaunchMode.externalNonBrowserApplication,
);
if (!nativeAppLaunchSucceeded) {
await launchUrl(
url,
mode: LaunchMode.inAppWebView,
mode: LaunchMode.inAppBrowserView,
);
}
}
Expand Down Expand Up @@ -198,7 +198,7 @@ class _MyHomePageState extends State<MyHomePage> {
const Padding(padding: EdgeInsets.all(16.0)),
ElevatedButton(
onPressed: () => setState(() {
_launched = _launchUniversalLinkIos(toLaunch);
_launched = _launchUniversalLinkIOS(toLaunch);
}),
child: const Text(
'Launch a universal link in a native app, fallback to Safari.(Youtube)'),
Expand Down
2 changes: 1 addition & 1 deletion packages/url_launcher/url_launcher/lib/src/link.dart
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ class DefaultLinkDelegate extends StatelessWidget {
success = await launchUrl(
url,
mode: _useWebView
? LaunchMode.inAppWebView
? LaunchMode.inAppBrowserView
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this since on Android we generally want to use Custom Tabs when possible, and it'll automatically fall back to webview when support isn't available.

: LaunchMode.externalApplication,
);
} on PlatformException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ PreferredLaunchMode convertLaunchMode(LaunchMode mode) {
switch (mode) {
case LaunchMode.platformDefault:
return PreferredLaunchMode.platformDefault;
case LaunchMode.inAppBrowserView:
return PreferredLaunchMode.inAppBrowserView;
case LaunchMode.inAppWebView:
return PreferredLaunchMode.inAppWebView;
case LaunchMode.externalApplication:
Expand Down
5 changes: 4 additions & 1 deletion packages/url_launcher/url_launcher/lib/src/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ enum LaunchMode {
/// implementation.
platformDefault,

/// Loads the URL in an in-app web view (e.g., Android Custom Tabs, Safari View Controller).
/// Loads the URL in an in-app web view (e.g., Android WebView).
inAppWebView,

/// Loads the URL in an in-app web view (e.g., Android Custom Tabs, SFSafariViewController).
inAppBrowserView,

/// Passes the URL to the OS to be handled by another application.
externalApplication,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ Future<bool> launchUrlString(
WebViewConfiguration webViewConfiguration = const WebViewConfiguration(),
String? webOnlyWindowName,
}) async {
if (mode == LaunchMode.inAppWebView &&
if ((mode == LaunchMode.inAppWebView ||
mode == LaunchMode.inAppBrowserView) &&
!(urlString.startsWith('https:') || urlString.startsWith('http:'))) {
throw ArgumentError.value(urlString, 'urlString',
'To use an in-app web view, you must provide an http(s) URL.');
Expand Down
51 changes: 29 additions & 22 deletions packages/url_launcher/url_launcher/lib/src/url_launcher_uri.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,13 @@ import 'type_conversion.dart';

/// Passes [url] to the underlying platform for handling.
///
/// [mode] support varies significantly by platform:
/// - [LaunchMode.platformDefault] is supported on all platforms:
/// - On iOS and Android, this treats web URLs as
/// [LaunchMode.inAppWebView], and all other URLs as
/// [LaunchMode.externalApplication].
/// - On Windows, macOS, and Linux this behaves like
/// [LaunchMode.externalApplication].
/// - On web, this uses `webOnlyWindowName` for web URLs, and behaves like
/// [LaunchMode.externalApplication] for any other content.
/// - [LaunchMode.inAppWebView] is currently only supported on iOS and
/// Android. If a non-web URL is passed with this mode, an [ArgumentError]
/// will be thrown.
/// - [LaunchMode.externalApplication] is supported on all platforms.
/// On iOS, this should be used in cases where sharing the cookies of the
/// user's browser is important, such as SSO flows, since Safari View
/// Controller does not share the browser's context.
/// - [LaunchMode.externalNonBrowserApplication] is supported on iOS 10+.
/// This setting is used to require universal links to open in a non-browser
/// application.
/// [mode] support varies significantly by platform. Clients can use
/// [supportsLaunchMode] to query for support, but platforms will fall back to
/// other modes if the requested mode is not supported, so checking is not
/// required. The default behavior of [LaunchMode.platformDefault] is up to each
/// platform, and its behavior for a given platform may change over time as new
/// modes are supported, so clients that want a specific mode should request it
/// rather than rely on any currently observed default behavior.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes the documentation federation-friendly and evergreen. If we find that people want docs about specific platform behaviors, we can add it to the platform package READMEs later.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per-platform docs are the way, agreed!

///
/// For web, [webOnlyWindowName] specifies a target for the launch. This
/// supports the standard special link target names. For example:
Expand All @@ -45,7 +33,8 @@ Future<bool> launchUrl(
WebViewConfiguration webViewConfiguration = const WebViewConfiguration(),
String? webOnlyWindowName,
}) async {
if (mode == LaunchMode.inAppWebView &&
if ((mode == LaunchMode.inAppWebView ||
mode == LaunchMode.inAppBrowserView) &&
!(url.scheme == 'https' || url.scheme == 'http')) {
throw ArgumentError.value(url, 'url',
'To use an in-app web view, you must provide an http(s) URL.');
Expand Down Expand Up @@ -81,8 +70,26 @@ Future<bool> canLaunchUrl(Uri url) async {
/// Closes the current in-app web view, if one was previously opened by
/// [launchUrl].
///
/// If [launchUrl] was never called with [LaunchMode.inAppWebView], then this
/// call will have no effect.
/// This works only if [supportsCloseForLaunchMode] returns true for the mode
/// that was used by [launchUrl].
Future<void> closeInAppWebView() async {
return UrlLauncherPlatform.instance.closeWebView();
}

/// Returns true if [mode] is supported by the current platform implementation.
///
/// Calling [launchUrl] with an unsupported mode will fall back to a supported
/// mode, so calling this method is only necessary for cases where the caller
/// needs to know which mode will be used.
Future<bool> supportsLaunchMode(PreferredLaunchMode mode) {
return UrlLauncherPlatform.instance.supportsMode(mode);
}

/// Returns true if [closeInAppWebView] is supported for [mode] in the current
/// platform implementation.
///
/// If this returns false, [closeInAppWebView] will not work when launching
/// URLs with [mode].
Future<bool> supportsCloseForLaunchMode(PreferredLaunchMode mode) {
return UrlLauncherPlatform.instance.supportsMode(mode);
}
2 changes: 1 addition & 1 deletion packages/url_launcher/url_launcher/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: Flutter plugin for launching a URL. Supports
web, phone, SMS, and email schemes.
repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22
version: 6.1.14
version: 6.2.0

environment:
sdk: ">=3.0.0 <4.0.0"
Expand Down
2 changes: 1 addition & 1 deletion packages/url_launcher/url_launcher/test/link_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ void main() {
mock
..setLaunchExpectations(
url: 'http://example.com/foobar',
launchMode: PreferredLaunchMode.inAppWebView,
launchMode: PreferredLaunchMode.inAppBrowserView,
universalLinksOnly: false,
enableJavaScript: true,
enableDomStorage: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,16 @@ class MockUrlLauncher extends Fake
Future<void> closeWebView() async {
closeWebViewCalled = true;
}

@override
Future<bool> supportsMode(PreferredLaunchMode mode) async {
launchMode = mode;
return response!;
}

@override
Future<bool> supportsCloseForMode(PreferredLaunchMode mode) async {
launchMode = mode;
return response!;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -248,4 +248,40 @@ void main() {
expect(await launchUrl(emailLaunchUrl), isTrue);
});
});

group('supportsLaunchMode', () {
test('handles returning true', () async {
const PreferredLaunchMode mode = PreferredLaunchMode.inAppBrowserView;
mock.setResponse(true);

expect(await supportsLaunchMode(mode), true);
expect(mock.launchMode, mode);
});

test('handles returning false', () async {
const PreferredLaunchMode mode = PreferredLaunchMode.inAppBrowserView;
mock.setResponse(false);

expect(await supportsLaunchMode(mode), false);
expect(mock.launchMode, mode);
});
});

group('supportsCloseForLaunchMode', () {
test('handles returning true', () async {
const PreferredLaunchMode mode = PreferredLaunchMode.inAppBrowserView;
mock.setResponse(true);

expect(await supportsCloseForLaunchMode(mode), true);
expect(mock.launchMode, mode);
});

test('handles returning false', () async {
const PreferredLaunchMode mode = PreferredLaunchMode.inAppBrowserView;
mock.setResponse(false);

expect(await supportsCloseForLaunchMode(mode), false);
expect(mock.launchMode, mode);
});
});
}