flutter: webview does not allow jumping to anchors in the same html page

1,584

Unfortunately this doesn't seem to be fixed by now. The best workaround I found was to create local HttpServer.

For simplicity - I used local_assets_server package, which basically wraps around HttpServer and simplifies serving bundled resources. But it is extremely easy to implement this functionality directly using HttpServer, especially if you use this solely for serving static HTML content. Here is my implementation using local_assets_server:

class HelpScreen extends StatefulWidget {
  final String? tag;

  HelpScreen({this.tag});

  @override
  _HelpScreenState createState() => _HelpScreenState();
}

class _HelpScreenState extends State<HelpScreen> {
  late LocalAssetsServer _webServer;
  late int _webServerPort;
  bool _webServerListening = false;

  @override
  void initState() {
    _startServer();

    if (Platform.isAndroid) {
      WebView.platform = SurfaceAndroidWebView();
    }

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: () {
            if (_webServerListening) {
              return WebView(
                initialUrl: 'http://localhost:$_webServerPort/help.html#${this.widget.tag ?? ""}',
                javascriptMode: JavascriptMode.unrestricted,
              );
            } else {
              return Center(
                child: CircularProgressIndicator(),
              );
            }
        });
  }

  _startServer() async {
    _webServer = new LocalAssetsServer(
      address: InternetAddress.loopbackIPv4,
      assetsBasePath: 'assets/html',
    );

    await _webServer.serve();

    setState(() {
      _webServerPort = _webServer.boundPort;
      _webServerListening = true;
    });
  }
}

Make sure you allow local HTTP networking on Android by creating/updating android/app/src/main/res/xml/network_security_config.xml:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="false">localhost</domain>
    </domain-config>
</network-security-config>

... and then referencing it from android/app/src/main/AndroidManifest.xml

<manifest ...>
    ...
    <application
        ...
        android:networkSecurityConfig="@xml/network_security_config"
        ...>

        ...
    </application>
</manifest>

... and on iOS in ios/Runner/Info.plist:

    <key>NSAppTransportSecurity</key>    
    <dict>
        <key>NSAllowsLocalNetworking</key>
        <true/>
    </dict>
Share:
1,584
kakyo
Author by

kakyo

The more you learn, the more you hesitate.

Updated on December 24, 2022

