Search code examples
flutterwebviewflutter-dependencies

Using flutter webview as home and pressing back button closes Application


I'm trying to use a webview in flutter as my home page for my application. Everything loads just fine, however hitting the back button doesn't send me to the previous web page in the webview, it just exits the application.

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:url_launcher/url_launcher.dart';

class _StudentPortalState extends State<StudentPortal>{

  _launchURL(url) async {

  if (await canLaunch(url)) {
    await launch(url);
  } else {
    throw 'Could not launch $url';
  }
}
  Widget build (BuildContext context){
    return Scaffold(
      body: WebView(
              javascriptMode: JavascriptMode.unrestricted,
              initialUrl: 'https://sites.google.com/ttmsa.org/ttmsa/home',

              navigationDelegate: (NavigationRequest request) {

                if(request.url.contains("intent:")) {

                  _launchURL('https://forms.gle/XfNbn1Ph9xFdUaKeA');
                  return NavigationDecision.prevent;
                }
                return NavigationDecision.navigate;
              },   
      ),

    );
  }
}`

Solution

  • Wrap Scaffold with WillPopScope and when user click device back button execute WebView Controller goback

    code snippet onwillpop

        @override
      Widget build(BuildContext context) {
        return WillPopScope(
          onWillPop: () => _exitApp(context),
          child: Scaffold(
            appBar: AppBar(
              title: const Text('Flutter WebView example'),
              // This drop down menu demonstrates that Flutter widgets can be shown over the web view.
              actions: <Widget>[
                NavigationControls(_controller.future),
                SampleMenu(_controller.future),
              ],
            ),
    

    code snippet for exit app

    WebViewController controllerGlobal;
    
    Future<bool> _exitApp(BuildContext context) async {
      if (await controllerGlobal.canGoBack()) {
        print("onwill goback");
        controllerGlobal.goBack();
        return Future.value(true);
      } else {
        Scaffold.of(context).showSnackBar(
          const SnackBar(content: Text("No back history item")),
        );
        return Future.value(false);
      }
    }
    

    full code

    // Copyright 2018 The Chromium Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    
    import 'dart:async';
    import 'dart:convert';
    import 'package:flutter/material.dart';
    import 'package:webview_flutter/webview_flutter.dart';
    
    void main() => runApp(MaterialApp(home: WebViewExample()));
    
    const String kNavigationExamplePage = '''
    <!DOCTYPE html><html>
    <head><title>Navigation Delegate Example</title></head>
    <body>
    <p>
    The navigation delegate is set to block navigation to the youtube website.
    </p>
    <ul>
    <ul><a href="https://www.youtube.com/">https://www.youtube.com/</a></ul>
    <ul><a href="https://www.google.com/">https://www.google.com/</a></ul>
    <ul><a href="https://www.google.com/">https://nodejs.org/en</a></ul>
    </ul>
    </body>
    </html>
    ''';
    
    class WebViewExample extends StatefulWidget {
      @override
      _WebViewExampleState createState() => _WebViewExampleState();
    }
    
    WebViewController controllerGlobal;
    
    Future<bool> _exitApp(BuildContext context) async {
      if (await controllerGlobal.canGoBack()) {
        print("onwill goback");
        controllerGlobal.goBack();
      } else {
        Scaffold.of(context).showSnackBar(
          const SnackBar(content: Text("No back history item")),
        );
        return Future.value(false);
      }
    }
    
    class _WebViewExampleState extends State<WebViewExample> {
      final Completer<WebViewController> _controller =
      Completer<WebViewController>();
    
      @override
      Widget build(BuildContext context) {
        return WillPopScope(
          onWillPop: () => _exitApp(context),
          child: Scaffold(
            appBar: AppBar(
              title: const Text('Flutter WebView example'),
              // This drop down menu demonstrates that Flutter widgets can be shown over the web view.
              actions: <Widget>[
                NavigationControls(_controller.future),
                SampleMenu(_controller.future),
              ],
            ),
            // We're using a Builder here so we have a context that is below the Scaffold
            // to allow calling Scaffold.of(context) so we can show a snackbar.
            body: Builder(builder: (BuildContext context) {
              return WebView(
                initialUrl: 'https://flutter.dev',
                javascriptMode: JavascriptMode.unrestricted,
                onWebViewCreated: (WebViewController webViewController) {
                  _controller.complete(webViewController);
                },
                // TODO(iskakaushik): Remove this when collection literals makes it to stable.
                // ignore: prefer_collection_literals
                javascriptChannels: <JavascriptChannel>[
                  _toasterJavascriptChannel(context),
                ].toSet(),
                navigationDelegate: (NavigationRequest request) {
                  if (request.url.startsWith('https://www.youtube.com/')) {
                    print('blocking navigation to $request}');
                    return NavigationDecision.prevent;
                  }
                  if (request.url.startsWith('https://flutter.dev/docs')) {
                    print('blocking navigation to $request}');
                    return NavigationDecision.prevent;
                  }
                  print('allowing navigation to $request');
                  return NavigationDecision.navigate;
                },
                onPageFinished: (String url) {
                  print('Page finished loading: $url');
                },
              );
            }),
            floatingActionButton: favoriteButton(),
          ),
        );
      }
    
      JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
        return JavascriptChannel(
            name: 'Toaster',
            onMessageReceived: (JavascriptMessage message) {
              Scaffold.of(context).showSnackBar(
                SnackBar(content: Text(message.message)),
              );
            });
      }
    
      Widget favoriteButton() {
        return FutureBuilder<WebViewController>(
            future: _controller.future,
            builder: (BuildContext context,
                AsyncSnapshot<WebViewController> controller) {
              if (controller.hasData) {
                return FloatingActionButton(
                  onPressed: () async {
                    final String url = await controller.data.currentUrl();
                    Scaffold.of(context).showSnackBar(
                      SnackBar(content: Text('Favorited $url')),
                    );
                  },
                  child: const Icon(Icons.favorite),
                );
              }
              return Container();
            });
      }
    }
    
    enum MenuOptions {
      showUserAgent,
      listCookies,
      clearCookies,
      addToCache,
      listCache,
      clearCache,
      navigationDelegate,
    }
    
    class SampleMenu extends StatelessWidget {
      SampleMenu(this.controller);
    
      final Future<WebViewController> controller;
      final CookieManager cookieManager = CookieManager();
    
      @override
      Widget build(BuildContext context) {
        return FutureBuilder<WebViewController>(
          future: controller,
          builder:
              (BuildContext context, AsyncSnapshot<WebViewController> controller) {
            return PopupMenuButton<MenuOptions>(
              onSelected: (MenuOptions value) {
                switch (value) {
                  case MenuOptions.showUserAgent:
                    _onShowUserAgent(controller.data, context);
                    break;
                  case MenuOptions.listCookies:
                    _onListCookies(controller.data, context);
                    break;
                  case MenuOptions.clearCookies:
                    _onClearCookies(context);
                    break;
                  case MenuOptions.addToCache:
                    _onAddToCache(controller.data, context);
                    break;
                  case MenuOptions.listCache:
                    _onListCache(controller.data, context);
                    break;
                  case MenuOptions.clearCache:
                    _onClearCache(controller.data, context);
                    break;
                  case MenuOptions.navigationDelegate:
                    _onNavigationDelegateExample(controller.data, context);
                    break;
                }
              },
              itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[
                PopupMenuItem<MenuOptions>(
                  value: MenuOptions.showUserAgent,
                  child: const Text('Show user agent'),
                  enabled: controller.hasData,
                ),
                const PopupMenuItem<MenuOptions>(
                  value: MenuOptions.listCookies,
                  child: Text('List cookies'),
                ),
                const PopupMenuItem<MenuOptions>(
                  value: MenuOptions.clearCookies,
                  child: Text('Clear cookies'),
                ),
                const PopupMenuItem<MenuOptions>(
                  value: MenuOptions.addToCache,
                  child: Text('Add to cache'),
                ),
                const PopupMenuItem<MenuOptions>(
                  value: MenuOptions.listCache,
                  child: Text('List cache'),
                ),
                const PopupMenuItem<MenuOptions>(
                  value: MenuOptions.clearCache,
                  child: Text('Clear cache'),
                ),
                const PopupMenuItem<MenuOptions>(
                  value: MenuOptions.navigationDelegate,
                  child: Text('Navigation Delegate example'),
                ),
              ],
            );
          },
        );
      }
    
      void _onShowUserAgent(
          WebViewController controller, BuildContext context) async {
        // Send a message with the user agent string to the Toaster JavaScript channel we registered
        // with the WebView.
        controller.evaluateJavascript(
            'Toaster.postMessage("User Agent: " + navigator.userAgent);');
      }
    
      void _onListCookies(
          WebViewController controller, BuildContext context) async {
        final String cookies =
        await controller.evaluateJavascript('document.cookie');
        Scaffold.of(context).showSnackBar(SnackBar(
          content: Column(
            mainAxisAlignment: MainAxisAlignment.end,
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              const Text('Cookies:'),
              _getCookieList(cookies),
            ],
          ),
        ));
      }
    
      void _onAddToCache(WebViewController controller, BuildContext context) async {
        await controller.evaluateJavascript(
            'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";');
        Scaffold.of(context).showSnackBar(const SnackBar(
          content: Text('Added a test entry to cache.'),
        ));
      }
    
      void _onListCache(WebViewController controller, BuildContext context) async {
        await controller.evaluateJavascript('caches.keys()'
            '.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))'
            '.then((caches) => Toaster.postMessage(caches))');
      }
    
      void _onClearCache(WebViewController controller, BuildContext context) async {
        await controller.clearCache();
        Scaffold.of(context).showSnackBar(const SnackBar(
          content: Text("Cache cleared."),
        ));
      }
    
      void _onClearCookies(BuildContext context) async {
        final bool hadCookies = await cookieManager.clearCookies();
        String message = 'There were cookies. Now, they are gone!';
        if (!hadCookies) {
          message = 'There are no cookies.';
        }
        Scaffold.of(context).showSnackBar(SnackBar(
          content: Text(message),
        ));
      }
    
      void _onNavigationDelegateExample(
          WebViewController controller, BuildContext context) async {
        final String contentBase64 =
        base64Encode(const Utf8Encoder().convert(kNavigationExamplePage));
        controller.loadUrl('data:text/html;base64,$contentBase64');
      }
    
      Widget _getCookieList(String cookies) {
        if (cookies == null || cookies == '""') {
          return Container();
        }
        final List<String> cookieList = cookies.split(';');
        final Iterable<Text> cookieWidgets =
        cookieList.map((String cookie) => Text(cookie));
        return Column(
          mainAxisAlignment: MainAxisAlignment.end,
          mainAxisSize: MainAxisSize.min,
          children: cookieWidgets.toList(),
        );
      }
    }
    
    
    
    class NavigationControls extends StatelessWidget {
      const NavigationControls(this._webViewControllerFuture)
          : assert(_webViewControllerFuture != null);
    
      final Future<WebViewController> _webViewControllerFuture;
    
      @override
      Widget build(BuildContext context) {
        return FutureBuilder<WebViewController>(
          future: _webViewControllerFuture,
          builder:
              (BuildContext context, AsyncSnapshot<WebViewController> snapshot) {
            final bool webViewReady =
                snapshot.connectionState == ConnectionState.done;
            final WebViewController controller = snapshot.data;
            controllerGlobal = controller;
    
            return Row(
              children: <Widget>[
                IconButton(
                  icon: const Icon(Icons.arrow_back_ios),
                  onPressed: !webViewReady
                      ? null
                      : () async {
                    if (await controller.canGoBack()) {
                      controller.goBack();
                    } else {
                      Scaffold.of(context).showSnackBar(
                        const SnackBar(content: Text("No back history item")),
                      );
                      return;
                    }
                  },
                ),
                IconButton(
                  icon: const Icon(Icons.arrow_forward_ios),
                  onPressed: !webViewReady
                      ? null
                      : () async {
                    if (await controller.canGoForward()) {
                      controller.goForward();
                    } else {
                      Scaffold.of(context).showSnackBar(
                        const SnackBar(
                            content: Text("No forward history item")),
                      );
                      return;
                    }
                  },
                ),
                IconButton(
                  icon: const Icon(Icons.replay),
                  onPressed: !webViewReady
                      ? null
                      : () {
                    controller.reload();
                  },
                ),
              ],
            );
          },
        );
      }
    }
    

    working demo. I click desktop page and then press device back button and you can see information display "onwill goback". and goback to previous page

    enter image description here

    enter image description here