flutterwebviewandroid-softkeyboard

Flutter webview text input gets hidden by soft keyboard


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


Solution

  • 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(),
            ],
          ),
        );
      }