diff --git a/packages/webview_flutter/CHANGELOG.md b/packages/webview_flutter/CHANGELOG.md
index 139f120f12b2..6d2b4bb26815 100644
--- a/packages/webview_flutter/CHANGELOG.md
+++ b/packages/webview_flutter/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 2.0.2
+
+* Fixes bug where text fields are hidden behind the keyboard
+when hybrid composition is used [flutter/issues/75667](https://github.com/flutter/flutter/issues/75667).
+
## 2.0.1
* Run CocoaPods iOS tests in RunnerUITests target
diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
index 4578c7e0d1fe..022f1c3597e7 100644
--- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
+++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
@@ -29,7 +29,7 @@
public class FlutterWebView implements PlatformView, MethodCallHandler {
private static final String JS_CHANNEL_NAMES_FIELD = "javascriptChannelNames";
- private final InputAwareWebView webView;
+ private final WebView webView;
private final MethodChannel methodChannel;
private final FlutterWebViewClient flutterWebViewClient;
private final Handler platformThreadHandler;
@@ -92,7 +92,13 @@ public void onProgressChanged(WebView view, int progress) {
DisplayManager displayManager =
(DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
displayListenerProxy.onPreWebViewInitialization(displayManager);
- webView = new InputAwareWebView(context, containerView);
+
+ Boolean usesHybridComposition = (Boolean) params.get("usesHybridComposition");
+ webView =
+ (usesHybridComposition)
+ ? new WebView(context)
+ : new InputAwareWebView(context, containerView);
+
displayListenerProxy.onPostWebViewInitialization(displayManager);
platformThreadHandler = new Handler(context.getMainLooper());
@@ -140,7 +146,9 @@ public View getView() {
// of Flutter but used as an override anyway wherever it's actually defined.
// TODO(mklim): Add the @Override annotation once flutter/engine#9727 rolls to stable.
public void onInputConnectionUnlocked() {
- webView.unlockInputConnection();
+ if (webView instanceof InputAwareWebView) {
+ ((InputAwareWebView) webView).unlockInputConnection();
+ }
}
// @Override
@@ -150,7 +158,9 @@ public void onInputConnectionUnlocked() {
// of Flutter but used as an override anyway wherever it's actually defined.
// TODO(mklim): Add the @Override annotation once flutter/engine#9727 rolls to stable.
public void onInputConnectionLocked() {
- webView.lockInputConnection();
+ if (webView instanceof InputAwareWebView) {
+ ((InputAwareWebView) webView).lockInputConnection();
+ }
}
// @Override
@@ -160,7 +170,9 @@ public void onInputConnectionLocked() {
// of Flutter but used as an override anyway wherever it's actually defined.
// TODO(mklim): Add the @Override annotation once stable passes v1.10.9.
public void onFlutterViewAttached(View flutterView) {
- webView.setContainerView(flutterView);
+ if (webView instanceof InputAwareWebView) {
+ ((InputAwareWebView) webView).setContainerView(flutterView);
+ }
}
// @Override
@@ -170,7 +182,9 @@ public void onFlutterViewAttached(View flutterView) {
// of Flutter but used as an override anyway wherever it's actually defined.
// TODO(mklim): Add the @Override annotation once stable passes v1.10.9.
public void onFlutterViewDetached() {
- webView.setContainerView(null);
+ if (webView instanceof InputAwareWebView) {
+ ((InputAwareWebView) webView).setContainerView(null);
+ }
}
@Override
@@ -425,7 +439,9 @@ private void updateUserAgent(String userAgent) {
@Override
public void dispose() {
methodChannel.setMethodCallHandler(null);
- webView.dispose();
+ if (webView instanceof InputAwareWebView) {
+ ((InputAwareWebView) webView).dispose();
+ }
webView.destroy();
}
}
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 50af77fe6c6e..beabe579de23 100644
--- a/packages/webview_flutter/example/integration_test/webview_flutter_test.dart
+++ b/packages/webview_flutter/example/integration_test/webview_flutter_test.dart
@@ -899,6 +899,111 @@ void main() {
expect(X_SCROLL * 2, scrollPosX);
expect(Y_SCROLL * 2, scrollPosY);
});
+
+ testWidgets('inputs are scrolled into view when focused',
+ (WidgetTester tester) async {
+ final String scrollTestPage = '''
+
+
+
+
+
+
+
+
+
+
+ ''';
+
+ final String scrollTestPageBase64 =
+ base64Encode(const Utf8Encoder().convert(scrollTestPage));
+
+ final Completer pageLoaded = Completer();
+ final Completer controllerCompleter =
+ Completer();
+
+ await tester.runAsync(() async {
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: SizedBox(
+ width: 200,
+ height: 200,
+ child: WebView(
+ initialUrl:
+ 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64',
+ onWebViewCreated: (WebViewController controller) {
+ controllerCompleter.complete(controller);
+ },
+ onPageFinished: (String url) {
+ pageLoaded.complete(null);
+ },
+ javascriptMode: JavascriptMode.unrestricted,
+ ),
+ ),
+ ),
+ );
+ await Future.delayed(Duration(milliseconds: 20));
+ await tester.pump();
+ });
+
+ final WebViewController controller = await controllerCompleter.future;
+ await pageLoaded.future;
+ final String viewportRectJSON = await _evaluateJavascript(
+ controller, 'JSON.stringify(viewport.getBoundingClientRect())');
+ final Map viewportRectRelativeToViewport =
+ jsonDecode(viewportRectJSON);
+
+ // Check that the input is originally outside of the viewport.
+
+ final String initialInputClientRectJSON = await _evaluateJavascript(
+ controller, 'JSON.stringify(inputEl.getBoundingClientRect())');
+ final Map initialInputClientRectRelativeToViewport =
+ jsonDecode(initialInputClientRectJSON);
+
+ expect(
+ initialInputClientRectRelativeToViewport['bottom'] <=
+ viewportRectRelativeToViewport['bottom'],
+ isFalse);
+
+ await controller.evaluateJavascript('inputEl.focus()');
+
+ // Check that focusing the input brought it into view.
+
+ final String lastInputClientRectJSON = await _evaluateJavascript(
+ controller, 'JSON.stringify(inputEl.getBoundingClientRect())');
+ final Map lastInputClientRectRelativeToViewport =
+ jsonDecode(lastInputClientRectJSON);
+
+ expect(
+ lastInputClientRectRelativeToViewport['top'] >=
+ viewportRectRelativeToViewport['top'],
+ isTrue);
+ expect(
+ lastInputClientRectRelativeToViewport['bottom'] <=
+ viewportRectRelativeToViewport['bottom'],
+ isTrue);
+
+ expect(
+ lastInputClientRectRelativeToViewport['left'] >=
+ viewportRectRelativeToViewport['left'],
+ isTrue);
+ expect(
+ lastInputClientRectRelativeToViewport['right'] <=
+ viewportRectRelativeToViewport['right'],
+ isTrue);
+ });
}, skip: !Platform.isAndroid);
group('NavigationDelegate', () {
@@ -966,7 +1071,8 @@ void main() {
expect(error.failingUrl, isNull);
} else if (Platform.isAndroid) {
expect(error.errorType, isNotNull);
- expect(error.failingUrl, 'https://www.notawebsite..com');
+ expect(error.failingUrl.startsWith('https://www.notawebsite..com'),
+ isTrue);
}
});
@@ -1236,9 +1342,13 @@ String _webviewBool(bool value) {
/// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests.
Future _getUserAgent(WebViewController controller) async {
+ return _evaluateJavascript(controller, 'navigator.userAgent;');
+}
+
+Future _evaluateJavascript(
+ WebViewController controller, String js) async {
if (defaultTargetPlatform == TargetPlatform.iOS) {
- return await controller.evaluateJavascript('navigator.userAgent;');
+ return await controller.evaluateJavascript(js);
}
- return jsonDecode(
- await controller.evaluateJavascript('navigator.userAgent;'));
+ return jsonDecode(await controller.evaluateJavascript(js));
}
diff --git a/packages/webview_flutter/lib/src/webview_method_channel.dart b/packages/webview_flutter/lib/src/webview_method_channel.dart
index ef1ed51835b8..e26604f74628 100644
--- a/packages/webview_flutter/lib/src/webview_method_channel.dart
+++ b/packages/webview_flutter/lib/src/webview_method_channel.dart
@@ -201,13 +201,16 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController {
/// This is used for the `creationParams` argument of the platform views created by
/// [AndroidWebViewBuilder] and [CupertinoWebViewBuilder].
static Map creationParamsToMap(
- CreationParams creationParams) {
+ CreationParams creationParams, {
+ bool usesHybridComposition = false,
+ }) {
return {
'initialUrl': creationParams.initialUrl,
'settings': _webSettingsToMap(creationParams.webSettings),
'javascriptChannelNames': creationParams.javascriptChannelNames.toList(),
'userAgent': creationParams.userAgent,
'autoMediaPlaybackPolicy': creationParams.autoMediaPlaybackPolicy.index,
+ 'usesHybridComposition': usesHybridComposition,
};
}
}
diff --git a/packages/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/lib/webview_flutter.dart
index 7e4f3d6ac079..22dd00e485c4 100644
--- a/packages/webview_flutter/lib/webview_flutter.dart
+++ b/packages/webview_flutter/lib/webview_flutter.dart
@@ -109,6 +109,7 @@ class SurfaceAndroidWebView extends AndroidWebView {
layoutDirection: TextDirection.rtl,
creationParams: MethodChannelWebViewPlatform.creationParamsToMap(
creationParams,
+ usesHybridComposition: true,
),
creationParamsCodec: const StandardMessageCodec(),
)
diff --git a/packages/webview_flutter/pubspec.yaml b/packages/webview_flutter/pubspec.yaml
index 0640386c517d..6ee9e119bd3a 100644
--- a/packages/webview_flutter/pubspec.yaml
+++ b/packages/webview_flutter/pubspec.yaml
@@ -1,7 +1,7 @@
name: webview_flutter
description: A Flutter plugin that provides a WebView widget on Android and iOS.
homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter
-version: 2.0.1
+version: 2.0.2
environment:
sdk: ">=2.12.0-259.9.beta <3.0.0"