Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[url_launcher] Add an inAppBrowserView mode #5155

Merged
merged 21 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
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
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
18 changes: 15 additions & 3 deletions packages/url_launcher/url_launcher/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ See [`-[UIApplication canOpenURL:]`](https://developer.apple.com/documentation/u

Add any URL schemes passed to `canLaunchUrl` as `<queries>` entries in your
`AndroidManifest.xml`, otherwise it will return false in most cases starting
on Android 11 (API 30) or higher. A `<queries>`
on Android 11 (API 30) or higher. Checking for
`supportsLaunchMode(PreferredLaunchMode.inAppBrowserView)` also requires
a `<queries>` entry to return anything but false. A `<queries>`
element must be added to your manifest as a child of the root element.

Example:
Expand All @@ -85,6 +87,10 @@ Example:
<action android:name="android.intent.action.VIEW" />
<data android:scheme="tel" />
</intent>
<!-- If your application checks for inAppBrowserView launch mode support -->
<intent>
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
</queries>
```

Expand Down Expand Up @@ -210,10 +216,16 @@ if (!await launchUrl(uri)) {
If you need to access files outside of your application's sandbox, you will need to have the necessary
[entitlements](https://docs.flutter.dev/desktop#entitlements-and-the-app-sandbox).

## Browser vs in-app Handling
## Browser vs in-app handling

On some platforms, web URLs can be launched either in an in-app web view, or
in the default browser. The default behavior depends on the platform (see
[`launchUrl`](https://pub.dev/documentation/url_launcher/latest/url_launcher/launchUrl.html)
for details), but a specific mode can be used on supported platforms by
passing a `LaunchMode`.
passing a `PreferredLaunchMode`.

Platforms that do no support a requested `PreferredLaunchMode` will
automatically fall back to a supported mode (usually `platformDefault`). If
your application needs to avoid that fallback behavior, however, you can check
if the current platform supports a given mode with `supportsLaunchMode` before
calling `launchUrl`.
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
5 changes: 5 additions & 0 deletions packages/url_launcher/url_launcher/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,8 @@ dev_dependencies:

flutter:
uses-material-design: true

# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins
dependency_overrides:
{url_launcher_platform_interface: {path: ../../../url_launcher/url_launcher_platform_interface}}
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
Contributor 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
Contributor 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);
}
7 changes: 6 additions & 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 Expand Up @@ -50,3 +50,8 @@ topics:
- os-integration
- url-launcher
- urls

# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins
dependency_overrides:
{url_launcher_platform_interface: {path: ../../url_launcher/url_launcher_platform_interface}}
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);
});
});
}
7 changes: 7 additions & 0 deletions packages/url_launcher/url_launcher_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 6.2.0

* Adds support for `inAppBrowserView` as a separate launch mode option from
`inAppWebView` mode. `inAppBrowserView` is the preferred in-app mode for most uses,
but does not support `closeInAppWebView`.
* Implements `supportsMode` and `supportsCloseForMode`.

## 6.1.0

* Adds support for Android Custom Tabs.
Expand Down
Loading