Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit c3955d2

Browse files
[url_launcher] Add a new launchUrl to platform interface (#5966)
This creates a new platform interface method for launching that closely parallels the new public-facing API, so that implementations can switch to implementing a more platform-neutral implementation. This will pave the way for things like cleanly implementing `externalNonBrowserApplication` support on non-iOS platforms. A follow-up will switch the app-facing package to call this new methods instead of the legacy method. Implementation packages can adopt the new method as is useful for them; eventually we can do a cleanup pass if we want to fully deprecate the old method.
1 parent c7aa994 commit c3955d2

File tree

6 files changed

+291
-67
lines changed

6 files changed

+291
-67
lines changed

packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.1.0
2+
3+
* Adds a new `launchUrl` method corresponding to the new app-facing interface.
4+
15
## 2.0.5
26

37
* Updates code for new analysis options.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/foundation.dart';
6+
7+
/// The desired mode to launch a URL.
8+
///
9+
/// Support for these modes varies by platform. Platforms that do not support
10+
/// the requested mode may substitute another mode.
11+
enum PreferredLaunchMode {
12+
/// Leaves the decision of how to launch the URL to the platform
13+
/// implementation.
14+
platformDefault,
15+
16+
/// Loads the URL in an in-app web view (e.g., Safari View Controller).
17+
inAppWebView,
18+
19+
/// Passes the URL to the OS to be handled by another application.
20+
externalApplication,
21+
22+
/// Passes the URL to the OS to be handled by another non-browser application.
23+
externalNonBrowserApplication,
24+
}
25+
26+
/// Additional configuration options for [PreferredLaunchMode.inAppWebView].
27+
///
28+
/// Not all options are supported on all platforms. This is a superset of
29+
/// available options exposed across all implementations.
30+
@immutable
31+
class InAppWebViewConfiguration {
32+
/// Creates a new WebViewConfiguration with the given settings.
33+
const InAppWebViewConfiguration({
34+
this.enableJavaScript = true,
35+
this.enableDomStorage = true,
36+
this.headers = const <String, String>{},
37+
});
38+
39+
/// Whether or not JavaScript is enabled for the web content.
40+
final bool enableJavaScript;
41+
42+
/// Whether or not DOM storage is enabled for the web content.
43+
final bool enableDomStorage;
44+
45+
/// Additional headers to pass in the load request.
46+
final Map<String, String> headers;
47+
}
48+
49+
/// Options for [launchUrl].
50+
@immutable
51+
class LaunchOptions {
52+
/// Creates a new parameter object with the given options.
53+
const LaunchOptions({
54+
this.mode = PreferredLaunchMode.platformDefault,
55+
this.webViewConfiguration = const InAppWebViewConfiguration(),
56+
this.webOnlyWindowName,
57+
});
58+
59+
/// The requested launch mode.
60+
final PreferredLaunchMode mode;
61+
62+
/// Configuration for the web view in [PreferredLaunchMode.inAppWebView] mode.
63+
final InAppWebViewConfiguration webViewConfiguration;
64+
65+
/// A web-platform-specific option to set the link target.
66+
///
67+
/// Default behaviour when unset should be to open the url in a new tab.
68+
final String? webOnlyWindowName;
69+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async';
6+
7+
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
8+
import 'package:url_launcher_platform_interface/link.dart';
9+
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
10+
11+
import '../method_channel_url_launcher.dart';
12+
13+
/// The interface that implementations of url_launcher must implement.
14+
///
15+
/// Platform implementations should extend this class rather than implement it as `url_launcher`
16+
/// does not consider newly added methods to be breaking changes. Extending this class
17+
/// (using `extends`) ensures that the subclass will get the default implementation, while
18+
/// platform implementations that `implements` this interface will be broken by newly added
19+
/// [UrlLauncherPlatform] methods.
20+
abstract class UrlLauncherPlatform extends PlatformInterface {
21+
/// Constructs a UrlLauncherPlatform.
22+
UrlLauncherPlatform() : super(token: _token);
23+
24+
static final Object _token = Object();
25+
26+
static UrlLauncherPlatform _instance = MethodChannelUrlLauncher();
27+
28+
/// The default instance of [UrlLauncherPlatform] to use.
29+
///
30+
/// Defaults to [MethodChannelUrlLauncher].
31+
static UrlLauncherPlatform get instance => _instance;
32+
33+
/// Platform-specific plugins should set this with their own platform-specific
34+
/// class that extends [UrlLauncherPlatform] when they register themselves.
35+
// TODO(amirh): Extract common platform interface logic.
36+
// https://github.com/flutter/flutter/issues/43368
37+
static set instance(UrlLauncherPlatform instance) {
38+
PlatformInterface.verify(instance, _token);
39+
_instance = instance;
40+
}
41+
42+
/// The delegate used by the Link widget to build itself.
43+
LinkDelegate? get linkDelegate;
44+
45+
/// Returns `true` if this platform is able to launch [url].
46+
Future<bool> canLaunch(String url) {
47+
throw UnimplementedError('canLaunch() has not been implemented.');
48+
}
49+
50+
/// Passes [url] to the underlying platform for handling.
51+
///
52+
/// Returns `true` if the given [url] was successfully launched.
53+
///
54+
/// For documentation on the other arguments, see the `launch` documentation
55+
/// in `package:url_launcher/url_launcher.dart`.
56+
Future<bool> launch(
57+
String url, {
58+
required bool useSafariVC,
59+
required bool useWebView,
60+
required bool enableJavaScript,
61+
required bool enableDomStorage,
62+
required bool universalLinksOnly,
63+
required Map<String, String> headers,
64+
String? webOnlyWindowName,
65+
}) {
66+
throw UnimplementedError('launch() has not been implemented.');
67+
}
68+
69+
/// Passes [url] to the underlying platform for handling.
70+
///
71+
/// Returns `true` if the given [url] was successfully launched.
72+
Future<bool> launchUrl(String url, LaunchOptions options) {
73+
final bool isWebURL = url.startsWith('http:') || url.startsWith('https:');
74+
final bool useWebView = options.mode == PreferredLaunchMode.inAppWebView ||
75+
(isWebURL && options.mode == PreferredLaunchMode.platformDefault);
76+
77+
return launch(
78+
url,
79+
useSafariVC: useWebView,
80+
useWebView: useWebView,
81+
enableJavaScript: options.webViewConfiguration.enableJavaScript,
82+
enableDomStorage: options.webViewConfiguration.enableDomStorage,
83+
universalLinksOnly:
84+
options.mode == PreferredLaunchMode.externalNonBrowserApplication,
85+
headers: options.webViewConfiguration.headers,
86+
webOnlyWindowName: options.webOnlyWindowName,
87+
);
88+
}
89+
90+
/// Closes the WebView, if one was opened earlier by [launch].
91+
Future<void> closeWebView() {
92+
throw UnimplementedError('closeWebView() has not been implemented.');
93+
}
94+
}

packages/url_launcher/url_launcher_platform_interface/lib/url_launcher_platform_interface.dart

Lines changed: 2 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2,69 +2,5 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
import 'dart:async';
6-
7-
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
8-
import 'package:url_launcher_platform_interface/link.dart';
9-
10-
import 'method_channel_url_launcher.dart';
11-
12-
/// The interface that implementations of url_launcher must implement.
13-
///
14-
/// Platform implementations should extend this class rather than implement it as `url_launcher`
15-
/// does not consider newly added methods to be breaking changes. Extending this class
16-
/// (using `extends`) ensures that the subclass will get the default implementation, while
17-
/// platform implementations that `implements` this interface will be broken by newly added
18-
/// [UrlLauncherPlatform] methods.
19-
abstract class UrlLauncherPlatform extends PlatformInterface {
20-
/// Constructs a UrlLauncherPlatform.
21-
UrlLauncherPlatform() : super(token: _token);
22-
23-
static final Object _token = Object();
24-
25-
static UrlLauncherPlatform _instance = MethodChannelUrlLauncher();
26-
27-
/// The default instance of [UrlLauncherPlatform] to use.
28-
///
29-
/// Defaults to [MethodChannelUrlLauncher].
30-
static UrlLauncherPlatform get instance => _instance;
31-
32-
/// Platform-specific plugins should set this with their own platform-specific
33-
/// class that extends [UrlLauncherPlatform] when they register themselves.
34-
// TODO(amirh): Extract common platform interface logic.
35-
// https://github.com/flutter/flutter/issues/43368
36-
static set instance(UrlLauncherPlatform instance) {
37-
PlatformInterface.verify(instance, _token);
38-
_instance = instance;
39-
}
40-
41-
/// The delegate used by the Link widget to build itself.
42-
LinkDelegate? get linkDelegate;
43-
44-
/// Returns `true` if this platform is able to launch [url].
45-
Future<bool> canLaunch(String url) {
46-
throw UnimplementedError('canLaunch() has not been implemented.');
47-
}
48-
49-
/// Returns `true` if the given [url] was successfully launched.
50-
///
51-
/// For documentation on the other arguments, see the `launch` documentation
52-
/// in `package:url_launcher/url_launcher.dart`.
53-
Future<bool> launch(
54-
String url, {
55-
required bool useSafariVC,
56-
required bool useWebView,
57-
required bool enableJavaScript,
58-
required bool enableDomStorage,
59-
required bool universalLinksOnly,
60-
required Map<String, String> headers,
61-
String? webOnlyWindowName,
62-
}) {
63-
throw UnimplementedError('launch() has not been implemented.');
64-
}
65-
66-
/// Closes the WebView, if one was opened earlier by [launch].
67-
Future<void> closeWebView() {
68-
throw UnimplementedError('closeWebView() has not been implemented.');
69-
}
70-
}
5+
export 'src/types.dart';
6+
export 'src/url_launcher_platform.dart';

packages/url_launcher/url_launcher_platform_interface/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/u
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22
55
# NOTE: We strongly prefer non-breaking changes, even at the expense of a
66
# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
7-
version: 2.0.5
7+
version: 2.1.0
88

99
environment:
1010
sdk: ">=2.12.0 <3.0.0"
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter_test/flutter_test.dart';
6+
import 'package:url_launcher_platform_interface/link.dart';
7+
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
8+
9+
class CapturingUrlLauncher extends UrlLauncherPlatform {
10+
String? url;
11+
bool? useSafariVC;
12+
bool? useWebView;
13+
bool? enableJavaScript;
14+
bool? enableDomStorage;
15+
bool? universalLinksOnly;
16+
Map<String, String> headers = <String, String>{};
17+
String? webOnlyWindowName;
18+
19+
@override
20+
final LinkDelegate? linkDelegate = null;
21+
22+
@override
23+
Future<bool> launch(
24+
String url, {
25+
required bool useSafariVC,
26+
required bool useWebView,
27+
required bool enableJavaScript,
28+
required bool enableDomStorage,
29+
required bool universalLinksOnly,
30+
required Map<String, String> headers,
31+
String? webOnlyWindowName,
32+
}) async {
33+
this.url = url;
34+
this.useSafariVC = useSafariVC;
35+
this.useWebView = useWebView;
36+
this.enableJavaScript = enableJavaScript;
37+
this.enableDomStorage = enableDomStorage;
38+
this.universalLinksOnly = universalLinksOnly;
39+
this.headers = headers;
40+
this.webOnlyWindowName = webOnlyWindowName;
41+
42+
return true;
43+
}
44+
}
45+
46+
void main() {
47+
test('launchUrl calls through to launch with default options for web URL',
48+
() async {
49+
final CapturingUrlLauncher launcher = CapturingUrlLauncher();
50+
51+
await launcher.launchUrl('https://flutter.dev', const LaunchOptions());
52+
53+
expect(launcher.url, 'https://flutter.dev');
54+
expect(launcher.useSafariVC, true);
55+
expect(launcher.useWebView, true);
56+
expect(launcher.enableJavaScript, true);
57+
expect(launcher.enableDomStorage, true);
58+
expect(launcher.universalLinksOnly, false);
59+
expect(launcher.headers, isEmpty);
60+
expect(launcher.webOnlyWindowName, null);
61+
});
62+
63+
test('launchUrl calls through to launch with default options for non-web URL',
64+
() async {
65+
final CapturingUrlLauncher launcher = CapturingUrlLauncher();
66+
67+
await launcher.launchUrl('tel:123456789', const LaunchOptions());
68+
69+
expect(launcher.url, 'tel:123456789');
70+
expect(launcher.useSafariVC, false);
71+
expect(launcher.useWebView, false);
72+
expect(launcher.enableJavaScript, true);
73+
expect(launcher.enableDomStorage, true);
74+
expect(launcher.universalLinksOnly, false);
75+
expect(launcher.headers, isEmpty);
76+
expect(launcher.webOnlyWindowName, null);
77+
});
78+
79+
test('launchUrl calls through to launch with universal links', () async {
80+
final CapturingUrlLauncher launcher = CapturingUrlLauncher();
81+
82+
await launcher.launchUrl(
83+
'https://flutter.dev',
84+
const LaunchOptions(
85+
mode: PreferredLaunchMode.externalNonBrowserApplication));
86+
87+
expect(launcher.url, 'https://flutter.dev');
88+
expect(launcher.useSafariVC, false);
89+
expect(launcher.useWebView, false);
90+
expect(launcher.enableJavaScript, true);
91+
expect(launcher.enableDomStorage, true);
92+
expect(launcher.universalLinksOnly, true);
93+
expect(launcher.headers, isEmpty);
94+
expect(launcher.webOnlyWindowName, null);
95+
});
96+
97+
test('launchUrl calls through to launch with all non-default options',
98+
() async {
99+
final CapturingUrlLauncher launcher = CapturingUrlLauncher();
100+
101+
await launcher.launchUrl(
102+
'https://flutter.dev',
103+
const LaunchOptions(
104+
mode: PreferredLaunchMode.externalApplication,
105+
webViewConfiguration: InAppWebViewConfiguration(
106+
enableJavaScript: false,
107+
enableDomStorage: false,
108+
headers: <String, String>{'foo': 'bar'}),
109+
webOnlyWindowName: 'a_name',
110+
));
111+
112+
expect(launcher.url, 'https://flutter.dev');
113+
expect(launcher.useSafariVC, false);
114+
expect(launcher.useWebView, false);
115+
expect(launcher.enableJavaScript, false);
116+
expect(launcher.enableDomStorage, false);
117+
expect(launcher.universalLinksOnly, false);
118+
expect(launcher.headers['foo'], 'bar');
119+
expect(launcher.webOnlyWindowName, 'a_name');
120+
});
121+
}

0 commit comments

Comments
 (0)