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

Commit d7f7b89

Browse files
[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 flutter/flutter#93335
1 parent 8496432 commit d7f7b89

5 files changed

Lines changed: 43 additions & 193 deletions

File tree

packages/webview_flutter/webview_flutter/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 3.0.0
2+
3+
* **BREAKING CHANGE**: On Android, hybrid composition (SurfaceAndroidWebView)
4+
is now the default. The previous default, virtual display, can be specified
5+
with `WebView.platform = AndroidWebView()`
6+
17
## 2.8.0
28

39
* Adds support for the `loadFlutterAsset` method.

packages/webview_flutter/webview_flutter/README.md

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,68 +15,63 @@ You can now include a WebView widget in your widget tree. See the
1515
widget's Dartdoc for more details on how to use the widget.
1616

1717
## Android Platform Views
18-
The WebView is relying on
18+
This plugin uses
1919
[Platform Views](https://flutter.dev/docs/development/platform-integration/platform-views) to embed
20-
the Android’s webview within the Flutter app. It supports two modes: *Virtual displays* (the current default) and *Hybrid composition*.
20+
the Android’s webview within the Flutter app. It supports two modes:
21+
*hybrid composition* (the current default) and *virtual display*.
2122

2223
Here are some points to consider when choosing between the two:
2324

24-
* *Hybrid composition* mode has a built-in keyboard support while *Virtual displays* mode has multiple
25-
[keyboard issues](https://github.com/flutter/flutter/issues?q=is%3Aopen+label%3Avd-only+label%3A%22p%3A+webview-keyboard%22)
26-
* *Hybrid composition* mode requires Android SDK 19+ while *Virtual displays* mode requires Android SDK 20+
27-
* *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
25+
* *Hybrid composition* has built-in keyboard support while *virtual display* has multiple
26+
[keyboard issues](https://github.com/flutter/flutter/issues?q=is%3Aopen+label%3Avd-only+label%3A%22p%3A+webview-keyboard%22).
27+
* *Hybrid composition* requires Android SDK 19+ while *virtual display* requires Android SDK 20+.
28+
* *Hybrid composition* and *virtual display* have different
29+
[performance tradeoffs](https://flutter.dev/docs/development/platform-integration/platform-views#performance).
2830

29-
| | Hybrid composition | Virtual displays |
30-
| --------------------------- | ------------------- | ---------------- |
31-
| **Full keyboard supoport** | yes | no |
32-
| **Android SDK support** | 19+ | 20+ |
33-
| **Full performance** | Android 10+ | always |
34-
| **The default** | no | yes |
3531

36-
### Using Virtual displays
32+
### Using Hybrid Composition
3733

38-
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):
34+
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:
3935

4036
```groovy
4137
android {
4238
defaultConfig {
43-
minSdkVersion 20
39+
minSdkVersion 19
4440
}
4541
}
4642
```
4743

44+
### Using Virtual displays
4845

49-
### Using Hybrid Composition
50-
51-
1. Set the correct `minSdkVersion` in `android/app/build.gradle` (if it was previously lower than 19):
46+
1. Set the correct `minSdkVersion` in `android/app/build.gradle` (if it was previously lower than 20):
5247

5348
```groovy
5449
android {
5550
defaultConfig {
56-
minSdkVersion 19
51+
minSdkVersion 20
5752
}
5853
}
5954
```
6055
61-
2. Set `WebView.platform = SurfaceAndroidWebView();` in `initState()`.
56+
2. Set `WebView.platform = AndroidWebView();` in `initState()`.
6257
For example:
63-
58+
6459
```dart
6560
import 'dart:io';
66-
61+
6762
import 'package:webview_flutter/webview_flutter.dart';
6863
6964
class WebViewExample extends StatefulWidget {
7065
@override
7166
WebViewExampleState createState() => WebViewExampleState();
7267
}
73-
68+
7469
class WebViewExampleState extends State<WebViewExample> {
7570
@override
7671
void initState() {
7772
super.initState();
78-
// Enable hybrid composition.
79-
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
73+
// Enable virtual display.
74+
if (Platform.isAndroid) WebView.platform = AndroidWebView();
8075
}
8176
8277
@override

packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart

Lines changed: 13 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -860,192 +860,41 @@ void main() {
860860
}, skip: Platform.isAndroid && _skipDueToIssue86757);
861861
});
862862

863-
group('SurfaceAndroidWebView', () {
863+
// Minimial end-to-end testing of the legacy Android implementation.
864+
group('AndroidWebView (virtual display)', () {
864865
setUpAll(() {
865-
WebView.platform = SurfaceAndroidWebView();
866+
WebView.platform = AndroidWebView();
866867
});
867868

868869
tearDownAll(() {
869870
WebView.platform = null;
870871
});
871872

872-
// TODO(bparrishMines): skipped due to https://github.com/flutter/flutter/issues/86757.
873-
testWidgets('setAndGetScrollPosition', (WidgetTester tester) async {
874-
const String scrollTestPage = '''
875-
<!DOCTYPE html>
876-
<html>
877-
<head>
878-
<style>
879-
body {
880-
height: 100%;
881-
width: 100%;
882-
}
883-
#container{
884-
width:5000px;
885-
height:5000px;
886-
}
887-
</style>
888-
</head>
889-
<body>
890-
<div id="container"/>
891-
</body>
892-
</html>
893-
''';
894-
895-
final String scrollTestPageBase64 =
896-
base64Encode(const Utf8Encoder().convert(scrollTestPage));
897-
898-
final Completer<void> pageLoaded = Completer<void>();
873+
testWidgets('initialUrl', (WidgetTester tester) async {
899874
final Completer<WebViewController> controllerCompleter =
900875
Completer<WebViewController>();
901-
876+
final Completer<void> loadCompleter = Completer<void>();
902877
await tester.pumpWidget(
903878
Directionality(
904879
textDirection: TextDirection.ltr,
905880
child: WebView(
906-
initialUrl:
907-
'data:text/html;charset=utf-8;base64,$scrollTestPageBase64',
881+
key: GlobalKey(),
882+
initialUrl: primaryUrl,
908883
onWebViewCreated: (WebViewController controller) {
909884
controllerCompleter.complete(controller);
910885
},
911886
onPageFinished: (String url) {
912-
pageLoaded.complete(null);
887+
loadCompleter.complete();
913888
},
914889
),
915890
),
916891
);
917-
918-
final WebViewController controller = await controllerCompleter.future;
919-
await pageLoaded.future;
920-
921-
await tester.pumpAndSettle(const Duration(seconds: 3));
922-
923-
// Check scrollTo()
924-
const int X_SCROLL = 123;
925-
const int Y_SCROLL = 321;
926-
927-
await controller.scrollTo(X_SCROLL, Y_SCROLL);
928-
int scrollPosX = await controller.getScrollX();
929-
int scrollPosY = await controller.getScrollY();
930-
expect(X_SCROLL, scrollPosX);
931-
expect(Y_SCROLL, scrollPosY);
932-
933-
// Check scrollBy() (on top of scrollTo())
934-
await controller.scrollBy(X_SCROLL, Y_SCROLL);
935-
scrollPosX = await controller.getScrollX();
936-
scrollPosY = await controller.getScrollY();
937-
expect(X_SCROLL * 2, scrollPosX);
938-
expect(Y_SCROLL * 2, scrollPosY);
939-
}, skip: !Platform.isAndroid || _skipDueToIssue86757);
940-
941-
// TODO(bparrishMines): skipped due to https://github.com/flutter/flutter/issues/86757.
942-
testWidgets('inputs are scrolled into view when focused',
943-
(WidgetTester tester) async {
944-
const String scrollTestPage = '''
945-
<!DOCTYPE html>
946-
<html>
947-
<head>
948-
<style>
949-
input {
950-
margin: 10000px 0;
951-
}
952-
#viewport {
953-
position: fixed;
954-
top:0;
955-
bottom:0;
956-
left:0;
957-
right:0;
958-
}
959-
</style>
960-
</head>
961-
<body>
962-
<div id="viewport"></div>
963-
<input type="text" id="inputEl">
964-
</body>
965-
</html>
966-
''';
967-
968-
final String scrollTestPageBase64 =
969-
base64Encode(const Utf8Encoder().convert(scrollTestPage));
970-
971-
final Completer<void> pageLoaded = Completer<void>();
972-
final Completer<WebViewController> controllerCompleter =
973-
Completer<WebViewController>();
974-
975-
await tester.runAsync(() async {
976-
await tester.pumpWidget(
977-
Directionality(
978-
textDirection: TextDirection.ltr,
979-
child: SizedBox(
980-
width: 200,
981-
height: 200,
982-
child: WebView(
983-
initialUrl:
984-
'data:text/html;charset=utf-8;base64,$scrollTestPageBase64',
985-
onWebViewCreated: (WebViewController controller) {
986-
controllerCompleter.complete(controller);
987-
},
988-
onPageFinished: (String url) {
989-
pageLoaded.complete(null);
990-
},
991-
javascriptMode: JavascriptMode.unrestricted,
992-
),
993-
),
994-
),
995-
);
996-
await Future<dynamic>.delayed(const Duration(milliseconds: 20));
997-
await tester.pump();
998-
});
999-
1000892
final WebViewController controller = await controllerCompleter.future;
1001-
await pageLoaded.future;
1002-
final String viewportRectJSON = await _runJavascriptReturningResult(
1003-
controller, 'JSON.stringify(viewport.getBoundingClientRect())');
1004-
final Map<String, dynamic> viewportRectRelativeToViewport =
1005-
jsonDecode(viewportRectJSON) as Map<String, dynamic>;
1006-
1007-
// Check that the input is originally outside of the viewport.
1008-
1009-
final String initialInputClientRectJSON =
1010-
await _runJavascriptReturningResult(
1011-
controller, 'JSON.stringify(inputEl.getBoundingClientRect())');
1012-
final Map<String, dynamic> initialInputClientRectRelativeToViewport =
1013-
jsonDecode(initialInputClientRectJSON) as Map<String, dynamic>;
1014-
1015-
expect(
1016-
initialInputClientRectRelativeToViewport['bottom'] <=
1017-
viewportRectRelativeToViewport['bottom'],
1018-
isFalse);
1019-
1020-
await controller.runJavascript('inputEl.focus()');
1021-
1022-
// Check that focusing the input brought it into view.
1023-
1024-
final String lastInputClientRectJSON =
1025-
await _runJavascriptReturningResult(
1026-
controller, 'JSON.stringify(inputEl.getBoundingClientRect())');
1027-
final Map<String, dynamic> lastInputClientRectRelativeToViewport =
1028-
jsonDecode(lastInputClientRectJSON) as Map<String, dynamic>;
1029-
1030-
expect(
1031-
lastInputClientRectRelativeToViewport['top'] >=
1032-
viewportRectRelativeToViewport['top'],
1033-
isTrue);
1034-
expect(
1035-
lastInputClientRectRelativeToViewport['bottom'] <=
1036-
viewportRectRelativeToViewport['bottom'],
1037-
isTrue);
1038-
1039-
expect(
1040-
lastInputClientRectRelativeToViewport['left'] >=
1041-
viewportRectRelativeToViewport['left'],
1042-
isTrue);
1043-
expect(
1044-
lastInputClientRectRelativeToViewport['right'] <=
1045-
viewportRectRelativeToViewport['right'],
1046-
isTrue);
1047-
}, skip: !Platform.isAndroid || _skipDueToIssue86757);
1048-
});
893+
await loadCompleter.future;
894+
final String? currentUrl = await controller.currentUrl();
895+
expect(currentUrl, primaryUrl);
896+
});
897+
}, skip: !Platform.isAndroid || _skipDueToIssue86757);
1049898

1050899
group('NavigationDelegate', () {
1051900
const String blankPage = '<!DOCTYPE html><head></head><body></body></html>';

packages/webview_flutter/webview_flutter/lib/src/webview.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import 'package:flutter/foundation.dart';
99
import 'package:flutter/gestures.dart';
1010
import 'package:flutter/rendering.dart';
1111
import 'package:flutter/widgets.dart';
12-
import 'package:webview_flutter_android/webview_android.dart';
1312
import 'package:webview_flutter_android/webview_android_cookie_manager.dart';
13+
import 'package:webview_flutter_android/webview_surface_android.dart';
1414
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
1515
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
1616

@@ -119,12 +119,12 @@ class WebView extends StatefulWidget {
119119

120120
/// The WebView platform that's used by this WebView.
121121
///
122-
/// The default value is [AndroidWebView] on Android and [CupertinoWebView] on iOS.
122+
/// The default value is [SurfaceAndroidWebView] on Android and [CupertinoWebView] on iOS.
123123
static WebViewPlatform get platform {
124124
if (_platform == null) {
125125
switch (defaultTargetPlatform) {
126126
case TargetPlatform.android:
127-
_platform = AndroidWebView();
127+
_platform = SurfaceAndroidWebView();
128128
break;
129129
case TargetPlatform.iOS:
130130
_platform = CupertinoWebView();

packages/webview_flutter/webview_flutter/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: webview_flutter
22
description: A Flutter plugin that provides a WebView widget on Android and iOS.
33
repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
5-
version: 2.8.0
5+
version: 3.0.0
66

77
environment:
88
sdk: ">=2.14.0 <3.0.0"

0 commit comments

Comments
 (0)