Comments

  • kakyo
    kakyo over 1 year

    I've bumped into an annoying flutter issue where HTML anchors don't work in the WebView.

    The full issue is here: https://github.com/flutter/flutter/issues/65379

    Steps to Reproduce

    I'm using webview to show localized html files. In each HTML file there are href links to redirect to corresponding language files. I'm using this little class below to show the HTML as a page.

    with this little class
    class LocalLoader {
      Future<String> loadLocal(String filename) async {
        return await rootBundle.loadString('assets/$filename');
      }
    }
    class HtmlPage extends StatelessWidget {
      final Completer<WebViewController> controller;
      final String htmlFile;
      HtmlPage({
        @required this.htmlFile,
        @required this.controller,
      });
      @override
      Widget build(BuildContext context) {
        return Container(
          child: FutureBuilder<String>(
            future: LocalLoader().loadLocal(htmlFile),
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return WebView(
                  debuggingEnabled: true,
                  initialUrl: Uri.dataFromString(snapshot.data,
                      mimeType: 'text/html',
                      // CAUTION
                      // - required for non-ascii chars
                      encoding: Encoding.getByName("UTF-8")
                  ).toString(),
                  javascriptMode: JavascriptMode.unrestricted,
                  onWebViewCreated: (WebViewController webViewController) {
                    // CAUTION
                    // - avoid exception "Bad state: Future already completed"
                    if(!controller.isCompleted) {
                      controller.complete(webViewController);
                    }
                  },
                );
              } else if (snapshot.hasError) {
                return Text("${snapshot.error}");
              } else {
                print('undefined behaviour');
              }
              return CircularProgressIndicator();
            },
          ),);
      }
    }
    class HelpPage extends HtmlPage {
      HelpPage(ctrl) : super(htmlFile: 'help_en.html', controller: ctrl);
    }
    
    class AboutPage extends HtmlPage {
      AboutPage(ctrl) : super(htmlFile: 'about_en.html', controller: ctrl);
    }
    
    

    And the relevant html code looks like this in each language files

    <p><span style="font-size: 20px;"><a href="help_en.html">English</a></span> <span class="math inline">    </span> <span style="font-size: 20px;"><a href="help_fr.html">Français</a></span></p>
    

    Repro 1

    Open my app

    1. Place help_en.html and help_fr.html in assets folder, and add the corresponding yaml section.
    2. Load the initial page "help_en.html" and it shows correctly
    3. Click the Français link on the page

    Expected results:

    We should see the help_fr.html upon clicking its redirection link.

    Actual results:

    We see a blank page

    Repro 2

    I suspected it may have been a relative path error. So I modified the HTML page so that the href looks like

    <p><span style="font-size: 20px;"><a href="assets/help_en.html">English</a></span> <span class="math inline">    </span> <span style="font-size: 20px;"><a href="assets/help_fr.html">Français</a></span></p>
    

    But the result is the same as in Repro 1.

    Logs
    E/InputMethodManager(24228): b/117267690: Failed to get fallback IMM with expected displayId=215 actual IMM#displayId=0 view=io.flutter.plugins.webviewflutter.InputAwareWebView{f259a5f VFEDHVC.. .F...... 0,0-2075,706}
    E/InputMethodManager(24228): b/117267690: Failed to get fallback IMM with expected displayId=215 actual IMM#displayId=0 view=io.flutter.plugins.webviewflutter.InputAwareWebView{f259a5f VFEDHVC.. .F...... 0,0-2075,706}
    
    

    No flutter analyze errors

    $ flutter doctor -v
    [✓] Flutter (Channel dev, 1.22.0-9.0.pre, on Mac OS X 10.15.6 19G2021, locale en-CN)
        • Flutter version 1.22.0-9.0.pre at /Applications/flutter
        • Framework revision 7a43175198 (10 days ago), 2020-08-28 23:18:04 -0400
        • Engine revision 07e2520d5d
        • Dart version 2.10.0 (build 2.10.0-73.0.dev)
        • Pub download mirror https://pub.flutter-io.cn
        • Flutter download mirror https://storage.flutter-io.cn
    
     
    [✓] Android toolchain - develop for Android devices (Android SDK version 30.0.0)
        • Android SDK at /Applications/Android/sdk
        • Platform android-30, build-tools 30.0.0
        • ANDROID_HOME = /Applications/Android/sdk
        • ANDROID_SDK_ROOT = /Applications/Android/sdk
        • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
        • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)
        • All Android licenses accepted.
    
    [✓] Xcode - develop for iOS and macOS (Xcode 11.7)
        • Xcode at /Applications/Xcode.app/Contents/Developer
        • Xcode 11.7, Build version 11E801a
        • CocoaPods version 1.9.1
    
    [✓] Android Studio (version 4.0)
        • Android Studio at /Applications/Android Studio.app/Contents
        • Flutter plugin version 49.0.2
        • Dart plugin version 193.7547
        • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)
    
    [✓] VS Code (version 1.48.2)
        • VS Code at /Applications/Visual Studio Code.app/Contents
        • Flutter extension version 3.13.2
    
    [✓] Connected device (2 available)
        • ONEPLUS A6000 (mobile)   • <my udid>                                 • android-arm64 • Android 10 (API 29)
        • MyiPhone (mobile) •<my udid> • ios           • iOS 13.6.1
        ! Device emulator-5554 is offline.
    
    • No issues found!
    

    UPDATE

    I dug further and thought this might be related to #27086, but it turned out to be even worse, see below:

    Attempt

    To work around this issue, I thought about merging help_en.html and help_fr.html into a single html file and turn the href's into anchors so that I can bypass the local assets reference issue by jumping within the same html file.

    In theory, this would work.

    Trouble

    However, to my surprise, this didn't work either! I still see blank pages when trying to go to the anchors. The same anchors work fine in an Electron test app and in the browsers.

    This time I got an error in Android Studio's console

    E/InputMethodManager(28987): b/117267690: Failed to get fallback IMM with expected displayId=259 actual IMM#displayId=0 view=io.flutter.plugins.webviewflutter.InputAwareWebView{b193667 VFEDHVC.. .F...... 0,0-2075,706}
    I/chromium(28987): [INFO:CONSOLE(0)] "Not allowed to navigate top frame to data URL: data:text/html;charset=utf-8,%3C!DOCTYPE%20html%3E%0A%3Chtml%20xmlns=%22http://www.w3.org/1999/xhtml%22%20lang=%22%22%20xml:lang=%22%22%3E%0A%3Chead%3E%0A%20%20%3Cmeta%20charset=%22utf-8%22%20/%3E%0A%20%20%3Cmeta%20name=%22generator%22%20content=%22pandoc%22%20/%3E%0A%20%20%3Cmeta%20name=%22viewport%22%20content=%22width=device-width,%20initial-scale=1.0,%20user-scalable=yes%22%20/%3E%0A%20%20%3Ctitle%3E%20%3C/title%3E%0A%20%20%3Cstyle%3E%0A%20%20%20%20code%7Bwhite-space:%20pre-wrap;%7D%0A%20%20%20%20span....E4%BA%8E%3C/h1%3E%0A%3Ch3%20id=%22section%22%3E%3C/h3%3E%0A%3Ch2%20id=%22%E5%8A%A8%E5%90%AC-version-1.0%22%3E%E5%8A%A8%E5%90%AC%20version%201.0%3C/h2%3E%0A%3Cp%3ECopyright%C2%A9%202020%20NExT%20%E5%B7%A5%E4%BD%9C%E5%AE%A4%E7%BE%A4%3C/p%3E%0A%3Cp%3E%E7%89%88%E6%9D%83%E6%89%80%E6%9C%89%3C/p%3E%0A%3Cp%3E%E9%9A%90%E7%A7%81%E6%94%BF%E7%AD%96%3Cspan%20class=%22math%20inline%22%3E%C2%A0%C2%A0%C2%A0%C2%A0%3C/span%3E%E7%94%A8%E6%88%B7%E6%9C%8D%E5%8A%A1%E5%8D%8F%E8%AE%AE%3C/p%3E%0A%3C/body%3E%0A%3C/html%3E%0A#helpen", source: data:text/html;charset=utf-8,%3C!DOCTYPE%20html%3E%0A%3Chtml%20xmlns=%22http://www.w3.org/1999/xhtml%22%20lang=%22%22%20xml:lang=%22%22%3E%0A%3Chead%3E%0A%20%20%3Cmeta%20charset=%22utf-8%22%20/%3E%0A%20%20%3Cmeta%20name=%22generator%22%20content=%22pandoc%22%20/%3E%0A%20%20%3Cmeta%20name=%22viewport%22%20content=%22width=device-width,%20initial-scale=1.0,%20user-scalable=yes%22%20/%3E%0A%20%20%3Ctitle%3E%20%3C/title%3E%0A%20%20%3Cstyle%3E%0A%20%20%20%20code%7Bwhite-space:%20pre-wrap;%7D%0A%20%20%20%20span.smallcaps%7Bfont-variant:%20small-caps;%7D%0A%20%20%20%20span.underline%7Btext-decoration:%20underline;%7D%0A%20%20%20%20div.column%7Bdisplay:%20inline-block;%20vertical-align:%20top;%20width:%2050%25;%7D%0A%20%20%20%20div.hanging-indent%7Bmargin-left:%201.5em;%20text-indent:%20-1.5em;%7D%0A%20%20%3C/style%3E%0A%20%20%3C!--%5Bif%20lt%20IE%209%5D%3E%0A%20%20%20%20%3Cscript%20src=%22//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js%22%3E%3C/script%3E%0A%20%20%3C!%5Bendif%5D--%3E%0A%20%20%3Cscript%20type=%22text/javascript%22%20src=%22help_lang.js%22%20type=%22module%22%3E%3C/script%3E%0A%20%20%3Cstyle%3E%0A%20%20/*%0A%20%20%20%20CAUTION:%20must%20use%20embedded%20style%20for%20standalone%20html%20with%20-s%0A%20%20*/%0A%20%20/***************%0A%20%20Global%0A%20%20***************/%0A%20%20/*%0A%20%20%20*%20I%20add%20this%20to%20html%20files%20generated%20with%20pandoc.%0A%20%20%20*/%0A%20%20html%20%7B%0A%20%20font-size:%20100%25;%0A%20%20overflow-y:%20scroll;%0A%20%20-webkit-text-size-adjust:%20100%25;%0A%20%20-ms-text-size-adjust:%20100%25;%0A%0A%20%20/*--scrollbarBG:%20%23CFD8DC;*/%0A%20%20--scrollbarBG:%20%232a2f39;%0A%20%20--thumbBG:%20%2390a4ae;%0A%20%20/*CAUTION:%20required%20for%20background%20parallax%20*/%0A%20%20height:%20100%25;%0A%20%20/*CAUTION%20end:%20required%20for%20background%20parallax%20*/%0A%20%20%7D%0A%20%20body%20%7B%0A%20%20color:%20%23e5e5e5;%0A%20%20font-family:%20Arial,%20Helvetica,%20sans-serif;%0A%20%20font-size:%2012px;%0A%20%20line-height:%201.7;%0A%20%20padding:%201em;%0A%20%20margin:%20auto;%0A%20%20max-width:%2042em;%0A%20%20/*CAUTION:%20required%20for%20background%20parallax%20*/%0A%20%20background:%20url('images/bg-logo.png')%20no-repeat%20top%20right,%20%232a2f39;%0A%20%20height:%20100%25;%0A%20%20background-attachment:%20fixed;%0A%20%20background-position:%20center;%0A%20%20background-repeat:%20no-repeat;%0A%20%20background-size:%20cover;%0A%20%20/*CAUTION%20end:%20required%20for%20background%20parallax*/%0A%20%20%7D%0A%20%20a%20%7B%0A%20%20color:%20%237c8694;%0A%20%20text-decoration:%20none;%0A%20%20%7D%0A%20%20a:visited%20%7B%0A%20%20color:%20%230072ae;%0A%20%20%7D%0A%20%20a:hover%20%7B%0A%20%20color:%20%2306e;%0A%20%20%7D%0A%20%20a:active%20%7B%0A%20%20color:%20%23faa700;%0A%20%20%7D%0A%20%20a:focus%20%7B%0A%20%20outline:%20thin%20dotted;%0A%20%20%7D%0A%20%20*::-moz-selection%20%7B%0A%20%20background:%20rgba(255,%20255,%200,%200.3);%
    E/InputMethodManager(28987): b/117267690: Failed to get fallback IMM with expected displayId=259 actual IMM#displayId=0 view=io.flutter.plugins.webviewflutter.InputAwareWebView{b193667 VFEDHVC.. .F...... 0,0-2075,706}
    
    

    Verdict

    So to me this is beyond the fuss of running an http server to work around a very simple webpage doc. It is actually a much more serious bug to me.

    • Alessandro
      Alessandro over 3 years
      Any workaround? :(
    • kakyo
      kakyo over 3 years
      Sorry, haven't found one. Looks like the web part of flutter has a long way to go.