13

In the current version of flutter 3.0.1 we have a service worker in index.html. So far, I cannot find any documentation on flutter.dev for how to force a cache refresh when screens or code has updated. The only way to refresh is using browser refresh buttons, etc.

There is a lot of outdated suggestions on SO to change the version number manually by appending something here or there, but that does not apply to the serviceworker. The service worker when running flutter build web automatically gets a new version number each time you build. This does not, however, force a cache refresh in the browser.

By default this service worker js is listening for a statechange and then calling console.log('Installed new service worker.'); This is only called when you click the browser's refresh button, so it is not helpful to prompt or force a refresh for new content.

I tried using flutter build web --pwa-strategy=none and this does not affect caching either.

Any bright ideas out there to force a refresh? In other websites I've built it was very easy to version css/js files, which would force a refresh without any user interaction, but I'm not seeing any clear paths with flutter build web.

This is the serviceWorker js in web/index.html.

After running flutter build web the var serviceWorkerVersion = null will become something like var serviceWorkerVersion = '1767749895'; when deployed to hosting and live on the web. However, this serviceWorkerVersion update does not force a refresh of content.

  <script>
    var serviceWorkerVersion = null;
    var scriptLoaded = false;
    function loadMainDartJs() {
      if (scriptLoaded) {
        return;
      }
      scriptLoaded = true;
      var scriptTag = document.createElement('script');
      scriptTag.src = 'main.dart.js';
      scriptTag.type = 'application/javascript';
      document.body.append(scriptTag);
    }

    if ('serviceWorker' in navigator) {
      // Service workers are supported. Use them.
      window.addEventListener('load', function () {
        // Wait for registration to finish before dropping the <script> tag.
        // Otherwise, the browser will load the script multiple times,
        // potentially different versions.
        var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
        navigator.serviceWorker.register(serviceWorkerUrl)
          .then((reg) => {
            function waitForActivation(serviceWorker) {
              serviceWorker.addEventListener('statechange', () => {
                if (serviceWorker.state == 'activated') {
                  console.log('Installed new service worker.');
                  loadMainDartJs();
                }
              });
            }
            if (!reg.active && (reg.installing || reg.waiting)) {
              // No active web worker and we have installed or are installing
              // one for the first time. Simply wait for it to activate.
              waitForActivation(reg.installing || reg.waiting);
            } else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
              // When the app updates the serviceWorkerVersion changes, so we
              // need to ask the service worker to update.
              console.log('New service worker available.');
              reg.update();
              waitForActivation(reg.installing);
            } else {
              // Existing service worker is still good.
              console.log('Loading app from service worker.');
              loadMainDartJs();
            }
          });

        // If service worker doesn't succeed in a reasonable amount of time,
        // fallback to plaint <script> tag.
        setTimeout(() => {
          if (!scriptLoaded) {
            console.warn(
              'Failed to load app from service worker. Falling back to plain <script> tag.',
            );
            loadMainDartJs();
          }
        }, 4000);
      });
    } else {
      // Service workers not supported. Just drop the <script> tag.
      loadMainDartJs();
    }
  </script>
Zelf
  • 1,723
  • 2
  • 23
  • 40
  • 1
    FYI, I've submitted this existing Flutter web issue to Flutter dev team, you can see the progress here and hopefully a resolution: https://github.com/flutter/flutter/issues/104509#issuecomment-1137224990 – Zelf Jun 11 '22 at 11:52
  • I've noticed in recent versions of flutter (3.0.X+) that my normal method of adding a build number parameter to the script src no longer worked to force a refresh automatically. Ex: `scriptTag.src = 'main.dart.js?v=' + buildVersionParam;` At some point, I've had to start manually triggering a browser refresh. – Matthew Rideout Oct 14 '22 at 00:41

2 Answers2

2

Have you tried this? Adding the no-cache in the index.html. Seems like working on Flutter 3.0. Keep in mind that by doing this your pwa will not work in offline mode.

<head>
...
    <meta http-equiv="cache-control" content="no-cache" />
    <meta http-equiv="expires" content="0" />
    <meta http-equiv="pragma" content="no-cache" />
...
</head>
Sunn
  • 766
  • 9
  • 17
  • 3
    I don't want it to not cache though. I want to force refresh or be able to display a refresh snackbar when a new version is available. – Zelf Oct 20 '22 at 12:35
0

You can use a Firestore listener to a specific document or Firebase Remote Config in real time, to check if needs to clear the cache.

Once it needs to clear the webpage cache, simply run:

import 'dart:html' as html show window;
html.window.location.reload();

https://firebase.google.com/docs/remote-config

Vitor
  • 11
  • 1
  • 1
  • Yes, we ultimately created a combination of a version constant in the code and then a remoteConfig version variable call. If the remoteConfig version is greater than the constant, we display a refresh prompt. – Zelf May 20 '23 at 18:39