skip to Main Content

One of my biggest issues with the Flutter web app is that the browser doesn’t properly clear the cache after deployment. This allows users to view old content even after updating the web application. I’m not sure why the browser keeps this old data, which results in loading old app assets (like javascript, css files or images) instead of newly installed versions. Instead of requesting updated files from the server, the browser continues to receive these old cached versions.

HTML CODE :


<!DOCTYPE html>
<html>
<head>
  <!--
    If you are serving your web app in a path other than the root, change the
    href value below to reflect the base path you are serving from.

    The path provided below has to start and end with a slash "/" in order for
    it to work correctly.

    For more details:
    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base

    This is a placeholder for base href that will be replaced by the value of
    the `--base-href` argument provided to `flutter build`.
  -->
  <base href="$FLUTTER_BASE_HREF">

  <meta charset="UTF-8">
  <meta content="IE=Edge" http-equiv="X-UA-Compatible">
  <meta name="description" content="This Flutter project is about a fruit supermarket side admin">

  <!-- iOS meta tags & icons -->
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="apple-mobile-web-app-title" content="shndz_panel">
  <link rel="apple-touch-icon" href="icons/Icon-192.png">

  <!-- Favicon -->
  <link rel="icon" type="image/png" href="favicon.png"/>

  <title>shndz</title>
  <link rel="manifest" href="manifest.json">

  <script>
    // The value below is injected by flutter build, do not touch.
    const serviceWorkerVersion = {{flutter_service_worker_version}};

  </script>

</head>
<body>
<script src="flutter_bootstrap.js" async></script>
<!--<script src="flutter_bootstrap.js" async></script>-->
<script>
  {{flutter_js}}
  {{flutter_build_config}}

  function launchFlutter() {
    // Download main.dart.js
    _flutter.loader.load({
        serviceWorker: {
            serviceWorkerVersion: serviceWorkerVersion,
        },
        onEntrypointLoaded: function(engineInitializer) {
            engineInitializer.initializeEngine().then(function(appRunner) {
                appRunner.runApp();
            });
        }
    });
  }

  window.addEventListener('load', function(ev) {
    if ('serviceWorker' in navigator) {

        // getting rid of undesired to fetch remote version.json file updated
        var seconds = new Date().getTime();
        var xmlhttp = new XMLHttpRequest();
        xmlhttp.open("GET", '/version.json?v=' + seconds, true);
        xmlhttp.addEventListener('load', function() {
            if (xmlhttp.status == 200) {
                var buildNumber = xmlhttp.responseText;
                console.log('remote version is ' + buildNumber);
                var currentBuildNumber = window.localStorage.getItem('buildNumber');

                console.log('local version is ' + currentBuildNumber);
                // clear worker cache if remote and local version are different
                if (currentBuildNumber != buildNumber) {
                    console.log('App update is necessary. Clearing service workers cache');
                    caches.delete('flutter-app-manifest');
                    caches.delete('flutter-temp-cache');
                    caches.delete('flutter-app-cache');

                    // store new version number
                    window.localStorage.setItem('buildNumber', buildNumber);
                } else {
                    console.log('App is up to date');
                }
            }
            launchFlutter();
        });
        
window.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames.map((cacheName) => {
          return caches.delete(cacheName);
        })
      );
    })
  );
  window.clients.claim();
  

        xmlhttp.addEventListener('error', function() {
            launchFlutter();
        });

        xmlhttp.addEventListener('abort', function() {
            launchFlutter();
        });

        xmlhttp.addEventListener('timeout', function() {
            launchFlutter();
        });

        xmlhttp.send();
    } else {
        console.log('Service worker not found. Continue app loading.');
        launchFlutter();
    }

  });
 
  </script>
</body>
</html>

flutter –version

enter image description here

I added a query parameter to the URL script to prevent the browser from using the old cached version. Specifically, I implemented a versioning query parameter, such as ?v=1.0, to ensure that the browser fetches the updated file. You can also check my code below.

But it was not successful.

Notice: Server apache / Cpanel

MY CODE

<!DOCTYPE html>
<html>
<head>
  <!--
    If you are serving your web app in a path other than the root, change the
    href value below to reflect the base path you are serving from.

    The path provided below has to start and end with a slash "/" in order for
    it to work correctly.

    For more details:
    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
  -->
  <base href="$FLUTTER_BASE_HREF">

  <meta charset="UTF-8">
  <meta content="IE=Edge" http-equiv="X-UA-Compatible">
  <meta name="description" content="This Flutter project is about a fruit supermarket side admin">

  <!-- iOS meta tags & icons -->
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="apple-mobile-web-app-title" content="shndz_panel">
  <link rel="apple-touch-icon" href="icons/Icon-192.png">

  <!-- Favicon -->
  <link rel="icon" type="image/png" href="favicon.png?v=1.0"/>  <!-- Add versioning to favicon -->

  <title>shndz </title>
  <link rel="manifest" href="manifest.json?v=1.0">  <!-- Add versioning to manifest -->

  <script>
    // The value below is injected by flutter build, do not touch.
    const serviceWorkerVersion = {{flutter_service_worker_version}};
  </script>

</head>
<body>
<script src="flutter_bootstrap.js?v=1.0" async></script>  <!-- Add versioning to flutter_bootstrap.js -->
<script>
  {{flutter_js}}
  {{flutter_build_config}}

  function launchFlutter() {
    // Download main.dart.js
    _flutter.loader.load({
        serviceWorker: {
            serviceWorkerVersion: serviceWorkerVersion,
        },
        onEntrypointLoaded: function(engineInitializer) {
            engineInitializer.initializeEngine().then(function(appRunner) {
                appRunner.runApp();
            });
        }
    });
  }

  window.addEventListener('load', function(ev) {
    if ('serviceWorker' in navigator) {
        // Getting rid of undesired to fetch remote version.json file updated
        var seconds = new Date().getTime();
        var xmlhttp = new XMLHttpRequest();
        xmlhttp.open("GET", '/version.json?v=' + seconds, true);
        xmlhttp.addEventListener('load', function() {
            if (xmlhttp.status == 200) {
                var buildNumber = xmlhttp.responseText;
                console.log('remote version is ' + buildNumber);
                var currentBuildNumber = window.localStorage.getItem('buildNumber');

                console.log('local version is ' + currentBuildNumber);
                // Clear worker cache if remote and local version are different
                if (currentBuildNumber != buildNumber) {
                    console.log('App update is necessary. Clearing service workers cache');
                    caches.delete('flutter-app-manifest');
                    caches.delete('flutter-temp-cache');
                    caches.delete('flutter-app-cache');

                    // Store new version number
                    window.localStorage.setItem('buildNumber', buildNumber);
                } else {
                    console.log('App is up to date');
                }
            }
            launchFlutter();
        });

        window.addEventListener('activate', (event) => {
          event.waitUntil(
            caches.keys().then((cacheNames) => {
              return Promise.all(
                cacheNames.map((cacheName) => {
                  return caches.delete(cacheName);
                })
              );
            })
          );
          window.clients.claim();
        });

        xmlhttp.addEventListener('error', function() {
            launchFlutter();
        });

        xmlhttp.addEventListener('abort', function() {
            launchFlutter();
        });

        xmlhttp.addEventListener('timeout', function() {
            launchFlutter();
        });

        xmlhttp.send();
    } else {
        console.log('Service worker not found. Continue app loading.');
        launchFlutter();
    }
  });
</script>
</body>
</html>

2

Answers


  1. Same here. You could try this:

    https://stackoverflow.com/a/74124477/8526660

    Add this to the head

      <meta http-equiv="cache-control" content="no-cache" />
      <meta http-equiv="expires" content="0" />
      <meta http-equiv="pragma" content="no-cache" />
    

    It won’t do cache/offline though

    Login or Signup to reply.
  2. In the last month or so, Flutter updated its Web FAQ with a section dedicated to cache concerns:

    https://docs.flutter.dev/platform-integration/web/faq#why-doesnt-my-app-update-immediately-after-its-deployed

    They outline a few different strategies, including how to force an app download after a new deployment. One thing in particular that may be necessary is to disable the service worker (as described at the end), assuming you don’t need PWA functionality. But you may need to try several of these strategies before finding one (or a combination) that works.

    Cache busting has been one of our team’s pain points with Flutter Web, but it’s good to see the Flutter team starting to address this.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search