[webview_flutter] Add new entrypoint that uses hybrid composition on Android (#3067)
diff --git a/packages/webview_flutter/CHANGELOG.md b/packages/webview_flutter/CHANGELOG.md
index 5e32e49..f327245 100644
--- a/packages/webview_flutter/CHANGELOG.md
+++ b/packages/webview_flutter/CHANGELOG.md
@@ -1,3 +1,17 @@
+## 1.0.0 - Out of developer preview 🎉.
+
+* Bumped the minimal Flutter SDK to 1.22 where platform views are out of developer preview, and
+performing better on iOS. Flutter 1.22 no longer requires adding the
+`io.flutter.embedded_views_preview` flag to `Info.plist`.
+
+* Added support for Hybrid Composition on Android (see opt-in instructions in [README](https://github.com/flutter/plugins/blob/master/packages/webview_flutter/README.md#android))
+ * Lowered the required Android API to 19 (was previously 20): [#23728](https://github.com/flutter/flutter/issues/23728).
+ * Fixed the following issues:
+ * 🎹 Keyboard: [#41089](https://github.com/flutter/flutter/issues/41089), [#36478](https://github.com/flutter/flutter/issues/36478), [#51254](https://github.com/flutter/flutter/issues/51254), [#50716](https://github.com/flutter/flutter/issues/50716), [#55724](https://github.com/flutter/flutter/issues/55724), [#56513](https://github.com/flutter/flutter/issues/56513), [#56515](https://github.com/flutter/flutter/issues/56515), [#61085](https://github.com/flutter/flutter/issues/61085), [#62205](https://github.com/flutter/flutter/issues/62205), [#62547](https://github.com/flutter/flutter/issues/62547), [#58943](https://github.com/flutter/flutter/issues/58943), [#56361](https://github.com/flutter/flutter/issues/56361), [#56361](https://github.com/flutter/flutter/issues/42902), [#40716](https://github.com/flutter/flutter/issues/40716), [#37989](https://github.com/flutter/flutter/issues/37989), [#27924](https://github.com/flutter/flutter/issues/27924).
+ * ♿️ Accessibility: [#50716](https://github.com/flutter/flutter/issues/50716).
+ * ⚡️ Performance: [#61280](https://github.com/flutter/flutter/issues/61280), [#31243](https://github.com/flutter/flutter/issues/31243), [#52211](https://github.com/flutter/flutter/issues/52211).
+ * 📹 Video: [#5191](https://github.com/flutter/flutter/issues/5191).
+
## 0.3.24
* Keep handling deprecated Android v1 classes for backward compatibility.
diff --git a/packages/webview_flutter/README.md b/packages/webview_flutter/README.md
index c86993a..c7b55f1 100644
--- a/packages/webview_flutter/README.md
+++ b/packages/webview_flutter/README.md
@@ -1,4 +1,4 @@
-# WebView for Flutter (Developers Preview)
+# WebView for Flutter
[![pub package](https://img.shields.io/pub/v/webview_flutter.svg)](https://pub.dartlang.org/packages/webview_flutter)
@@ -7,30 +7,58 @@
On iOS the WebView widget is backed by a [WKWebView](https://developer.apple.com/documentation/webkit/wkwebview);
On Android the WebView widget is backed by a [WebView](https://developer.android.com/reference/android/webkit/WebView).
-## Developers Preview Status
-The plugin relies on Flutter's new mechanism for embedding Android and iOS views.
-As that mechanism is currently in a developers preview, this plugin should also be
-considered a developers preview.
-
-Known issues are tagged with the [platform-views](https://github.com/flutter/flutter/labels/a%3A%20platform-views) and/or [webview](https://github.com/flutter/flutter/labels/p%3A%20webview) labels.
-
-To use this plugin on iOS you need to opt-in for the embedded views preview by
-adding a boolean property to the app's `Info.plist` file, with the key `io.flutter.embedded_views_preview`
-and the value `YES`.
-
-## Keyboard support - not ready for production use
-Keyboard support within webviews is experimental. The Android version relies on some low-level knobs that have not been well tested
-on a broad spectrum of devices yet, and therefore **it is not recommended to rely on webview keyboard in production apps yet**.
-See the [webview-keyboard](https://github.com/flutter/flutter/issues?q=is%3Aopen+is%3Aissue+label%3A%22p%3A+webview-keyboard%22) for known issues with keyboard input.
-
-## Setup
-
-### iOS
-Opt-in to the embedded views preview by adding a boolean property to the app's `Info.plist` file
-with the key `io.flutter.embedded_views_preview` and the value `YES`.
-
## Usage
Add `webview_flutter` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/).
-You can now include a WebView widget in your widget tree.
-See the WebView widget's Dartdoc for more details on how to use the widget.
+You can now include a WebView widget in your widget tree. See the
+[WebView](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebView-class.html)
+widget's Dartdoc for more details on how to use the widget.
+
+
+
+## Android Platform Views
+The WebView is relying on
+[Platform Views](https://flutter.dev/docs/development/platform-integration/platform-views) to embed
+the Android’s webview within the Flutter app. By default a Virtual Display based platform view
+backend is used, this implementation has multiple
+[keyboard](https://github.com/flutter/flutter/issues?q=is%3Aopen+label%3Avd-only+label%3A%22p%3A+webview-keyboard%22).
+When keyboard input is required we recommend using the Hybrid Composition based platform views
+implementation. Note that on Android versions prior to Android 10 Hybrid Composition has some
+[performance drawbacks](https://flutter.dev/docs/development/platform-integration/platform-views#performance).
+
+### Using Hybrid Composition
+
+To enable hybrid composition, set `WebView.platform = SurfaceAndroidWebView();` in `initState()`.
+For example:
+
+```dart
+import 'dart:io';
+
+import 'package:webview_flutter/webview_flutter.dart';
+
+class WebViewExample extends StatefulWidget {
+ @override
+ void initState() {
+ super.initState();
+ if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return WebView(
+ initialUrl: 'https://flutter.dev',
+ );
+ }
+}
+```
+
+`SurfaceAndroidWebView()` requires [API level 19](https://developer.android.com/studio/releases/platforms?hl=th#4.4). The plugin itself doesn't enforce the API level, so if you want to make the app available on devices running this API level or above, add the following to `<your-app>/android/app/build.gradle`:
+
+```gradle
+android {
+ defaultConfig {
+ // Required by the Flutter WebView plugin.
+ minSdkVersion 19
+ }
+ }
+```
diff --git a/packages/webview_flutter/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/example/integration_test/webview_flutter_test.dart
index 53fc991..2a17c53 100644
--- a/packages/webview_flutter/example/integration_test/webview_flutter_test.dart
+++ b/packages/webview_flutter/example/integration_test/webview_flutter_test.dart
@@ -608,6 +608,84 @@
});
});
+ group('$SurfaceAndroidWebView', () {
+ setUpAll(() {
+ WebView.platform = SurfaceAndroidWebView();
+ });
+
+ tearDownAll(() {
+ WebView.platform = null;
+ });
+
+ testWidgets('setAndGetScrollPosition', (WidgetTester tester) async {
+ final 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>();
+ final Completer<WebViewController> controllerCompleter =
+ Completer<WebViewController>();
+
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: WebView(
+ initialUrl:
+ 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64',
+ onWebViewCreated: (WebViewController controller) {
+ controllerCompleter.complete(controller);
+ },
+ onPageFinished: (String url) {
+ pageLoaded.complete(null);
+ },
+ ),
+ ),
+ );
+
+ final WebViewController controller = await controllerCompleter.future;
+ await pageLoaded.future;
+
+ await tester.pumpAndSettle(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);
+
group('NavigationDelegate', () {
final String blankPage = "<!DOCTYPE html><head></head><body></body></html>";
final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' +
diff --git a/packages/webview_flutter/example/ios/Runner/Info.plist b/packages/webview_flutter/example/ios/Runner/Info.plist
index 94f5857..a810c5a 100644
--- a/packages/webview_flutter/example/ios/Runner/Info.plist
+++ b/packages/webview_flutter/example/ios/Runner/Info.plist
@@ -39,8 +39,6 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
- <key>io.flutter.embedded_views_preview</key>
- <true/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
diff --git a/packages/webview_flutter/example/lib/main.dart b/packages/webview_flutter/example/lib/main.dart
index 59c87a2..ff25e8a 100644
--- a/packages/webview_flutter/example/lib/main.dart
+++ b/packages/webview_flutter/example/lib/main.dart
@@ -6,6 +6,7 @@
import 'dart:async';
import 'dart:convert';
+import 'dart:io';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
@@ -36,6 +37,12 @@
Completer<WebViewController>();
@override
+ void initState() {
+ super.initState();
+ if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
+ }
+
+ @override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
diff --git a/packages/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/lib/webview_flutter.dart
index 2635b04..5e2bffd 100644
--- a/packages/webview_flutter/lib/webview_flutter.dart
+++ b/packages/webview_flutter/lib/webview_flutter.dart
@@ -6,11 +6,14 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'platform_interface.dart';
import 'src/webview_android.dart';
import 'src/webview_cupertino.dart';
+import 'src/webview_method_channel.dart';
/// Optional callback invoked when a web view is first created. [controller] is
/// the [WebViewController] for the created web view.
@@ -64,6 +67,66 @@
navigate,
}
+/// Android [WebViewPlatform] that uses [AndroidViewSurface] to build the [WebView] widget.
+///
+/// To use this, set [WebView.platform] to an instance of this class.
+///
+/// This implementation uses hybrid composition to render the [WebView] on
+/// Android. It solves multiple issues related to accessibility and interaction
+/// with the [WebView] at the cost of some performance on Android versions below
+/// 10. See https://github.com/flutter/flutter/wiki/Hybrid-Composition for more
+/// information.
+class SurfaceAndroidWebView extends AndroidWebView {
+ @override
+ Widget build({
+ BuildContext context,
+ CreationParams creationParams,
+ WebViewPlatformCreatedCallback onWebViewPlatformCreated,
+ Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
+ @required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler,
+ }) {
+ assert(webViewPlatformCallbacksHandler != null);
+ return PlatformViewLink(
+ viewType: 'plugins.flutter.io/webview',
+ surfaceFactory: (
+ BuildContext context,
+ PlatformViewController controller,
+ ) {
+ return AndroidViewSurface(
+ controller: controller,
+ gestureRecognizers: gestureRecognizers ??
+ const <Factory<OneSequenceGestureRecognizer>>{},
+ hitTestBehavior: PlatformViewHitTestBehavior.opaque,
+ );
+ },
+ onCreatePlatformView: (PlatformViewCreationParams params) {
+ return PlatformViewsService.initSurfaceAndroidView(
+ id: params.id,
+ viewType: 'plugins.flutter.io/webview',
+ // WebView content is not affected by the Android view's layout direction,
+ // we explicitly set it here so that the widget doesn't require an ambient
+ // directionality.
+ layoutDirection: TextDirection.rtl,
+ creationParams: MethodChannelWebViewPlatform.creationParamsToMap(
+ creationParams,
+ ),
+ creationParamsCodec: const StandardMessageCodec(),
+ )
+ ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
+ ..addOnPlatformViewCreatedListener((int id) {
+ if (onWebViewPlatformCreated == null) {
+ return;
+ }
+ onWebViewPlatformCreated(
+ MethodChannelWebViewPlatform(id, webViewPlatformCallbacksHandler),
+ );
+ })
+ ..create();
+ },
+ );
+ }
+}
+
/// Decides how to handle a specific navigation request.
///
/// The returned [NavigationDecision] determines how the navigation described by
@@ -445,9 +508,7 @@
Set<String> _extractChannelNames(Set<JavascriptChannel> channels) {
final Set<String> channelNames = channels == null
- // TODO(iskakaushik): Remove this when collection literals makes it to stable.
- // ignore: prefer_collection_literals
- ? Set<String>()
+ ? <String>{}
: channels.map((JavascriptChannel channel) => channel.name).toSet();
return channelNames;
}
diff --git a/packages/webview_flutter/pubspec.yaml b/packages/webview_flutter/pubspec.yaml
index 2212ef6..aa9c606 100644
--- a/packages/webview_flutter/pubspec.yaml
+++ b/packages/webview_flutter/pubspec.yaml
@@ -1,11 +1,11 @@
name: webview_flutter
description: A Flutter plugin that provides a WebView widget on Android and iOS.
-version: 0.3.24
+version: 1.0.0
homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter
environment:
sdk: ">=2.7.0 <3.0.0"
- flutter: ">=1.12.13+hotfix.5"
+ flutter: ">=1.22.0 <2.0.0"
dependencies:
flutter: