DEV Community

kanta13jp1
kanta13jp1

Posted on

Flutter PWA Optimization: Service Worker, Offline Support, and Install Prompts

Flutter PWA Optimization: Service Worker, Offline Support, and Install Prompts

Flutter Web ships with PWA support built in. Here's how to tune it: install prompt control, offline data, and cache strategy.

PWA Baseline

Flutter Web is PWA-ready out of the box:
  - manifest.json → install metadata
  - service worker → offline support
  - HTTPS → required

flutter build web --pwa-strategy=offline-first
  → service worker pre-caches all assets
Enter fullscreen mode Exit fullscreen mode

Customize manifest.json

{
  "name": "My App",
  "short_name": "MyApp",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#1a1a2e",
  "theme_color": "#f97316",
  "description": "AI life management app",
  "orientation": "portrait-primary",
  "icons": [
    {
      "src": "icons/Icon-192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "icons/Icon-512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable any"
    }
  ],
  "screenshots": [
    {
      "src": "screenshots/home.png",
      "sizes": "390x844",
      "type": "image/png",
      "form_factor": "narrow"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Control the Install Prompt from Flutter

import 'dart:js_interop';

@JS('window.deferredPrompt')
external JSAny? get deferredPrompt;

@JS('window.promptInstall')
external JSPromise promptInstall();

bool get canInstallPwa => deferredPrompt != null;

Future<void> showInstallPrompt() async {
  if (!canInstallPwa) return;
  await promptInstall().toDart;
}
Enter fullscreen mode Exit fullscreen mode
// Add to <script> in web/index.html
window.addEventListener('beforeinstallprompt', (e) => {
  e.preventDefault();
  window.deferredPrompt = e;
});

window.promptInstall = async () => {
  if (!window.deferredPrompt) return;
  window.deferredPrompt.prompt();
  const { outcome } = await window.deferredPrompt.userChoice;
  window.deferredPrompt = null;
  return outcome;
};
Enter fullscreen mode Exit fullscreen mode

Install banner:

class InstallBanner extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    if (!canInstallPwa) return const SizedBox.shrink();
    return MaterialBanner(
      content: const Text('Add to home screen for offline access'),
      actions: [
        TextButton(
          onPressed: showInstallPrompt,
          child: const Text('Install'),
        ),
        TextButton(
          onPressed: _dismissBanner,
          child: const Text('Later'),
        ),
      ],
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Service Worker Cache Strategy

// Override flutter_service_worker.js after flutter build web
// Cache-First: assets (images, fonts)
// Network-First: API responses

self.addEventListener('fetch', (event) => {
  const url = new URL(event.request.url);

  // Supabase API → always network-first
  if (url.hostname.includes('supabase')) {
    event.respondWith(
      fetch(event.request).catch(() => caches.match('/offline.html'))
    );
    return;
  }

  // Assets → cache-first
  event.respondWith(
    caches.match(event.request).then((cached) =>
      cached || fetch(event.request)
    )
  );
});
Enter fullscreen mode Exit fullscreen mode

Offline Data with Hive

Future<List<Task>> getTasks() async {
  try {
    final online = await supabase
      .from('tasks')
      .select()
      .order('created_at');
    await _cache.put('tasks', jsonEncode(online));
    return online.map(Task.fromJson).toList();
  } catch (_) {
    // Offline: return cached data
    final cached = _cache.get('tasks');
    if (cached != null) {
      return (jsonDecode(cached) as List).map(Task.fromJson).toList();
    }
    return [];
  }
}
Enter fullscreen mode Exit fullscreen mode

Summary

manifest.json   → name, icon, theme_color, screenshots
Install prompt  → intercept beforeinstallprompt + call from Flutter
Service Worker  → Supabase = Network-First / assets = Cache-First
Offline data    → Hive local cache fallback
Enter fullscreen mode Exit fullscreen mode

Flutter PWA already covers 80% of the work. Tune manifest and the service worker and you get a near-native install experience.

Top comments (0)