Flutter webview text input gets hidden by soft keyboard
Asked Answered
E

6

5

I'm testing on Android (I'll verify it's the same on iOS also).

My issue is that when I have a webview showing a stripe checkout page, and I tap a text entry there to enter something near the bottom (zipcode) then the virtual keyboard covers the webview and I'm NOT able to scroll up even in the webview.

It appears that the webview takes up the whole screen as expected, but when a soft keyboard comes up Flutter I think usually makes space for it (shrinks widgets showing on the screen). Webview appears to just stay the same size.

I tried a hack of putting the web view in a container() with dynamic height myself. It sorta works. The code is below, and the key line is this height of the Container():

height: MediaQuery.of(context).size.height * (MediaQuery.of(context).viewInsets.bottom != 0 ? .7 : 1)

But this has issues with confusing the keyboard. It somehow tricks the keyboard to NOT be digit entry type for zip code for example. It looks like it tries, but repaints to non digit keyboard after a split second.

Why does Webview not respect the soft keyboard on phones?

Here is my build in the stateful widget:

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Stack(
        children: [
          initialUrl == null
              ? Container()
              : Container(
                  height: MediaQuery.of(context).size.height * (MediaQuery.of(context).viewInsets.bottom != 0 ? .7 : 1),
                  child: WebView(
                    initialUrl: initialUrl,
                    javascriptMode: JavascriptMode.unrestricted,
                    onPageStarted: (controller) {},
                    onPageFinished: (controller) {
                      Future.delayed(Duration(milliseconds: 1234), () {
                        if (mounted) {
                          showLoading = false;
                          setState(() {});
                        }
                      });
                    },
                    navigationDelegate: (NavigationRequest request) {
                      if (request.url.startsWith('https://example.com/success')) {
                        Navigator.of(context).pop('success');
                      } else if (request.url.startsWith('https://example.com/cancel')) {
                        Navigator.of(context).pop('cancel');
                      }
                      return NavigationDecision.navigate;
                    },
                  ),
                ),
          showLoading == true
              ? Center(
                  child: Container(width: 80, height: 80, child: CircularProgressIndicator()),
                )
              : Container(),
        ],
      ),
    );
  }

Here are screen shots. Note in the keyboard one you can NOT even scroll the webview to see the zip you're typing...

enter image description here enter image description here

Entente answered 27/4, 2021 at 15:42 Comment(0)
E
11

The answer for me was two things.

First to use this line which sets WebView.platform inside the stateful widget showing the webview. Notice that it's specific to my testing at the time on Android, so perhaps for some of you when you didn't see the issue, you maybe were on iOS?

  @override
  void initState() {
    super.initState();
    if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView(); // <<== THIS
  }

Second I added a Scaffold with resizeToAvoidBottomInset set to true and removed my use of this:

height: MediaQuery.of(context).size.height * (MediaQuery.of(context).viewInsets.bottom != 0 ? .7 : 1),`

Here is the code for the body with the webview

@override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Stack(
        children: [
          initialUrl == null
              ? Container()
              : Scaffold(
                resizeToAvoidBottomInset: true,
                body: WebView(
                  initialUrl: initialUrl,
                  javascriptMode: JavascriptMode.unrestricted,
                  onPageStarted: (controller) {},
                  onPageFinished: (controller) {
                    Future.delayed(Duration(milliseconds: 1234), () {
                      if (mounted) {
                        showLoading = false;
                        setState(() {});
                      }
                    });
                  },
                  navigationDelegate: (NavigationRequest request) {
                    if (request.url.startsWith('https://example.com/success')) {
                      Navigator.of(context).pop('success');
                    } else if (request.url.startsWith('https://example.com/cancel')) {
                      Navigator.of(context).pop('cancel');
                    }
                    return NavigationDecision.navigate;
                  },
                ),
              ),
          showLoading == true
              ? Center(
                  child: Container(width: 80, height: 80, child: CircularProgressIndicator()),
                )
              : Container(),
        ],
      ),
    );
  }
Entente answered 28/5, 2021 at 13:20 Comment(0)
A
5

Here a simpler approach by using the 'gestureRecognizers' property to move the scroll gesture to the Webview itself:

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Webview Example',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Webview Example'),
        ),
        body: Stack(
          children: [
            //... widgets that render behind the column
            WebView(
              initialUrl: 'https://inputtypes.com',
              javascriptMode: JavascriptMode.unrestricted,
              gestureRecognizers: Set()
                ..add(
                  Factory<DragGestureRecognizer>(
                    () => VerticalDragGestureRecognizer(),
                  ),
                ),
            ),
          ],
        ),
      ),
    );
  }
}

Try it out and see if it solves your issue

Anecdotic answered 28/4, 2021 at 19:48 Comment(0)
A
0

Have you tried wrapping your widget with a SingleChildScrollView?

Anecdotic answered 27/4, 2021 at 16:7 Comment(4)
Yes, I tried that. It causes a render issue since the webview was infinitely (un) constrained. I guess I could try putting it into a container the size of the screen, and then in a SingeChildScrollView.Entente
perhaps you could also use a column instead of a container with a specific height.Anecdotic
Unfortunately, it doesn't help. And also there is an overflow by 31 pixels which my guess is caused by the SafeArea().Entente
it worked for me, I posted the code as an answer for better code formatting ;)Anecdotic
A
0

This worked for me:

Scaffold(
      body: Stack(
        children: [
          //... widgets that render behind the column
          Align(
            alignment: Alignment.center,
            child: SingleChildScrollView( 
              child: Column(
                 children: [
                    //... login form inputs and buttons
                 ]
              ),
            ),
          ),
        ],
      ),
    );
Anecdotic answered 27/4, 2021 at 18:31 Comment(1)
You tried this with a Webview? What website did you point it to? Can you show a image of the web with the keyboard showing and not showing so I can see it working?Entente
M
0

Scaffold + the WebViewWidget did the trick for me:

return Scaffold(
  body: WebViewWidget(controller: controller),
);
Mikael answered 7/8, 2024 at 4:52 Comment(0)
I
0

here is very simple solution that can be implimented via 2 simple ways

Method 1 - Direct Javacript

  window.addEventListener('focusin', (event) => {
      setTimeout(() => {
        event.target.scrollIntoView({ behavior: 'smooth', block: 'center' });
      }, 300);
    });

whenever user taps on a input which triggers focusin event input will be automatially center to the screen with a smooth scroll effect.

Above method is good when you have access to html code you can simply add it on header or anywhere within document. Method 2 is not required in that case.

Method 2 - Execute same script via flutter when page load finishes

Note:- Make sure you run this script when page is loaded completly if you execute it before or during the page load it won't work.

Use this method if you don't have acess to html code or don't want to put it on html code , then webviewcontroller will inject this code.

in my case i am using WebviewController to control the webview

 webViewController.runJavaScript('''
      window.addEventListener('focusin', (event) => {
         setTimeout(() => {
          event.target.scrollIntoView({ behavior: 'smooth', block: 'center' });
        }, 300);
      });         
  ''');

Also using resizeToAvoidBottomInset with scaffold would be cherry on top.

Scaffold(
       resizeToAvoidBottomInset: true,  // <-- like this
       body : WebViewWidget()
);
Inanition answered 29/8, 2024 at 14:39 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.