diff --git a/packages/webview_flutter/CHANGELOG.md b/packages/webview_flutter/CHANGELOG.md index e1f4c3c2d1c8..2b5bb7e45152 100644 --- a/packages/webview_flutter/CHANGELOG.md +++ b/packages/webview_flutter/CHANGELOG.md @@ -1,3 +1,27 @@ +## 0.3.24 + +* Add support for building `WebView` widget with Android hybrid views. To use this feature, set + `WebView.platform` to an instance of `SurfaceAndroidWebView`. For example: + +```dart +import 'dart:io'; + +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', + ); + } +} +``` + ## 0.3.23 * Handle WebView multi-window support. diff --git a/packages/webview_flutter/example/lib/main.dart b/packages/webview_flutter/example/lib/main.dart index 59c87a25dedf..ff25e8a16c9b 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'; @@ -35,6 +36,12 @@ class _WebViewExampleState extends State { final Completer _controller = Completer(); + @override + void initState() { + super.initState(); + if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView(); + } + @override Widget build(BuildContext context) { return Scaffold( diff --git a/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart b/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart index 53fc991c48f2..2a17c53a4c3f 100644 --- a/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart +++ b/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart @@ -608,6 +608,84 @@ void main() { }); }); + group('$SurfaceAndroidWebView', () { + setUpAll(() { + WebView.platform = SurfaceAndroidWebView(); + }); + + tearDownAll(() { + WebView.platform = null; + }); + + testWidgets('setAndGetScrollPosition', (WidgetTester tester) async { + final String scrollTestPage = ''' + + + + + + +
+ + + '''; + + final String scrollTestPageBase64 = + base64Encode(const Utf8Encoder().convert(scrollTestPage)); + + final Completer pageLoaded = Completer(); + final Completer controllerCompleter = + Completer(); + + 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 = ""; final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' + diff --git a/packages/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/lib/webview_flutter.dart index 2635b0446fa2..5e2bffd6539d 100644 --- a/packages/webview_flutter/lib/webview_flutter.dart +++ b/packages/webview_flutter/lib/webview_flutter.dart @@ -6,11 +6,14 @@ import 'dart:async'; 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 @@ enum NavigationDecision { 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> 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 >{}, + 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 @@ WebSettings _clearUnchangedWebSettings( Set _extractChannelNames(Set channels) { final Set channelNames = channels == null - // TODO(iskakaushik): Remove this when collection literals makes it to stable. - // ignore: prefer_collection_literals - ? Set() + ? {} : channels.map((JavascriptChannel channel) => channel.name).toSet(); return channelNames; } diff --git a/packages/webview_flutter/pubspec.yaml b/packages/webview_flutter/pubspec.yaml index 8a4a62ea76a6..2212ef6c4059 100644 --- a/packages/webview_flutter/pubspec.yaml +++ b/packages/webview_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: webview_flutter description: A Flutter plugin that provides a WebView widget on Android and iOS. -version: 0.3.23 +version: 0.3.24 homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter environment: