How I Fixed Flutter Web’s Annoying Page Reload Problem
DEV Community

How I Fixed Flutter Web’s Annoying Page Reload Problem

Have you ever been filling out a long form on a website, accidentally hit F5 (or Command+R), and watched everything you just typed disappear into thin air? If you build web apps with Flutter, this happens more often than you think. When compiling Flutter for the web, we get amazing performance, especially with WebAssembly (WASM). But the browser environment comes with one brutal reality: the browser reload. When a user refreshes the page: - Every in-memory variable resets to null or0 . - Active text fields wipe clean. - The user gets kicked out of their current subpage and sent right back to / . To solve this, most developers pull in packages like shared_preferences . But on the web, that introduces another annoying problem: The Startup UI Flicker. The Startup UI Flicker Problem Because standard key-value storage packages rely on Future -based APIs, reading a stored value on app boot requires waiting for microtasks to resolve: // Standard asynchronous approach void initState() { super.initState(); loadSavedText(); } Future loadSavedText() async { final prefs = await SharedPreferences.getInstance(); setState(() { savedName = prefs.getString('user_name') ?? 'Guest'; }); } Here is what your user sees on screen when they hit refresh: Frame 1 (0ms): Flutter renders the widget immediately using the default value ("Guest"). Frame 5 (50ms - 100ms later): The background Future finally resolves. Frame 6: setState() triggers, and the text suddenly snaps from "Guest" to "Alex". That split-second jump is the Startup UI Flicker. To hide it, developers end up wrapping basic text fields in FutureBuilder blocks or showing awkward loading spinners just to read a string on app boot. Real-World Scenarios Where This Breaks UX Let's look at four everyday situations where standard Flutter storage falls short on the web: 1. The Unfinished Form Draft Imagine a user filling out a support ticket or profile editor. They get a phone call, switch tabs, come back, and accidentally reload. Without synchronous text persistence, their 10-minute typed draft is gone. 2. E-Commerce Filters and Carts A customer filters products by "Price: Low to High" and "Category: Electronics". They refresh to see if new items loaded. If those filter states reset, they have to re-select everything from scratch. 3. Multi-Tab Admin Dashboards An admin opens your app in two side-by-side browser tabs. They change an order status in Tab A. If Tab B isn't reactively listening to storage events, Tab B displays stale data until a manual refresh. 4. Deep Link Navigation Loss A user is reviewing a report at /dashboard/reports/2026. They hit refresh. Instead of staying on that report, the app resets to / because Flutter's default navigator loses route history on F5. Under the Hood: Why and How flutter_web_storage Works To eliminate this flicker, flutter_web_storage bypasses the asynchronous microtask queue entirely: 1. Direct Synchronous JS-Interop (package:web + dart:js_interop) Browser DOM APIs (window.localStorage and window.sessionStorage) are inherently synchronous operations in JavaScript. Legacy Flutter plugins wrapped these in asynchronous native channels or Future bridges. flutter_web_storage uses modern Dart 3.4+ interop bindings to invoke web.window.localStorage.getItem(key) directly. Because the browser returns the value in the exact same execution tick, the state hydrates inside the class constructor or initState() before Flutter renders its very first frame. 2. Tab Teardown Guard (onbeforeunload Event Listener) To ensure zero data loss during sudden tab closures or browser reloads, flutter_web_storage registers an active event listener on web.window.onbeforeunload. Right as the browser begins tearing down the page context, any queued updates are flushed to browser storage instantly. 3. Cross-Tab Event Broadcasting (window.onStorage) Instead of polling storage keys using timers, flutter_web_storage listens directly to native browser StorageEvent triggers (web.window.onStorage). When a user modifies a value in Tab A, the browser fires an origin-wide event. flutter_web_storage captures this and emits it through a synchronous StreamController.broadcast(), instantly updating Tab B. 4. Zero-Crash Multiplatform Conditional Imports If you compile your app for iOS, Android, or Desktop, web JS-interop libraries will crash. flutter_web_storage solves this using conditional library exports export 'src/stub/storage_stub.dart' if (dart.library.js_interop) 'src/web/storage_web.dart'; Real-World Practical Code Examples Example 1: Managing Dynamic Lists final storage = FlutterWebStorage.instance; // Save list of selected categories void saveSelectedCategories(List categories) { storage.setStringList('selected_filters', categories, area: StorageArea.session); } // Read list on widget init List getSavedCategories() { return storage.getStringList('selected_filters', area: StorageArea.session) ?? ['All']; } Example 2: Preserving Navigation Routes on Browser Refresh void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( navigatorObservers: [WebRoutePreserverNavigatorObserver()], initialRoute: WebRoutePreserverNavigatorObserver.getRestoredRoute() ?? '/', routes: { '/': (context) => const HomeScreen(), '/settings': (context) => const SettingsScreen(), }, ); } } Getting Started Add the package to your pubspec.yaml dependencies: flutter_web_storage: ^1.0.0 pub.dev Package: packagelink GitHub Repository: Github Portfolio: portfolio Top comments (0)

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.