Skip to content

Commit a0ed435

Browse files
[url_launcher] Add an inAppBrowserView mode in implementations (#5211)
Implementation package portion of #5155 This adds: - Android support for the new `inAppBrowserView` launch mode which is distinct from `inAppWebView`, so that use cases that require programatic close can specifically request `inAppWebView` instead. - The default for web links is the new `inAppBrowserView` since that gives better results in most cases. - `inAppBrowserView` will still automatically fall back to `inAppBrowserView` in cases where it's not supported. (In the future, we might want to tune that based on feedback. We could instead have three modes: the webview-only mode we now have, the dynamic mode we now have iff the user requested `platformDefault`, and a new Android Custom Tabs-only if it was explicitly requested which would fail if it doesn't work.) - iOS support for treating `inAppBrowserView` as identical to `inAppWebView`, since in practice that's what its `inAppWebView` mode has always been. - Support on all platforms for the new `supportsMode` and `supportsCloseForMode` support query methods. Fixes flutter/flutter#134208
1 parent e34ad30 commit a0ed435

File tree

36 files changed

+987
-157
lines changed

36 files changed

+987
-157
lines changed

packages/url_launcher/url_launcher_android/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 6.2.0
2+
3+
* Adds support for `inAppBrowserView` as a separate launch mode option from
4+
`inAppWebView` mode. `inAppBrowserView` is the preferred in-app mode for most uses,
5+
but does not support `closeInAppWebView`.
6+
* Implements `supportsMode` and `supportsCloseForMode`.
7+
18
## 6.1.1
29

310
* Updates annotations lib to 1.7.0.

packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/Messages.java

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright 2013 The Flutter Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
4-
// Autogenerated from Pigeon (v10.0.0), do not edit directly.
4+
// Autogenerated from Pigeon (v10.1.4), do not edit directly.
55
// See also: https://pub.dev/packages/pigeon
66

77
package io.flutter.plugins.urllauncher;
@@ -190,9 +190,15 @@ public interface UrlLauncherApi {
190190
/** Opens the URL externally, returning true if successful. */
191191
@NonNull
192192
Boolean launchUrl(@NonNull String url, @NonNull Map<String, String> headers);
193-
/** Opens the URL in an in-app WebView, returning true if it opens successfully. */
193+
/**
194+
* Opens the URL in an in-app Custom Tab or WebView, returning true if it opens successfully.
195+
*/
194196
@NonNull
195-
Boolean openUrlInWebView(@NonNull String url, @NonNull WebViewOptions options);
197+
Boolean openUrlInApp(
198+
@NonNull String url, @NonNull Boolean allowCustomTab, @NonNull WebViewOptions options);
199+
200+
@NonNull
201+
Boolean supportsCustomTabs();
196202
/** Closes the view opened by [openUrlInSafariViewController]. */
197203
void closeWebView();
198204

@@ -205,7 +211,9 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche
205211
{
206212
BasicMessageChannel<Object> channel =
207213
new BasicMessageChannel<>(
208-
binaryMessenger, "dev.flutter.pigeon.UrlLauncherApi.canLaunchUrl", getCodec());
214+
binaryMessenger,
215+
"dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.canLaunchUrl",
216+
getCodec());
209217
if (api != null) {
210218
channel.setMessageHandler(
211219
(message, reply) -> {
@@ -228,7 +236,9 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche
228236
{
229237
BasicMessageChannel<Object> channel =
230238
new BasicMessageChannel<>(
231-
binaryMessenger, "dev.flutter.pigeon.UrlLauncherApi.launchUrl", getCodec());
239+
binaryMessenger,
240+
"dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.launchUrl",
241+
getCodec());
232242
if (api != null) {
233243
channel.setMessageHandler(
234244
(message, reply) -> {
@@ -252,16 +262,42 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche
252262
{
253263
BasicMessageChannel<Object> channel =
254264
new BasicMessageChannel<>(
255-
binaryMessenger, "dev.flutter.pigeon.UrlLauncherApi.openUrlInWebView", getCodec());
265+
binaryMessenger,
266+
"dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.openUrlInApp",
267+
getCodec());
256268
if (api != null) {
257269
channel.setMessageHandler(
258270
(message, reply) -> {
259271
ArrayList<Object> wrapped = new ArrayList<Object>();
260272
ArrayList<Object> args = (ArrayList<Object>) message;
261273
String urlArg = (String) args.get(0);
262-
WebViewOptions optionsArg = (WebViewOptions) args.get(1);
274+
Boolean allowCustomTabArg = (Boolean) args.get(1);
275+
WebViewOptions optionsArg = (WebViewOptions) args.get(2);
276+
try {
277+
Boolean output = api.openUrlInApp(urlArg, allowCustomTabArg, optionsArg);
278+
wrapped.add(0, output);
279+
} catch (Throwable exception) {
280+
ArrayList<Object> wrappedError = wrapError(exception);
281+
wrapped = wrappedError;
282+
}
283+
reply.reply(wrapped);
284+
});
285+
} else {
286+
channel.setMessageHandler(null);
287+
}
288+
}
289+
{
290+
BasicMessageChannel<Object> channel =
291+
new BasicMessageChannel<>(
292+
binaryMessenger,
293+
"dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.supportsCustomTabs",
294+
getCodec());
295+
if (api != null) {
296+
channel.setMessageHandler(
297+
(message, reply) -> {
298+
ArrayList<Object> wrapped = new ArrayList<Object>();
263299
try {
264-
Boolean output = api.openUrlInWebView(urlArg, optionsArg);
300+
Boolean output = api.supportsCustomTabs();
265301
wrapped.add(0, output);
266302
} catch (Throwable exception) {
267303
ArrayList<Object> wrappedError = wrapError(exception);
@@ -276,7 +312,9 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche
276312
{
277313
BasicMessageChannel<Object> channel =
278314
new BasicMessageChannel<>(
279-
binaryMessenger, "dev.flutter.pigeon.UrlLauncherApi.closeWebView", getCodec());
315+
binaryMessenger,
316+
"dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.closeWebView",
317+
getCodec());
280318
if (api != null) {
281319
channel.setMessageHandler(
282320
(message, reply) -> {

packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncher.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
import androidx.annotation.NonNull;
1717
import androidx.annotation.Nullable;
1818
import androidx.annotation.VisibleForTesting;
19+
import androidx.browser.customtabs.CustomTabsClient;
1920
import androidx.browser.customtabs.CustomTabsIntent;
2021
import io.flutter.plugins.urllauncher.Messages.UrlLauncherApi;
2122
import io.flutter.plugins.urllauncher.Messages.WebViewOptions;
23+
import java.util.Collections;
2224
import java.util.Locale;
2325
import java.util.Map;
2426

@@ -95,14 +97,16 @@ void setActivity(@Nullable Activity activity) {
9597
}
9698

9799
@Override
98-
public @NonNull Boolean openUrlInWebView(@NonNull String url, @NonNull WebViewOptions options) {
100+
public @NonNull Boolean openUrlInApp(
101+
@NonNull String url, @NonNull Boolean allowCustomTab, @NonNull WebViewOptions options) {
99102
ensureActivity();
100103
assert activity != null;
101104

102105
Bundle headersBundle = extractBundle(options.getHeaders());
103106

104-
// Try to launch using Custom Tabs if they have the necessary functionality.
105-
if (!containsRestrictedHeader(options.getHeaders())) {
107+
// Try to launch using Custom Tabs if they have the necessary functionality, unless the caller
108+
// specifically requested a web view.
109+
if (allowCustomTab && !containsRestrictedHeader(options.getHeaders())) {
106110
Uri uri = Uri.parse(url);
107111
if (openCustomTab(activity, uri, headersBundle)) {
108112
return true;
@@ -131,6 +135,11 @@ public void closeWebView() {
131135
applicationContext.sendBroadcast(new Intent(WebViewActivity.ACTION_CLOSE));
132136
}
133137

138+
@Override
139+
public @NonNull Boolean supportsCustomTabs() {
140+
return CustomTabsClient.getPackageName(applicationContext, Collections.emptyList()) != null;
141+
}
142+
134143
private static boolean openCustomTab(
135144
@NonNull Context context, @NonNull Uri uri, @NonNull Bundle headersBundle) {
136145
CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder().build();

packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/UrlLauncherTest.java

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ public void launch_returnsTrue() {
130130
}
131131

132132
@Test
133-
public void openWebView_opensUrl_inWebView() {
133+
public void openUrlInApp_opensUrlInWebViewIfNecessary() {
134134
Activity activity = mock(Activity.class);
135135
UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext());
136136
api.setActivity(activity);
@@ -141,8 +141,9 @@ public void openWebView_opensUrl_inWebView() {
141141
headers.put("key", "value");
142142

143143
boolean result =
144-
api.openUrlInWebView(
144+
api.openUrlInApp(
145145
url,
146+
true,
146147
new Messages.WebViewOptions.Builder()
147148
.setEnableJavaScript(enableJavaScript)
148149
.setEnableDomStorage(enableDomStorage)
@@ -162,15 +163,39 @@ public void openWebView_opensUrl_inWebView() {
162163
}
163164

164165
@Test
165-
public void openWebView_opensUrl_inCustomTabs() {
166+
public void openWebView_opensUrlInWebViewIfRequested() {
166167
Activity activity = mock(Activity.class);
167168
UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext());
168169
api.setActivity(activity);
169170
String url = "https://flutter.dev";
170171

171172
boolean result =
172-
api.openUrlInWebView(
173+
api.openUrlInApp(
173174
url,
175+
false,
176+
new Messages.WebViewOptions.Builder()
177+
.setEnableJavaScript(false)
178+
.setEnableDomStorage(false)
179+
.setHeaders(new HashMap<>())
180+
.build());
181+
182+
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
183+
verify(activity).startActivity(intentCaptor.capture());
184+
assertTrue(result);
185+
assertEquals(url, intentCaptor.getValue().getExtras().getString(WebViewActivity.URL_EXTRA));
186+
}
187+
188+
@Test
189+
public void openWebView_opensUrlInCustomTabs() {
190+
Activity activity = mock(Activity.class);
191+
UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext());
192+
api.setActivity(activity);
193+
String url = "https://flutter.dev";
194+
195+
boolean result =
196+
api.openUrlInApp(
197+
url,
198+
true,
174199
new Messages.WebViewOptions.Builder()
175200
.setEnableJavaScript(false)
176201
.setEnableDomStorage(false)
@@ -185,7 +210,7 @@ public void openWebView_opensUrl_inCustomTabs() {
185210
}
186211

187212
@Test
188-
public void openWebView_opensUrl_inCustomTabs_withCORSAllowedHeader() {
213+
public void openWebView_opensUrlInCustomTabsWithCORSAllowedHeader() {
189214
Activity activity = mock(Activity.class);
190215
UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext());
191216
api.setActivity(activity);
@@ -195,8 +220,9 @@ public void openWebView_opensUrl_inCustomTabs_withCORSAllowedHeader() {
195220
headers.put(headerKey, "text/plain");
196221

197222
boolean result =
198-
api.openUrlInWebView(
223+
api.openUrlInApp(
199224
url,
225+
true,
200226
new Messages.WebViewOptions.Builder()
201227
.setEnableJavaScript(false)
202228
.setEnableDomStorage(false)
@@ -214,7 +240,7 @@ public void openWebView_opensUrl_inCustomTabs_withCORSAllowedHeader() {
214240
}
215241

216242
@Test
217-
public void openWebView_fallsbackTo_inWebView() {
243+
public void openWebView_fallsBackToWebViewIfCustomTabFails() {
218244
Activity activity = mock(Activity.class);
219245
UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext());
220246
api.setActivity(activity);
@@ -224,8 +250,9 @@ public void openWebView_fallsbackTo_inWebView() {
224250
.startActivity(any(), isNull()); // for custom tabs intent
225251

226252
boolean result =
227-
api.openUrlInWebView(
253+
api.openUrlInApp(
228254
url,
255+
true,
229256
new Messages.WebViewOptions.Builder()
230257
.setEnableJavaScript(false)
231258
.setEnableDomStorage(false)
@@ -251,8 +278,9 @@ public void openWebView_handlesEnableJavaScript() {
251278
HashMap<String, String> headers = new HashMap<>();
252279
headers.put("key", "value");
253280

254-
api.openUrlInWebView(
281+
api.openUrlInApp(
255282
"https://flutter.dev",
283+
true,
256284
new Messages.WebViewOptions.Builder()
257285
.setEnableJavaScript(enableJavaScript)
258286
.setEnableDomStorage(false)
@@ -277,8 +305,9 @@ public void openWebView_handlesHeaders() {
277305
headers.put(key1, "value");
278306
headers.put(key2, "value2");
279307

280-
api.openUrlInWebView(
308+
api.openUrlInApp(
281309
"https://flutter.dev",
310+
true,
282311
new Messages.WebViewOptions.Builder()
283312
.setEnableJavaScript(false)
284313
.setEnableDomStorage(false)
@@ -303,8 +332,9 @@ public void openWebView_handlesEnableDomStorage() {
303332
HashMap<String, String> headers = new HashMap<>();
304333
headers.put("key", "value");
305334

306-
api.openUrlInWebView(
335+
api.openUrlInApp(
307336
"https://flutter.dev",
337+
true,
308338
new Messages.WebViewOptions.Builder()
309339
.setEnableJavaScript(false)
310340
.setEnableDomStorage(enableDomStorage)
@@ -327,8 +357,9 @@ public void openWebView_throwsForNoCurrentActivity() {
327357
assertThrows(
328358
Messages.FlutterError.class,
329359
() ->
330-
api.openUrlInWebView(
360+
api.openUrlInApp(
331361
"https://flutter.dev",
362+
true,
332363
new Messages.WebViewOptions.Builder()
333364
.setEnableJavaScript(false)
334365
.setEnableDomStorage(false)
@@ -350,8 +381,9 @@ public void openWebView_returnsFalse() {
350381
.startActivity(any()); // for webview intent
351382

352383
boolean result =
353-
api.openUrlInWebView(
384+
api.openUrlInApp(
354385
"https://flutter.dev",
386+
true,
355387
new Messages.WebViewOptions.Builder()
356388
.setEnableJavaScript(false)
357389
.setEnableDomStorage(false)

0 commit comments

Comments
 (0)