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

[webview_flutter] Add new entrypoint that uses hybrid composition on Android #2883

Merged
merged 12 commits into from
Sep 21, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions packages/webview_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
## 0.3.23

* Add `Webview.useExperimentalAndroidSurfaceView` flag that builds Android WebView using hybrid views.
To use this feature, set this value to `true` and have the following lines in your `android/app/src/main/AndroidManifest.xml`:
```xml
<application>
.
.
<meta-data
android:name="io.flutter.embedded_views_preview"
android:value="true" />
.
.
</application>
```

## 0.3.22+1

* Update the `setAndGetScrollPosition` to use hard coded values and add a `pumpAndSettle` call.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<!-- Hybrid composition -->
<meta-data
android:name="io.flutter.embedded_views_preview"
android:value="true" />
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
android:exported="true"
Expand Down
1 change: 1 addition & 0 deletions packages/webview_flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class _WebViewExampleState extends State<WebViewExample> {
print('Page finished loading: $url');
},
gestureNavigationEnabled: true,
useExperimentalAndroidSurfaceView: true,
);
}),
floatingActionButton: favoriteButton(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,77 @@ void main() {
});
});

group('Programmatic Scroll with hybrid view', () {
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);
},
useExperimentalAndroidSurfaceView: true,
),
),
);

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);
});
});

group('NavigationDelegate', () {
final String blankPage = "<!DOCTYPE html><head></head><body></body></html>";
final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' +
Expand Down
50 changes: 50 additions & 0 deletions packages/webview_flutter/lib/src/webview_android.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ 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';

Expand Down Expand Up @@ -58,6 +59,55 @@ class AndroidWebView implements WebViewPlatform {
);
}

// ignore: public_member_api_docs
bparrishMines marked this conversation as resolved.
Show resolved Hide resolved
Widget buildWithSurfaceView({
BuildContext context,
CreationParams creationParams,
@required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler,
WebViewPlatformCreatedCallback onWebViewPlatformCreated,
Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
}) {
bparrishMines marked this conversation as resolved.
Show resolved Hide resolved
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();
},
);
}

@override
Future<bool> clearCookies() => MethodChannelWebViewPlatform.clearCookies();
}
27 changes: 23 additions & 4 deletions packages/webview_flutter/lib/webview_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ class WebView extends StatefulWidget {
this.userAgent,
this.initialMediaPlaybackPolicy =
AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
this.useExperimentalAndroidSurfaceView = false,
}) : assert(javascriptMode != null),
assert(initialMediaPlaybackPolicy != null),
super(key: key);
Expand Down Expand Up @@ -329,6 +330,16 @@ class WebView extends StatefulWidget {
/// The default policy is [AutoMediaPlaybackPolicy.require_user_action_for_all_media_types].
final AutoMediaPlaybackPolicy initialMediaPlaybackPolicy;

/// Whether to use `AndroidViewSurface` and `SurfaceAndroidViewController` to create the widget.
///
/// This flag is temporary and is highly subject to removal. One should only
/// use this if they are willing to test [WebView] features that aren't
/// available in the standard version and acknowledges that it is possible
/// this can have slower performance and/or unpredictable bugs.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it has slower performance necessarily. It depends on what is being measured and how relevant that is.

For example, this mode makes the webview itself much faster. The Flutter UI might be slower, but only on devices below Android 10. Only while the webview is rendered, and Flutter is producing a ton of frames. e.g. animations. That's really it. For the most part, these are pretty good trade-off for webview.

I'd change this description to point out that it's an experimental feature that appends the webview to the Android view hierarchy for better performance and compatibility.

That's it for now. Let me know what you think.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, re: unpredictable bug. This is true for any new features, and it's definitely true for the current webview, which is the motivation behind this feature anyways. I think I will rephrase it as "this mode fixes many issues while interacting with the Android webview. For more, see flutter/flutter#61133"

///
/// Defaults to false.
final bool useExperimentalAndroidSurfaceView;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about a separate .dart file that has this feature on by default? This way, we have a BUILD target that automatically adds the metadata to the Android Manifest.

@mehmetf do you have any preference about how to add this feature?

Copy link
Contributor Author

@bparrishMines bparrishMines Jul 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this out, but this file has a lot of private methods/constructors that needed to be copy pasted to the new file. I went with a different approach and decided just create to a new WebViewPlatform implementation that extends AndroidWebView. So, now all a user needs to do is: WebView.platform = SurfaceAndroidWebView(). What are your thoughts on that approach?


@override
State<StatefulWidget> createState() => _WebViewState();
}
Expand All @@ -341,7 +352,17 @@ class _WebViewState extends State<WebView> {

@override
Widget build(BuildContext context) {
return WebView.platform.build(
final WebViewPlatform platform = WebView.platform;
if (widget.useExperimentalAndroidSurfaceView && platform is AndroidWebView) {
return platform.buildWithSurfaceView(
context: context,
onWebViewPlatformCreated: _onWebViewPlatformCreated,
webViewPlatformCallbacksHandler: _platformCallbacksHandler,
gestureRecognizers: widget.gestureRecognizers,
creationParams: _creationParamsfromWidget(widget),
);
}
return platform.build(
context: context,
onWebViewPlatformCreated: _onWebViewPlatformCreated,
webViewPlatformCallbacksHandler: _platformCallbacksHandler,
Expand Down Expand Up @@ -445,9 +466,7 @@ WebSettings _clearUnchangedWebSettings(

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;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/webview_flutter/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: webview_flutter
description: A Flutter plugin that provides a WebView widget on Android and iOS.
version: 0.3.22+1
version: 0.3.23
homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter

environment:
Expand Down