Force cache refresh on flutter v3.0.1 web
Asked Answered
M

2

19

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>
Matilda answered 24/5, 2022 at 13:42 Comment(3)
FYI, I've submitted this existing Flutter web issue to Flutter dev team, you can see the progress here and hopefully a resolution: github.com/flutter/flutter/issues/…Matilda
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.Synergetic
@AlejandroCumpa Yes, but using a different path. Using Firebase Remote config, I created a version variable. Then, in the Flutter app I created a version Constant. If the remote config version is > than the Flutter app Constant, I display a Refresh prompt to the user. When they refresh they get the new version.Matilda
T
4

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

Thynne answered 19/5, 2023 at 12:55 Comment(2)
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.Matilda
Is this still the best way to do things? It's kind of a pain.Hydranth
E
3

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>
Extensible answered 19/10, 2022 at 11:22 Comment(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.Matilda
Any information on how to do that? @MatildaReorder
@Reorder see my response to Vitor. In the end using remoteConfig and a constant in the code, it is simple to display a SHIFT+F5 prompt, just like you see in Google Message, if you use that. When we push a new version, we update the version in the constant in the code, and then in the remote config. So, if a user is running an outdated version, the constant in the outdated code will not be the same as the remote config, which is checked on every screen, so, they will see a prompt to manually refresh. Works just fine.Matilda

© 2022 - 2024 — McMap. All rights reserved.