[webview_flutter] Default Android to hybrid composition (#4576)
Switches the default implemention on Android from virtual display to
hybrid composition. This is a breaking change.
Part of https://github.com/flutter/flutter/issues/93335
diff --git a/packages/webview_flutter/webview_flutter/CHANGELOG.md b/packages/webview_flutter/webview_flutter/CHANGELOG.md
index 4906003..aa117c9 100644
--- a/packages/webview_flutter/webview_flutter/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 3.0.0
+
+* **BREAKING CHANGE**: On Android, hybrid composition (SurfaceAndroidWebView)
+ is now the default. The previous default, virtual display, can be specified
+ with `WebView.platform = AndroidWebView()`
+
## 2.8.0
* Adds support for the `loadFlutterAsset` method.
diff --git a/packages/webview_flutter/webview_flutter/README.md b/packages/webview_flutter/webview_flutter/README.md
index c3982ea..8387eb6 100644
--- a/packages/webview_flutter/webview_flutter/README.md
+++ b/packages/webview_flutter/webview_flutter/README.md
@@ -15,68 +15,63 @@
widget's Dartdoc for more details on how to use the widget.
## Android Platform Views
-The WebView is relying on
+This plugin uses
[Platform Views](https://flutter.dev/docs/development/platform-integration/platform-views) to embed
-the Android’s webview within the Flutter app. It supports two modes: *Virtual displays* (the current default) and *Hybrid composition*.
+the Android’s webview within the Flutter app. It supports two modes:
+*hybrid composition* (the current default) and *virtual display*.
Here are some points to consider when choosing between the two:
-* *Hybrid composition* mode has a built-in keyboard support while *Virtual displays* mode has multiple
-[keyboard issues](https://github.com/flutter/flutter/issues?q=is%3Aopen+label%3Avd-only+label%3A%22p%3A+webview-keyboard%22)
-* *Hybrid composition* mode requires Android SDK 19+ while *Virtual displays* mode requires Android SDK 20+
-* *Hybrid composition* mode has [performance limitations](https://flutter.dev/docs/development/platform-integration/platform-views#performance) when working on Android versions prior to Android 10 while *Virtual displays* is performant on all supported Android versions
-
-| | Hybrid composition | Virtual displays |
-| --------------------------- | ------------------- | ---------------- |
-| **Full keyboard supoport** | yes | no |
-| **Android SDK support** | 19+ | 20+ |
-| **Full performance** | Android 10+ | always |
-| **The default** | no | yes |
-
-### Using Virtual displays
-
-The mode is currently enabled by default. You should however make sure to set the correct `minSdkVersion` in `android/app/build.gradle` (if it was previously lower than 20):
-
-```groovy
-android {
- defaultConfig {
- minSdkVersion 20
- }
-}
-```
+* *Hybrid composition* has built-in keyboard support while *virtual display* has multiple
+[keyboard issues](https://github.com/flutter/flutter/issues?q=is%3Aopen+label%3Avd-only+label%3A%22p%3A+webview-keyboard%22).
+* *Hybrid composition* requires Android SDK 19+ while *virtual display* requires Android SDK 20+.
+* *Hybrid composition* and *virtual display* have different
+ [performance tradeoffs](https://flutter.dev/docs/development/platform-integration/platform-views#performance).
### Using Hybrid Composition
-1. Set the correct `minSdkVersion` in `android/app/build.gradle` (if it was previously lower than 19):
+The mode is currently enabled by default. You should however make sure to set the correct `minSdkVersion` in `android/app/build.gradle` if it was previously lower than 19:
+
+```groovy
+android {
+ defaultConfig {
+ minSdkVersion 19
+ }
+}
+```
+
+### Using Virtual displays
+
+1. Set the correct `minSdkVersion` in `android/app/build.gradle` (if it was previously lower than 20):
```groovy
android {
defaultConfig {
- minSdkVersion 19
+ minSdkVersion 20
}
}
```
-2. Set `WebView.platform = SurfaceAndroidWebView();` in `initState()`.
+2. Set `WebView.platform = AndroidWebView();` in `initState()`.
For example:
-
+
```dart
import 'dart:io';
-
+
import 'package:webview_flutter/webview_flutter.dart';
class WebViewExample extends StatefulWidget {
@override
WebViewExampleState createState() => WebViewExampleState();
}
-
+
class WebViewExampleState extends State<WebViewExample> {
@override
void initState() {
super.initState();
- // Enable hybrid composition.
- if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
+ // Enable virtual display.
+ if (Platform.isAndroid) WebView.platform = AndroidWebView();
}
@override
diff --git a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart
index cc74afd..4c2d1fb 100644
--- a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart
+++ b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart
@@ -860,192 +860,41 @@
}, skip: Platform.isAndroid && _skipDueToIssue86757);
});
- group('SurfaceAndroidWebView', () {
+ // Minimial end-to-end testing of the legacy Android implementation.
+ group('AndroidWebView (virtual display)', () {
setUpAll(() {
- WebView.platform = SurfaceAndroidWebView();
+ WebView.platform = AndroidWebView();
});
tearDownAll(() {
WebView.platform = null;
});
- // TODO(bparrishMines): skipped due to https://github.com/flutter/flutter/issues/86757.
- testWidgets('setAndGetScrollPosition', (WidgetTester tester) async {
- const String scrollTestPage = '''
- <!DOCTYPE html>
- <html>
- <head>
- <style>
- body {
- height: 100%;
- width: 100%;
- }
- #container{
- width:5000px;
- height:5000px;
- }
- </style>
- </head>
- <body>
- <div id="container"/>
- </body>
- </html>
- ''';
-
- final String scrollTestPageBase64 =
- base64Encode(const Utf8Encoder().convert(scrollTestPage));
-
- final Completer<void> pageLoaded = Completer<void>();
+ testWidgets('initialUrl', (WidgetTester tester) async {
final Completer<WebViewController> controllerCompleter =
Completer<WebViewController>();
-
+ final Completer<void> loadCompleter = Completer<void>();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: WebView(
- initialUrl:
- 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64',
+ key: GlobalKey(),
+ initialUrl: primaryUrl,
onWebViewCreated: (WebViewController controller) {
controllerCompleter.complete(controller);
},
onPageFinished: (String url) {
- pageLoaded.complete(null);
+ loadCompleter.complete();
},
),
),
);
-
final WebViewController controller = await controllerCompleter.future;
- await pageLoaded.future;
-
- await tester.pumpAndSettle(const Duration(seconds: 3));
-
- // Check scrollTo()
- const int X_SCROLL = 123;
- const int Y_SCROLL = 321;
-
- await controller.scrollTo(X_SCROLL, Y_SCROLL);
- int scrollPosX = await controller.getScrollX();
- int scrollPosY = await controller.getScrollY();
- expect(X_SCROLL, scrollPosX);
- expect(Y_SCROLL, scrollPosY);
-
- // Check scrollBy() (on top of scrollTo())
- await controller.scrollBy(X_SCROLL, Y_SCROLL);
- scrollPosX = await controller.getScrollX();
- scrollPosY = await controller.getScrollY();
- expect(X_SCROLL * 2, scrollPosX);
- expect(Y_SCROLL * 2, scrollPosY);
- }, skip: !Platform.isAndroid || _skipDueToIssue86757);
-
- // TODO(bparrishMines): skipped due to https://github.com/flutter/flutter/issues/86757.
- testWidgets('inputs are scrolled into view when focused',
- (WidgetTester tester) async {
- const String scrollTestPage = '''
- <!DOCTYPE html>
- <html>
- <head>
- <style>
- input {
- margin: 10000px 0;
- }
- #viewport {
- position: fixed;
- top:0;
- bottom:0;
- left:0;
- right:0;
- }
- </style>
- </head>
- <body>
- <div id="viewport"></div>
- <input type="text" id="inputEl">
- </body>
- </html>
- ''';
-
- final String scrollTestPageBase64 =
- base64Encode(const Utf8Encoder().convert(scrollTestPage));
-
- final Completer<void> pageLoaded = Completer<void>();
- final Completer<WebViewController> controllerCompleter =
- Completer<WebViewController>();
-
- await tester.runAsync(() async {
- await tester.pumpWidget(
- Directionality(
- textDirection: TextDirection.ltr,
- child: SizedBox(
- width: 200,
- height: 200,
- child: WebView(
- initialUrl:
- 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64',
- onWebViewCreated: (WebViewController controller) {
- controllerCompleter.complete(controller);
- },
- onPageFinished: (String url) {
- pageLoaded.complete(null);
- },
- javascriptMode: JavascriptMode.unrestricted,
- ),
- ),
- ),
- );
- await Future<dynamic>.delayed(const Duration(milliseconds: 20));
- await tester.pump();
- });
-
- final WebViewController controller = await controllerCompleter.future;
- await pageLoaded.future;
- final String viewportRectJSON = await _runJavascriptReturningResult(
- controller, 'JSON.stringify(viewport.getBoundingClientRect())');
- final Map<String, dynamic> viewportRectRelativeToViewport =
- jsonDecode(viewportRectJSON) as Map<String, dynamic>;
-
- // Check that the input is originally outside of the viewport.
-
- final String initialInputClientRectJSON =
- await _runJavascriptReturningResult(
- controller, 'JSON.stringify(inputEl.getBoundingClientRect())');
- final Map<String, dynamic> initialInputClientRectRelativeToViewport =
- jsonDecode(initialInputClientRectJSON) as Map<String, dynamic>;
-
- expect(
- initialInputClientRectRelativeToViewport['bottom'] <=
- viewportRectRelativeToViewport['bottom'],
- isFalse);
-
- await controller.runJavascript('inputEl.focus()');
-
- // Check that focusing the input brought it into view.
-
- final String lastInputClientRectJSON =
- await _runJavascriptReturningResult(
- controller, 'JSON.stringify(inputEl.getBoundingClientRect())');
- final Map<String, dynamic> lastInputClientRectRelativeToViewport =
- jsonDecode(lastInputClientRectJSON) as Map<String, dynamic>;
-
- expect(
- lastInputClientRectRelativeToViewport['top'] >=
- viewportRectRelativeToViewport['top'],
- isTrue);
- expect(
- lastInputClientRectRelativeToViewport['bottom'] <=
- viewportRectRelativeToViewport['bottom'],
- isTrue);
-
- expect(
- lastInputClientRectRelativeToViewport['left'] >=
- viewportRectRelativeToViewport['left'],
- isTrue);
- expect(
- lastInputClientRectRelativeToViewport['right'] <=
- viewportRectRelativeToViewport['right'],
- isTrue);
- }, skip: !Platform.isAndroid || _skipDueToIssue86757);
- });
+ await loadCompleter.future;
+ final String? currentUrl = await controller.currentUrl();
+ expect(currentUrl, primaryUrl);
+ });
+ }, skip: !Platform.isAndroid || _skipDueToIssue86757);
group('NavigationDelegate', () {
const String blankPage = '<!DOCTYPE html><head></head><body></body></html>';
diff --git a/packages/webview_flutter/webview_flutter/lib/src/webview.dart b/packages/webview_flutter/webview_flutter/lib/src/webview.dart
index 7b907a5..a177027 100644
--- a/packages/webview_flutter/webview_flutter/lib/src/webview.dart
+++ b/packages/webview_flutter/webview_flutter/lib/src/webview.dart
@@ -9,8 +9,8 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
-import 'package:webview_flutter_android/webview_android.dart';
import 'package:webview_flutter_android/webview_android_cookie_manager.dart';
+import 'package:webview_flutter_android/webview_surface_android.dart';
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
@@ -119,12 +119,12 @@
/// The WebView platform that's used by this WebView.
///
- /// The default value is [AndroidWebView] on Android and [CupertinoWebView] on iOS.
+ /// The default value is [SurfaceAndroidWebView] on Android and [CupertinoWebView] on iOS.
static WebViewPlatform get platform {
if (_platform == null) {
switch (defaultTargetPlatform) {
case TargetPlatform.android:
- _platform = AndroidWebView();
+ _platform = SurfaceAndroidWebView();
break;
case TargetPlatform.iOS:
_platform = CupertinoWebView();
diff --git a/packages/webview_flutter/webview_flutter/pubspec.yaml b/packages/webview_flutter/webview_flutter/pubspec.yaml
index 4ac5e1d..4af4462 100644
--- a/packages/webview_flutter/webview_flutter/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter/pubspec.yaml
@@ -2,7 +2,7 @@
description: A Flutter plugin that provides a WebView widget on Android and iOS.
repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
-version: 2.8.0
+version: 3.0.0
environment:
sdk: ">=2.14.0 <3.0.0"