From 90a8828c827bb0a98b75232ad0071e8ca1852ef6 Mon Sep 17 00:00:00 2001 From: Amir Hardon Date: Mon, 20 May 2019 16:47:30 -0700 Subject: [PATCH 1/5] Allow changing the webview's platform specific implementation --- .../lib/platform_interface.dart | 44 ++++++++++ .../lib/src/webview_android.dart | 41 ++++++++++ .../webview_flutter/lib/src/webview_ios.dart | 26 ++++++ .../lib/src/webview_method_channel.dart | 33 ++++++++ .../webview_flutter/lib/webview_flutter.dart | 82 +++++++++---------- 5 files changed, 183 insertions(+), 43 deletions(-) create mode 100644 packages/webview_flutter/lib/platform_interface.dart create mode 100644 packages/webview_flutter/lib/src/webview_android.dart create mode 100644 packages/webview_flutter/lib/src/webview_ios.dart create mode 100644 packages/webview_flutter/lib/src/webview_method_channel.dart diff --git a/packages/webview_flutter/lib/platform_interface.dart b/packages/webview_flutter/lib/platform_interface.dart new file mode 100644 index 000000000000..ea9feab18faf --- /dev/null +++ b/packages/webview_flutter/lib/platform_interface.dart @@ -0,0 +1,44 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/widgets.dart'; + +/// Interface for talking to the webview controller on the platform side. +/// +/// An instance implementing this interface is passed to the `onWebViewCreated` callback that is +/// passed to [WebViewPlatformInterface#onWebViewCreated]. +abstract class WebViewPlatformControllerInterface { + Future loadUrl( + String url, + Map headers, + ); + + // As the PR currently focus about the wiring I've only moved loadUrl to the new way, so + // the discussion is more focused. + // In this temporary state WebViewController still uses a method channel directly for all other + // method calls so we need to expose the webview ID. + // TODO(amirh): remove this before submitting this PR(after getting an LGTM for the overall approach). + int get id; +} + +typedef WebViewCreatedCallback = void Function(WebViewPlatformControllerInterface); + +/// Interface for a platform specific webview implementation. +/// +/// [WebView#implementation] controls the platform interface that is used by [WebView]. +/// [WebViewAndroidImplementation] and [WebViewIosImplementation] are the default implementations +/// for Android and iOS respectively. +abstract class WebViewPlatformInterface { + Widget build({ + BuildContext context, + Map creationParams, + WebViewCreatedCallback onWebViewCreated, + Set> gestureRecognizers, + }); +} + diff --git a/packages/webview_flutter/lib/src/webview_android.dart b/packages/webview_flutter/lib/src/webview_android.dart new file mode 100644 index 000000000000..855fd347fa79 --- /dev/null +++ b/packages/webview_flutter/lib/src/webview_android.dart @@ -0,0 +1,41 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +import '../platform_interface.dart'; +import 'webview_method_channel.dart'; + +class WebViewAndroidImplementation implements WebViewPlatformInterface { + @override + Widget build({BuildContext context, Map creationParams, WebViewCreatedCallback onWebViewCreated, Set> gestureRecognizers}) { + return GestureDetector( + // We prevent text selection by intercepting the long press event. + // This is a temporary stop gap due to issues with text selection on Android: + // https://github.com/flutter/flutter/issues/24585 - the text selection + // dialog is not responding to touch events. + // https://github.com/flutter/flutter/issues/24584 - the text selection + // handles are not showing. + // TODO(amirh): remove this when the issues above are fixed. + onLongPress: () {}, + excludeFromSemantics: true, + child: AndroidView( + viewType: 'plugins.flutter.io/webview', + onPlatformViewCreated: (int id) { + onWebViewCreated(WebViewMethodChannelController(id)); + }, + gestureRecognizers: gestureRecognizers, + // 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: creationParams, + creationParamsCodec: const StandardMessageCodec(), + ), + ); + } +} diff --git a/packages/webview_flutter/lib/src/webview_ios.dart b/packages/webview_flutter/lib/src/webview_ios.dart new file mode 100644 index 000000000000..d6bccb8255f3 --- /dev/null +++ b/packages/webview_flutter/lib/src/webview_ios.dart @@ -0,0 +1,26 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +import '../platform_interface.dart'; +import 'webview_method_channel.dart'; + +class WebViewIosImplementation implements WebViewPlatformInterface { + @override + Widget build({BuildContext context, Map creationParams, WebViewCreatedCallback onWebViewCreated, Set> gestureRecognizers}) { + return UiKitView( + viewType: 'plugins.flutter.io/webview', + onPlatformViewCreated: (int id) { + onWebViewCreated(WebViewMethodChannelController(id)); + }, + gestureRecognizers: gestureRecognizers, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + ); + } +} diff --git a/packages/webview_flutter/lib/src/webview_method_channel.dart b/packages/webview_flutter/lib/src/webview_method_channel.dart new file mode 100644 index 000000000000..5f33517c0a4c --- /dev/null +++ b/packages/webview_flutter/lib/src/webview_method_channel.dart @@ -0,0 +1,33 @@ +import 'dart:async'; + +import 'package:flutter/services.dart'; + +import '../platform_interface.dart'; + +/// A [WebViewPlatformControllerInterface] that uses a method channel to control the webview. +class WebViewMethodChannelController implements WebViewPlatformControllerInterface { + + WebViewMethodChannelController(this._id) : _channel = MethodChannel('plugins.flutter.io/webview_$_id'); + + final int _id; + + final MethodChannel _channel; + + @override + Future loadUrl( + String url, + Map headers, + ) async { + assert(url != null); + // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter. + // https://github.com/flutter/flutter/issues/26431 + // ignore: strong_mode_implicit_dynamic_method + return _channel.invokeMethod('loadUrl', { + 'url': url, + 'headers': headers, + }); + } + + @override + int get id => _id; +} diff --git a/packages/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/lib/webview_flutter.dart index 1dabba1a0acf..5b46cd34d207 100644 --- a/packages/webview_flutter/lib/webview_flutter.dart +++ b/packages/webview_flutter/lib/webview_flutter.dart @@ -9,6 +9,10 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; +import 'platform_interface.dart'; +import 'src/webview_android.dart'; +import 'src/webview_ios.dart'; + typedef void WebViewCreatedCallback(WebViewController controller); enum JavascriptMode { @@ -121,6 +125,28 @@ class WebView extends StatefulWidget { }) : assert(javascriptMode != null), super(key: key); + static WebViewPlatformInterface _implementation; + + static set implementation(WebViewPlatformInterface implementation) { + _implementation = implementation; + } + + static WebViewPlatformInterface get implementation { + if (_implementation != null) { + return implementation; + } + + if (defaultTargetPlatform == TargetPlatform.android) { + return WebViewAndroidImplementation(); + } + + if (defaultTargetPlatform == TargetPlatform.iOS) { + return WebViewIosImplementation(); + } + + throw UnsupportedError("Trying to use the default webview implementation for $defaultTargetPlatform but there isn't a default one"); + } + /// If not null invoked once the web view is created. final WebViewCreatedCallback onWebViewCreated; @@ -229,40 +255,12 @@ class _WebViewState extends State { @override Widget build(BuildContext context) { - if (defaultTargetPlatform == TargetPlatform.android) { - return GestureDetector( - // We prevent text selection by intercepting the long press event. - // This is a temporary stop gap due to issues with text selection on Android: - // https://github.com/flutter/flutter/issues/24585 - the text selection - // dialog is not responding to touch events. - // https://github.com/flutter/flutter/issues/24584 - the text selection - // handles are not showing. - // TODO(amirh): remove this when the issues above are fixed. - onLongPress: () {}, - excludeFromSemantics: true, - child: AndroidView( - viewType: 'plugins.flutter.io/webview', - onPlatformViewCreated: _onPlatformViewCreated, - gestureRecognizers: widget.gestureRecognizers, - // 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: _CreationParams.fromWidget(widget).toMap(), - creationParamsCodec: const StandardMessageCodec(), - ), - ); - } else if (defaultTargetPlatform == TargetPlatform.iOS) { - return UiKitView( - viewType: 'plugins.flutter.io/webview', - onPlatformViewCreated: _onPlatformViewCreated, - gestureRecognizers: widget.gestureRecognizers, - creationParams: _CreationParams.fromWidget(widget).toMap(), - creationParamsCodec: const StandardMessageCodec(), - ); - } - return Text( - '$defaultTargetPlatform is not yet supported by the webview_flutter plugin'); + return WebView.implementation.build( + context: context, + creationParams: _CreationParams.fromWidget(widget).toMap(), + onWebViewCreated: _onWebViewControllerCreated, + gestureRecognizers: widget.gestureRecognizers, + ); } @override @@ -279,8 +277,9 @@ class _WebViewState extends State { (WebViewController controller) => controller._updateWidget(widget)); } - void _onPlatformViewCreated(int id) { - final WebViewController controller = WebViewController._(id, widget); + void _onWebViewControllerCreated(WebViewPlatformControllerInterface platformController) { + final WebViewController controller = + WebViewController._(platformController.id, platformController, widget); _controller.complete(controller); if (widget.onWebViewCreated != null) { widget.onWebViewCreated(controller); @@ -385,6 +384,7 @@ class _WebSettings { class WebViewController { WebViewController._( int id, + this._platformInterface, this._widget, ) : _channel = MethodChannel('plugins.flutter.io/webview_$id') { _settings = _WebSettings.fromWidget(_widget); @@ -394,6 +394,8 @@ class WebViewController { final MethodChannel _channel; + final WebViewPlatformControllerInterface _platformInterface; + _WebSettings _settings; WebView _widget; @@ -443,13 +445,7 @@ class WebViewController { }) async { assert(url != null); _validateUrlString(url); - // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter. - // https://github.com/flutter/flutter/issues/26431 - // ignore: strong_mode_implicit_dynamic_method - return _channel.invokeMethod('loadUrl', { - 'url': url, - 'headers': headers, - }); + return _platformInterface.loadUrl(url, headers); } /// Accessor to the current URL that the WebView is displaying. From b78c98fef910e87fda4a27a0452acde5d299df70 Mon Sep 17 00:00:00 2001 From: Amir Hardon Date: Tue, 21 May 2019 13:34:43 -0700 Subject: [PATCH 2/5] initial review comments followup --- .../lib/platform_interface.dart | 20 +++++---- .../lib/src/webview_android.dart | 13 ++++-- ...ebview_ios.dart => webview_cupertino.dart} | 13 ++++-- .../lib/src/webview_method_channel.dart | 18 +++++--- .../webview_flutter/lib/webview_flutter.dart | 43 ++++++++++--------- 5 files changed, 64 insertions(+), 43 deletions(-) rename packages/webview_flutter/lib/src/{webview_ios.dart => webview_cupertino.dart} (63%) diff --git a/packages/webview_flutter/lib/platform_interface.dart b/packages/webview_flutter/lib/platform_interface.dart index ea9feab18faf..7299d8e5a8a2 100644 --- a/packages/webview_flutter/lib/platform_interface.dart +++ b/packages/webview_flutter/lib/platform_interface.dart @@ -9,14 +9,17 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; /// Interface for talking to the webview controller on the platform side. -/// +/// /// An instance implementing this interface is passed to the `onWebViewCreated` callback that is /// passed to [WebViewPlatformInterface#onWebViewCreated]. -abstract class WebViewPlatformControllerInterface { +abstract class WebViewPlatform { Future loadUrl( - String url, - Map headers, - ); + String url, + Map headers, + ) { + throw UnimplementedError( + "WebView loadUrl is not implemented on the current platform"); + } // As the PR currently focus about the wiring I've only moved loadUrl to the new way, so // the discussion is more focused. @@ -26,14 +29,14 @@ abstract class WebViewPlatformControllerInterface { int get id; } -typedef WebViewCreatedCallback = void Function(WebViewPlatformControllerInterface); +typedef WebViewCreatedCallback = void Function(WebViewPlatform webViewPlatform); /// Interface for a platform specific webview implementation. /// -/// [WebView#implementation] controls the platform interface that is used by [WebView]. +/// [WebView#iplatformBuilder] controls the platform interface that is used by [WebView]. /// [WebViewAndroidImplementation] and [WebViewIosImplementation] are the default implementations /// for Android and iOS respectively. -abstract class WebViewPlatformInterface { +abstract class WebViewBuilder { Widget build({ BuildContext context, Map creationParams, @@ -41,4 +44,3 @@ abstract class WebViewPlatformInterface { Set> gestureRecognizers, }); } - diff --git a/packages/webview_flutter/lib/src/webview_android.dart b/packages/webview_flutter/lib/src/webview_android.dart index 855fd347fa79..d8083e68620c 100644 --- a/packages/webview_flutter/lib/src/webview_android.dart +++ b/packages/webview_flutter/lib/src/webview_android.dart @@ -10,9 +10,13 @@ import 'package:flutter/widgets.dart'; import '../platform_interface.dart'; import 'webview_method_channel.dart'; -class WebViewAndroidImplementation implements WebViewPlatformInterface { +class AndroidWebViewBuilder implements WebViewBuilder { @override - Widget build({BuildContext context, Map creationParams, WebViewCreatedCallback onWebViewCreated, Set> gestureRecognizers}) { + Widget build( + {BuildContext context, + Map creationParams, + WebViewCreatedCallback onWebViewCreated, + Set> gestureRecognizers}) { return GestureDetector( // We prevent text selection by intercepting the long press event. // This is a temporary stop gap due to issues with text selection on Android: @@ -26,7 +30,10 @@ class WebViewAndroidImplementation implements WebViewPlatformInterface { child: AndroidView( viewType: 'plugins.flutter.io/webview', onPlatformViewCreated: (int id) { - onWebViewCreated(WebViewMethodChannelController(id)); + if (onWebViewCreated == null) { + return; + } + onWebViewCreated(MethodChannelWebViewPlatform(id)); }, gestureRecognizers: gestureRecognizers, // WebView content is not affected by the Android view's layout direction, diff --git a/packages/webview_flutter/lib/src/webview_ios.dart b/packages/webview_flutter/lib/src/webview_cupertino.dart similarity index 63% rename from packages/webview_flutter/lib/src/webview_ios.dart rename to packages/webview_flutter/lib/src/webview_cupertino.dart index d6bccb8255f3..e97fbe54eb15 100644 --- a/packages/webview_flutter/lib/src/webview_ios.dart +++ b/packages/webview_flutter/lib/src/webview_cupertino.dart @@ -10,13 +10,20 @@ import 'package:flutter/widgets.dart'; import '../platform_interface.dart'; import 'webview_method_channel.dart'; -class WebViewIosImplementation implements WebViewPlatformInterface { +class CupertinoWebViewBuilder implements WebViewBuilder { @override - Widget build({BuildContext context, Map creationParams, WebViewCreatedCallback onWebViewCreated, Set> gestureRecognizers}) { + Widget build( + {BuildContext context, + Map creationParams, + WebViewCreatedCallback onWebViewCreated, + Set> gestureRecognizers}) { return UiKitView( viewType: 'plugins.flutter.io/webview', onPlatformViewCreated: (int id) { - onWebViewCreated(WebViewMethodChannelController(id)); + if (onWebViewCreated == null) { + return; + } + onWebViewCreated(MethodChannelWebViewPlatform(id)); }, gestureRecognizers: gestureRecognizers, creationParams: creationParams, diff --git a/packages/webview_flutter/lib/src/webview_method_channel.dart b/packages/webview_flutter/lib/src/webview_method_channel.dart index 5f33517c0a4c..e13fe6f4e5e5 100644 --- a/packages/webview_flutter/lib/src/webview_method_channel.dart +++ b/packages/webview_flutter/lib/src/webview_method_channel.dart @@ -1,13 +1,17 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + import 'dart:async'; import 'package:flutter/services.dart'; import '../platform_interface.dart'; -/// A [WebViewPlatformControllerInterface] that uses a method channel to control the webview. -class WebViewMethodChannelController implements WebViewPlatformControllerInterface { - - WebViewMethodChannelController(this._id) : _channel = MethodChannel('plugins.flutter.io/webview_$_id'); +/// A [WebViewPlatform] that uses a method channel to control the webview. +class MethodChannelWebViewPlatform implements WebViewPlatform { + MethodChannelWebViewPlatform(this._id) + : _channel = MethodChannel('plugins.flutter.io/webview_$_id'); final int _id; @@ -15,9 +19,9 @@ class WebViewMethodChannelController implements WebViewPlatformControllerInterfa @override Future loadUrl( - String url, - Map headers, - ) async { + String url, + Map headers, + ) async { assert(url != null); // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter. // https://github.com/flutter/flutter/issues/26431 diff --git a/packages/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/lib/webview_flutter.dart index 5b46cd34d207..dd07e74cf244 100644 --- a/packages/webview_flutter/lib/webview_flutter.dart +++ b/packages/webview_flutter/lib/webview_flutter.dart @@ -11,7 +11,7 @@ import 'package:flutter/widgets.dart'; import 'platform_interface.dart'; import 'src/webview_android.dart'; -import 'src/webview_ios.dart'; +import 'src/webview_cupertino.dart'; typedef void WebViewCreatedCallback(WebViewController controller); @@ -125,26 +125,27 @@ class WebView extends StatefulWidget { }) : assert(javascriptMode != null), super(key: key); - static WebViewPlatformInterface _implementation; + static WebViewBuilder _platformBuilder; - static set implementation(WebViewPlatformInterface implementation) { - _implementation = implementation; + static set platformBuilder(WebViewBuilder platformBuilder) { + _platformBuilder = platformBuilder; } - static WebViewPlatformInterface get implementation { - if (_implementation != null) { - return implementation; + static WebViewBuilder get platformBuilder { + if (_platformBuilder == null) { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + _platformBuilder = AndroidWebViewBuilder(); + break; + case TargetPlatform.iOS: + _platformBuilder = CupertinoWebViewBuilder(); + break; + default: + throw UnsupportedError( + "Trying to use the default webview implementation for $defaultTargetPlatform but there isn't a default one"); + } } - - if (defaultTargetPlatform == TargetPlatform.android) { - return WebViewAndroidImplementation(); - } - - if (defaultTargetPlatform == TargetPlatform.iOS) { - return WebViewIosImplementation(); - } - - throw UnsupportedError("Trying to use the default webview implementation for $defaultTargetPlatform but there isn't a default one"); + return _platformBuilder; } /// If not null invoked once the web view is created. @@ -255,7 +256,7 @@ class _WebViewState extends State { @override Widget build(BuildContext context) { - return WebView.implementation.build( + return WebView.platformBuilder.build( context: context, creationParams: _CreationParams.fromWidget(widget).toMap(), onWebViewCreated: _onWebViewControllerCreated, @@ -277,9 +278,9 @@ class _WebViewState extends State { (WebViewController controller) => controller._updateWidget(widget)); } - void _onWebViewControllerCreated(WebViewPlatformControllerInterface platformController) { + void _onWebViewControllerCreated(WebViewPlatform platformController) { final WebViewController controller = - WebViewController._(platformController.id, platformController, widget); + WebViewController._(platformController.id, platformController, widget); _controller.complete(controller); if (widget.onWebViewCreated != null) { widget.onWebViewCreated(controller); @@ -394,7 +395,7 @@ class WebViewController { final MethodChannel _channel; - final WebViewPlatformControllerInterface _platformInterface; + final WebViewPlatform _platformInterface; _WebSettings _settings; From 5eebd7ce54510821ae0e4e0e302880a0ff32a1da Mon Sep 17 00:00:00 2001 From: Amir Hardon Date: Tue, 21 May 2019 14:05:44 -0700 Subject: [PATCH 3/5] add unit tests --- .../lib/platform_interface.dart | 5 +- .../lib/src/webview_android.dart | 6 +- .../lib/src/webview_cupertino.dart | 6 +- .../webview_flutter/lib/webview_flutter.dart | 4 +- .../test/webview_flutter_test.dart | 94 +++++++++++++++++++ 5 files changed, 105 insertions(+), 10 deletions(-) diff --git a/packages/webview_flutter/lib/platform_interface.dart b/packages/webview_flutter/lib/platform_interface.dart index 7299d8e5a8a2..264293f5a2fc 100644 --- a/packages/webview_flutter/lib/platform_interface.dart +++ b/packages/webview_flutter/lib/platform_interface.dart @@ -29,7 +29,8 @@ abstract class WebViewPlatform { int get id; } -typedef WebViewCreatedCallback = void Function(WebViewPlatform webViewPlatform); +typedef WebViewPlatformCreatedCallback = void Function( + WebViewPlatform webViewPlatform); /// Interface for a platform specific webview implementation. /// @@ -40,7 +41,7 @@ abstract class WebViewBuilder { Widget build({ BuildContext context, Map creationParams, - WebViewCreatedCallback onWebViewCreated, + WebViewPlatformCreatedCallback onWebViewPlatformCreated, Set> gestureRecognizers, }); } diff --git a/packages/webview_flutter/lib/src/webview_android.dart b/packages/webview_flutter/lib/src/webview_android.dart index d8083e68620c..800f8125c241 100644 --- a/packages/webview_flutter/lib/src/webview_android.dart +++ b/packages/webview_flutter/lib/src/webview_android.dart @@ -15,7 +15,7 @@ class AndroidWebViewBuilder implements WebViewBuilder { Widget build( {BuildContext context, Map creationParams, - WebViewCreatedCallback onWebViewCreated, + WebViewPlatformCreatedCallback onWebViewPlatformCreated, Set> gestureRecognizers}) { return GestureDetector( // We prevent text selection by intercepting the long press event. @@ -30,10 +30,10 @@ class AndroidWebViewBuilder implements WebViewBuilder { child: AndroidView( viewType: 'plugins.flutter.io/webview', onPlatformViewCreated: (int id) { - if (onWebViewCreated == null) { + if (onWebViewPlatformCreated == null) { return; } - onWebViewCreated(MethodChannelWebViewPlatform(id)); + onWebViewPlatformCreated(MethodChannelWebViewPlatform(id)); }, gestureRecognizers: gestureRecognizers, // WebView content is not affected by the Android view's layout direction, diff --git a/packages/webview_flutter/lib/src/webview_cupertino.dart b/packages/webview_flutter/lib/src/webview_cupertino.dart index e97fbe54eb15..40499c313ae4 100644 --- a/packages/webview_flutter/lib/src/webview_cupertino.dart +++ b/packages/webview_flutter/lib/src/webview_cupertino.dart @@ -15,15 +15,15 @@ class CupertinoWebViewBuilder implements WebViewBuilder { Widget build( {BuildContext context, Map creationParams, - WebViewCreatedCallback onWebViewCreated, + WebViewPlatformCreatedCallback onWebViewPlatformCreated, Set> gestureRecognizers}) { return UiKitView( viewType: 'plugins.flutter.io/webview', onPlatformViewCreated: (int id) { - if (onWebViewCreated == null) { + if (onWebViewPlatformCreated == null) { return; } - onWebViewCreated(MethodChannelWebViewPlatform(id)); + onWebViewPlatformCreated(MethodChannelWebViewPlatform(id)); }, gestureRecognizers: gestureRecognizers, creationParams: creationParams, diff --git a/packages/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/lib/webview_flutter.dart index dd07e74cf244..c380b827c2c4 100644 --- a/packages/webview_flutter/lib/webview_flutter.dart +++ b/packages/webview_flutter/lib/webview_flutter.dart @@ -259,7 +259,7 @@ class _WebViewState extends State { return WebView.platformBuilder.build( context: context, creationParams: _CreationParams.fromWidget(widget).toMap(), - onWebViewCreated: _onWebViewControllerCreated, + onWebViewPlatformCreated: _onWebViewPlatformCreated, gestureRecognizers: widget.gestureRecognizers, ); } @@ -278,7 +278,7 @@ class _WebViewState extends State { (WebViewController controller) => controller._updateWidget(widget)); } - void _onWebViewControllerCreated(WebViewPlatform platformController) { + void _onWebViewPlatformCreated(WebViewPlatform platformController) { final WebViewController controller = WebViewController._(platformController.id, platformController, widget); _controller.complete(controller); diff --git a/packages/webview_flutter/test/webview_flutter_test.dart b/packages/webview_flutter/test/webview_flutter_test.dart index 699b8918e6f2..fd04b1b42e4b 100644 --- a/packages/webview_flutter/test/webview_flutter_test.dart +++ b/packages/webview_flutter/test/webview_flutter_test.dart @@ -6,8 +6,11 @@ import 'dart:math'; import 'dart:typed_data'; import 'package:flutter/services.dart'; +import 'package:flutter/src/foundation/basic_types.dart'; +import 'package:flutter/src/gestures/recognizer.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:webview_flutter/platform_interface.dart'; import 'package:webview_flutter/webview_flutter.dart'; typedef void VoidCallback(); @@ -747,6 +750,60 @@ void main() { expect(platformWebView.debuggingEnabled, false); }); }); + + group('Custom platform implementation', () { + setUpAll(() { + WebView.platformBuilder = MyWebViewBuilder(); + }); + tearDownAll(() { + WebView.platformBuilder = null; + }); + + testWidgets('creation', (WidgetTester tester) async { + await tester.pumpWidget( + const WebView( + initialUrl: 'https://youtube.com', + ), + ); + + final MyWebViewBuilder builder = WebView.platformBuilder; + final MyWebViewPlatform platform = builder.lastPlatformBuilt; + + expect(platform.creationParams, { + 'initialUrl': 'https://youtube.com', + 'settings': { + 'jsMode': 0, + 'hasNavigationDelegate': false, + 'debuggingEnabled': false + }, + 'javascriptChannelNames': [], + }); + }); + + testWidgets('loadUrl', (WidgetTester tester) async { + WebViewController controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://youtube.com', + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + final MyWebViewBuilder builder = WebView.platformBuilder; + final MyWebViewPlatform platform = builder.lastPlatformBuilt; + + final Map headers = { + 'header': 'value', + }; + + await controller.loadUrl('https://google.com', headers: headers); + + expect(platform.lastUrlLoaded, 'https://google.com'); + expect(platform.lastRequestHeaders, headers); + }); + }); } class FakePlatformWebView { @@ -963,3 +1020,40 @@ class _FakeCookieManager { hasCookies = true; } } + +class MyWebViewBuilder implements WebViewBuilder { + MyWebViewPlatform lastPlatformBuilt; + + @override + Widget build( + {BuildContext context, + Map creationParams, + @required WebViewPlatformCreatedCallback onWebViewPlatformCreated, + Set> gestureRecognizers}) { + assert(onWebViewPlatformCreated != null); + lastPlatformBuilt = MyWebViewPlatform(creationParams, gestureRecognizers); + onWebViewPlatformCreated(lastPlatformBuilt); + return Container(); + } +} + +class MyWebViewPlatform extends WebViewPlatform { + MyWebViewPlatform(this.creationParams, this.gestureRecognizers); + + Map creationParams; + Set> gestureRecognizers; + + String lastUrlLoaded; + Map lastRequestHeaders; + + @override + Future loadUrl(String url, Map headers) { + lastUrlLoaded = url; + lastRequestHeaders = headers; + return null; + } + + @override + // TODO: implement id + int get id => 1; +} From 3cfd04d88350a2a403fdf6ab7b3fe0675966bb76 Mon Sep 17 00:00:00 2001 From: Amir Hardon Date: Tue, 21 May 2019 14:09:36 -0700 Subject: [PATCH 4/5] update TODO --- packages/webview_flutter/lib/platform_interface.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webview_flutter/lib/platform_interface.dart b/packages/webview_flutter/lib/platform_interface.dart index 264293f5a2fc..3015560c6f2d 100644 --- a/packages/webview_flutter/lib/platform_interface.dart +++ b/packages/webview_flutter/lib/platform_interface.dart @@ -25,7 +25,7 @@ abstract class WebViewPlatform { // the discussion is more focused. // In this temporary state WebViewController still uses a method channel directly for all other // method calls so we need to expose the webview ID. - // TODO(amirh): remove this before submitting this PR(after getting an LGTM for the overall approach). + // TODO(amirh): remove this before publishing this package. int get id; } From 07d892be51e26e8907c363d6b54d1bb2fcce3870 Mon Sep 17 00:00:00 2001 From: Amir Hardon Date: Tue, 21 May 2019 14:55:16 -0700 Subject: [PATCH 5/5] add docs --- .../lib/platform_interface.dart | 39 ++++++++++++++++--- .../lib/src/webview_android.dart | 16 +++++--- .../lib/src/webview_cupertino.dart | 16 +++++--- .../webview_flutter/lib/webview_flutter.dart | 13 +++++++ .../test/webview_flutter_test.dart | 11 +++--- 5 files changed, 74 insertions(+), 21 deletions(-) diff --git a/packages/webview_flutter/lib/platform_interface.dart b/packages/webview_flutter/lib/platform_interface.dart index 3015560c6f2d..f5918d80aef6 100644 --- a/packages/webview_flutter/lib/platform_interface.dart +++ b/packages/webview_flutter/lib/platform_interface.dart @@ -8,11 +8,19 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; -/// Interface for talking to the webview controller on the platform side. +/// Interface for talking to the webview's platform implementation. /// -/// An instance implementing this interface is passed to the `onWebViewCreated` callback that is -/// passed to [WebViewPlatformInterface#onWebViewCreated]. +/// An instance implementing this interface is passed to the `onWebViewPlatformCreated` callback that is +/// passed to [WebViewPlatformBuilder#onWebViewPlatformCreated]. abstract class WebViewPlatform { + /// Loads the specified URL. + /// + /// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will + /// be added as key value pairs of HTTP headers for the request. + /// + /// `url` must not be null. + /// + /// Throws an ArgumentError if `url` is not a valid URL string. Future loadUrl( String url, Map headers, @@ -32,14 +40,33 @@ abstract class WebViewPlatform { typedef WebViewPlatformCreatedCallback = void Function( WebViewPlatform webViewPlatform); -/// Interface for a platform specific webview implementation. +/// Interface building a platform WebView implementation. /// -/// [WebView#iplatformBuilder] controls the platform interface that is used by [WebView]. -/// [WebViewAndroidImplementation] and [WebViewIosImplementation] are the default implementations +/// [WebView#platformBuilder] controls the builder that is used by [WebView]. +/// [AndroidWebViewPlatform] and [CupertinoWebViewPlatform] are the default implementations /// for Android and iOS respectively. abstract class WebViewBuilder { + /// Builds a new WebView. + /// + /// Returns a Widget tree that embeds the created webview. + /// + /// `creationParams` are the initial parameters used to setup the webview. + /// + /// `onWebViewPlatformCreated` will be invoked after the platform specific [WebViewPlatform] + /// implementation is created with the [WebViewPlatform] instance as a parameter. + /// + /// `gestureRecognizers` specifies which gestures should be consumed by the web view. + /// It is possible for other gesture recognizers to be competing with the web view on pointer + /// events, e.g if the web view is inside a [ListView] the [ListView] will want to handle + /// vertical drags. The web view will claim gestures that are recognized by any of the + /// recognizers on this list. + /// When `gestureRecognizers` is empty or null, the web view will only handle pointer events for gestures that + /// were not claimed by any other gesture recognizer. Widget build({ BuildContext context, + // TODO(amirh): convert this to be the actual parameters. + // I'm starting without it as the PR is starting to become pretty big. + // I'll followup with the conversion PR. Map creationParams, WebViewPlatformCreatedCallback onWebViewPlatformCreated, Set> gestureRecognizers, diff --git a/packages/webview_flutter/lib/src/webview_android.dart b/packages/webview_flutter/lib/src/webview_android.dart index 800f8125c241..86a20b27dcb2 100644 --- a/packages/webview_flutter/lib/src/webview_android.dart +++ b/packages/webview_flutter/lib/src/webview_android.dart @@ -10,13 +10,19 @@ import 'package:flutter/widgets.dart'; import '../platform_interface.dart'; import 'webview_method_channel.dart'; +/// Builds an Android webview. +/// +/// This is used as the default implementation for [WebView.platformBuilder] on Android. It uses +/// an [AndroidView] to embed the webview in the widget hierarchy, and uses a method channel to +/// communicate with the platform code. class AndroidWebViewBuilder implements WebViewBuilder { @override - Widget build( - {BuildContext context, - Map creationParams, - WebViewPlatformCreatedCallback onWebViewPlatformCreated, - Set> gestureRecognizers}) { + Widget build({ + BuildContext context, + Map creationParams, + WebViewPlatformCreatedCallback onWebViewPlatformCreated, + Set> gestureRecognizers, + }) { return GestureDetector( // We prevent text selection by intercepting the long press event. // This is a temporary stop gap due to issues with text selection on Android: diff --git a/packages/webview_flutter/lib/src/webview_cupertino.dart b/packages/webview_flutter/lib/src/webview_cupertino.dart index 40499c313ae4..e5487f435230 100644 --- a/packages/webview_flutter/lib/src/webview_cupertino.dart +++ b/packages/webview_flutter/lib/src/webview_cupertino.dart @@ -10,13 +10,19 @@ import 'package:flutter/widgets.dart'; import '../platform_interface.dart'; import 'webview_method_channel.dart'; +/// Builds an iOS webview. +/// +/// This is used as the default implementation for [WebView.platformBuilder] on iOS. It uses +/// a [UiKitView] to embed the webview in the widget hierarchy, and uses a method channel to +/// communicate with the platform code. class CupertinoWebViewBuilder implements WebViewBuilder { @override - Widget build( - {BuildContext context, - Map creationParams, - WebViewPlatformCreatedCallback onWebViewPlatformCreated, - Set> gestureRecognizers}) { + Widget build({ + BuildContext context, + Map creationParams, + WebViewPlatformCreatedCallback onWebViewPlatformCreated, + Set> gestureRecognizers, + }) { return UiKitView( viewType: 'plugins.flutter.io/webview', onPlatformViewCreated: (int id) { diff --git a/packages/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/lib/webview_flutter.dart index c380b827c2c4..432de6e95d5a 100644 --- a/packages/webview_flutter/lib/webview_flutter.dart +++ b/packages/webview_flutter/lib/webview_flutter.dart @@ -127,10 +127,20 @@ class WebView extends StatefulWidget { static WebViewBuilder _platformBuilder; + /// Sets a custom [WebViewBuilder]. + /// + /// This property can be set to use a custom platform implementation for WebViews. + /// + /// Setting `platformBuilder` doesn't affect [WebView]s that were already created. + /// + /// The default value is [AndroidWebViewBuilder] on Android and [CupertinoWebViewBuilder] on iOs. static set platformBuilder(WebViewBuilder platformBuilder) { _platformBuilder = platformBuilder; } + /// The [WebViewBuilder] that's used to create new [WebView]s. + /// + /// The default value is [AndroidWebViewBuilder] on Android and [CupertinoWebViewBuilder] on iOs. static WebViewBuilder get platformBuilder { if (_platformBuilder == null) { switch (defaultTargetPlatform) { @@ -437,6 +447,9 @@ class WebViewController { /// Loads the specified URL. /// + /// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will + /// be added as key value pairs of HTTP headers for the request. + /// /// `url` must not be null. /// /// Throws an ArgumentError if `url` is not a valid URL string. diff --git a/packages/webview_flutter/test/webview_flutter_test.dart b/packages/webview_flutter/test/webview_flutter_test.dart index fd04b1b42e4b..a973e43e6081 100644 --- a/packages/webview_flutter/test/webview_flutter_test.dart +++ b/packages/webview_flutter/test/webview_flutter_test.dart @@ -1025,11 +1025,12 @@ class MyWebViewBuilder implements WebViewBuilder { MyWebViewPlatform lastPlatformBuilt; @override - Widget build( - {BuildContext context, - Map creationParams, - @required WebViewPlatformCreatedCallback onWebViewPlatformCreated, - Set> gestureRecognizers}) { + Widget build({ + BuildContext context, + Map creationParams, + @required WebViewPlatformCreatedCallback onWebViewPlatformCreated, + Set> gestureRecognizers, + }) { assert(onWebViewPlatformCreated != null); lastPlatformBuilt = MyWebViewPlatform(creationParams, gestureRecognizers); onWebViewPlatformCreated(lastPlatformBuilt);