8 min read

Flutter: Core Widget Concepts Deep Dive (Part 2/2)

A detailed learning-in-public deep dive on Core Widget Concepts in Flutter, covering concept batch 2/2.

#Learning-log#Flutter New Topic

Table of contents

  1. Context and Scope
  2. Conceptual Model & Analogy
  3. Deep Dive
  4. Implementation Patterns
  5. Common Pitfalls and Tradeoffs
  6. Technical Note
  7. Sources & Further Reading
  8. Check Your Work

Context and Scope

Run date: 2026-03-09. This deep dive focuses on two essential Flutter developer workflows: Hot Reload and Hot Restart. We’ll cover how each one works under the hood, when to use which, practical shortcuts across IDEs and CLI, gotchas that force a restart, and production-minded patterns that keep your debug cycles fast and predictable. Unless noted, guidance reflects Flutter 3.41 (stable) and Dart 3.10-era tooling. Notably, as of Flutter 3.35 (Aug 13, 2025), hot reload is supported on the web without an experimental flag. (docs.flutter.dev)

Conceptual Model & Analogy

  • Hot Reload is like swapping parts on a running server without shutting it down. You patch code in place, and the running program rebuilds its UI from the new code while keeping in-memory state.
  • Hot Restart is like power-cycling the server while leaving the rack and OS untouched. You replace the running Dart isolate and start from main() again, resetting app state, but you don’t rebuild native host code. On the web, this restart typically happens without a full page refresh. (docs.flutter.dev)

Deep Dive

Hot Reload (must know)

  • What it does: Injects changed Dart libraries into the running Dart VM (or browser runtime), then triggers a global rebuild/re-layout/repaint so you see the effect in under a second—without rerunning main() or initState(). App state in memory (including most State objects) is preserved. (docs.flutter.dev)
  • Scope and mode: Works only in debug mode. Profile/release builds don’t support it. (docs.flutter.dev)
  • CLI/IDE triggers:
    • Terminal: while running with flutter run, press r. (docs.flutter.dev)
    • Android Studio/IntelliJ: Hot Reload shortcut shown in the Run toolbar (⌘\ on macOS). VS Code: Debug > Restart (Ctrl/Cmd+R or use the lightning icon), and you can enable “hot reload on save.” (docs.flutter.dev)
  • Under the hood: Changed libraries + app’s main library are recompiled to a kernel file and reloaded into the VM. Then Flutter “reassembles” the running app to rebuild the widget tree. (docs.flutter.dev)

Hot Restart

  • What it does: Loads code changes and restarts the Flutter app from main(), resetting all Dart-side state. It’s slower than hot reload but guarantees a clean state. On web, it restarts the app without a full page refresh. (docs.flutter.dev)
  • CLI/IDE triggers:
    • Terminal: R (uppercase) for Hot Restart (shown in “Flutter run key commands”). (stackoverflow.com)
    • Android Studio/IntelliJ and VS Code: dedicated Hot Restart button/shortcut. (docs.flutter.dev)
  • What it does not do: It doesn’t rebuild native code (Kotlin/Java/Swift/Obj‑C) or re-link plugins. If you change native code, you must do a full stop/start (cold) restart. (docs.flutter.dev)

Implementation Patterns

Baseline example: See the state difference between Hot Reload and Hot Restart

import 'package:flutter/material.dart';

void main() {
  runApp(const CounterApp());
}

class CounterApp extends StatefulWidget {
  const CounterApp({super.key});

  @override
  State<CounterApp> createState() => _CounterAppState();
}

class _CounterAppState extends State<CounterApp> {
  int _count = 0;
  // Try editing this label, then Hot Reload: state (_count) stays.
  String _label = 'Tap the FAB';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Reload vs Restart')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(_label, style: const TextStyle(fontSize: 20)),
              const SizedBox(height: 8),
              Text('Count: $_count', style: const TextStyle(fontSize: 32)),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () => setState(() => _count++),
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}
  • Do this: Increment to 5. Change _label’s string and Hot Reload (r or the lightning icon). The label updates and Count remains 5.
  • Then Hot Restart (R or the “restart” icon). The app restarts from main(), and Count returns to 0. (docs.flutter.dev)

Production-grade pattern: Make hot reload resilient by reloading debug-only resources

Use State.reassemble to refresh assets/config when reloading in debug. This hook is called during hot reload so you can redo setup that depends on global/asset state. It won’t run in release builds. (api.flutter.dev)

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:flutter/painting.dart' show PaintingBinding;

void main() => runApp(const AppShell());

class AppShell extends StatefulWidget {
  const AppShell({super.key});
  @override
  State<AppShell> createState() => _AppShellState();
}

class _AppShellState extends State<AppShell> {
  late Future<AppConfig> _config;

  @override
  void initState() {
    super.initState();
    _config = _loadConfig();
  }

  @override
  void reassemble() {
    // Called on hot reload in debug builds; rerun any init that depends on global/asset state.
    super.reassemble();
    // Clear the image cache so changed assets show up immediately during dev.
    PaintingBinding.instance.imageCache.clear();
    // Reload configuration on each hot reload to reflect edits without a restart.
    setState(() {
      _config = _loadConfig();
    });
  }

  Future<AppConfig> _loadConfig() async {
    final jsonStr = await rootBundle.loadString('assets/config.json');
    return AppConfig.fromJson(json.decode(jsonStr) as Map<String, dynamic>);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: FutureBuilder<AppConfig>(
        future: _config,
        builder: (context, snapshot) {
          final title = snapshot.data?.title ?? 'Loading...';
          return Scaffold(
            appBar: AppBar(title: Text(title)),
            body: Center(child: Text('Env: ${snapshot.data?.env ?? '...'}')),
          );
        },
      ),
    );
  }
}

class AppConfig {
  final String title;
  final String env;
  AppConfig({required this.title, required this.env});
  factory AppConfig.fromJson(Map<String, dynamic> m) =>
      AppConfig(title: m['title'] as String, env: m['env'] as String);
}
  • Edit assets/config.json during development and Hot Reload to propagate changes without losing navigation/session state.
  • Note: State.reassemble is debug-only; don’t put production logic here. Also, not all asset/pubspec changes can be picked up without a full restart; if you modify native code or certain bundling rules, stop and start the app. (api.flutter.dev)

Common Pitfalls and Tradeoffs

  • You changed the wrong “place” for hot reload to take effect:
    • Hot reload does not rerun main() or initState(). If your change sits “above” the root build() (for example, changing which root widget runApp() uses), you might see no visible update until you Hot Restart. (docs.flutter.dev)
  • Structural type changes that require Hot Restart:
    • Changing an enum to a class (or vice versa) or modifying generic type declarations won’t apply with Hot Reload. Use Hot Restart. (docs.flutter.dev)
  • Platform/native changes demand a full stop/start:
    • Any changes to Android/iOS/macOS/Windows native code (Kotlin/Java/Swift/Obj‑C/C++), build.gradle, Info.plist, etc. require a complete rebuild (stop and start). (docs.flutter.dev)
  • CupertinoTabView.builder edge case:
    • Changes made to a CupertinoTabView.builder aren’t applied by hot reload. Plan to hot restart in this scenario. (docs.flutter.dev)
  • Static fields and globals are treated as “state”:
    • Static/global initializers aren’t re-executed on hot reload. If you change a static initializer value (or global), expect to Hot Restart to see it take effect. Const values are treated like aliases and are hot reloaded, but initializers themselves don’t rerun. (docs.flutter.dev)
  • Web specifics (2026 reality):
    • Flutter web now supports both hot reload and hot restart. Historically, many guides said “web only has hot restart,” which is outdated. Upgrade to ≥3.35 and avoid experimental flags. (docs.flutter.dev)
  • Lifecycle nuance:
    • Hot Restart does not call your dispose() handlers first; plugin-driven effects (e.g., audio playing) may continue until your restarted app re-initializes and stops them. Design cleanup flows accordingly. (stackoverflow.com)
  • State restoration testing:
    • To accurately test platform state restoration (e.g., after process death), perform a full rebuild/run; changes injected via hot reload/restart aren’t persisted. (api.flutter.dev)

Technical Note

Conflicting guidance exists across older posts and answers that “Flutter web doesn’t support hot reload.” This changed in 2025. As of Flutter 3.35 (Aug 13, 2025), hot reload on web is supported without any experimental flag. If you previously used —web-experimental-hot-reload or assumed hot restart was your only option, update your SDK and tooling (VS Code/IntelliJ plugins) and use standard hot reload. (docs.flutter.dev)

Sources & Further Reading

Check Your Work

Hands-on Exercise

  1. Start the baseline Counter app.
  2. Tap + to reach 7. Change the label text and Hot Reload. Verify the count stays 7 and the label updates.
  3. Modify a static/global initializer (for example, add a top-level final greeting = DateTime.now()) and display it. Hot Reload; if it doesn’t change, perform a Hot Restart and confirm it updates. (docs.flutter.dev)
  4. On web (Chrome), confirm that Hot Reload works without experimental flags in Flutter ≥3.35. If not, run flutter —version and upgrade. (docs.flutter.dev)

Brain Teaser

  • For each of the following edits, choose Hot Reload or Hot Restart (and why):
    • A) Change a Color in a TextStyle inside a deeply nested widget.
    • B) Add a new case to an existing enum and branch on it.
    • C) Replace the root widget passed to runApp().
    • D) Update Swift code inside an iOS plugin method.
    • E) Tweak a static singleton’s initial value used at first read.

Answer key (short): A) Hot Reload; it’s downstream of build. B) Hot Restart; enum change. C) Hot Restart; main()/root not rerun on reload. D) Full stop/start; native code. E) Hot Restart; static initializer not rerun on reload. (docs.flutter.dev)

References

Share

More to explore

Keep exploring

Previous

Flutter: Core Widget Concepts Refresher

Next

Weekly Engineering Mastery Quiz (2026-03-02 to 2026-03-06)