Flutter Setup & First App

Hot Reload & Hot Restart

40 min Lesson 6 of 12

What is Hot Reload?

Hot reload is one of Flutter’s most powerful features. It allows you to inject updated source code into a running Dart Virtual Machine (VM) without restarting the entire application. The result? You see your changes reflected on screen in less than a second, while preserving the current state of your app.

This means if you are on a specific screen, have filled out a form, or scrolled to a certain position, all of that state is preserved when you hot reload. Only the code changes are applied.

Note: Hot reload works by injecting updated source code files into the running Dart VM. The VM updates classes with the new versions of fields and functions, and the Flutter framework automatically rebuilds the widget tree so you can quickly see the effects of your changes.

How Hot Reload Works Internally

Understanding the internal mechanism helps you know when and why hot reload works or fails:

  1. You make a code change and save the file (or press the hot reload button)
  2. The Dart VM identifies which source files have changed since the last compilation
  3. The modified Dart source code is compiled into kernel files (incremental compilation)
  4. The new kernel files are injected into the running Dart VM
  5. The VM updates affected classes with the new field and function definitions
  6. The Flutter framework triggers a rebuild of the widget tree starting from the root
  7. The framework calls build() methods on affected widgets
  8. The updated UI appears on screen

Hot Reload in Action

// Before: Blue background
Scaffold(
  backgroundColor: Colors.blue,
  body: Center(
    child: Text('Hello World'),
  ),
)

// After saving: Change to green -- hot reload applies instantly
Scaffold(
  backgroundColor: Colors.green,
  body: Center(
    child: Text('Hello World'),
  ),
)

Hot Reload vs Hot Restart vs Full Restart

Flutter provides three levels of reloading, each with different trade-offs between speed and completeness:

Hot Reload (Fastest)

Injects code changes without losing app state. Takes less than 1 second. Ideal for UI tweaks, style changes, and minor logic updates.

Triggering Hot Reload

# From terminal (when app is running with flutter run)
# Press: r

# VS Code
# Press: Ctrl+S (auto-save triggers hot reload)
# Or: Click the hot reload button (lightning bolt icon)

# Android Studio / IntelliJ
# Press: Ctrl+S (or Cmd+S on macOS)
# Or: Click the hot reload button in the toolbar

Hot Restart (Medium Speed)

Restarts the app from scratch without a full recompilation. Takes 2-5 seconds. The app state is lost, but it is much faster than a full restart. Use this when hot reload does not pick up your changes.

Triggering Hot Restart

# From terminal
# Press: R (capital R)

# VS Code
# Press: Ctrl+Shift+F5
# Or: Click the hot restart button (circular arrow icon)

# Android Studio / IntelliJ
# Click the hot restart button in the toolbar

Full Restart (Slowest)

Completely stops and rebuilds the application from scratch. Takes 10-30+ seconds. Required when you change native code, add plugins, or modify platform-specific files.

Triggering Full Restart

# Stop the app and run again
# Terminal: Press q to quit, then run flutter run again

# VS Code: Stop debugging, then start again (F5)

# Android Studio: Stop and re-run the application
Tip: A good rule of thumb: Try hot reload first (press r). If the changes do not appear, try hot restart (press R). Only do a full restart if neither works, which usually means you changed something outside of Dart code (like adding a new dependency or modifying Android/iOS configuration files).

When Hot Reload Works

Hot reload works reliably in these common scenarios:

1. Changing Widget Properties

Modifying Widget Styles

// Change colors, sizes, padding, margins
Container(
  padding: const EdgeInsets.all(16),  // Change from 8 to 16
  color: Colors.red,                   // Change from blue to red
  child: Text(
    'Styled Text',
    style: TextStyle(
      fontSize: 24,    // Change from 18 to 24
      fontWeight: FontWeight.bold,  // Add bold
    ),
  ),
)

2. Modifying Build Methods

Updating the Widget Tree

@override
Widget build(BuildContext context) {
  return Column(
    children: [
      Text('Welcome'),
      // Add new widgets -- hot reload picks this up
      Text('This is a new line!'),
      ElevatedButton(
        onPressed: () {},
        child: Text('New Button'),
      ),
    ],
  );
}

3. Changing Function Bodies

Updating Logic

void _incrementCounter() {
  setState(() {
    // Change from _counter++ to _counter += 5
    _counter += 5;
  });
}

When Hot Reload Does NOT Work

Hot reload has limitations. In these cases, you need a hot restart or full restart:

1. Changes to Global Variables and Static Fields

Global/Static Changes Require Hot Restart

// Changing these requires hot restart (R), not hot reload (r)
int globalCounter = 0;        // Changing initial value
const String appName = 'MyApp';  // Changing const values

class MyService {
  static final instance = MyService._();  // Changing static fields
  MyService._();
}

2. Changes to initState()

initState Changes Need Hot Restart

@override
void initState() {
  super.initState();
  // Changes here will NOT take effect with hot reload
  // because initState() only runs once when the widget
  // is first inserted into the tree
  _counter = 100;  // This change needs hot restart
}

3. Changes to Enum Types

Enum Changes Require Hot Restart

// Adding or removing enum values requires hot restart
enum Status {
  active,
  inactive,
  pending,  // Adding this requires hot restart
}

4. Changes to Generic Type Arguments

Generic Type Changes

// Changing type parameters requires hot restart
class MyList<T> {
  // Changing from MyList<T> to MyList<T extends Comparable>
  // requires hot restart
}
Warning: If hot reload produces unexpected behavior or the app enters a broken state, always try a hot restart first. If that does not fix it, do a full restart. Do not waste time debugging issues that are simply caused by stale code from a partial reload.

Stateful Hot Reload -- Preserving State

One of the most valuable aspects of hot reload is that it preserves the State of StatefulWidgets. This is incredibly useful during development.

State Preservation Example

class _CounterPageState extends State<CounterPage> {
  int _count = 0;
  String _message = 'Hello';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // If _count is 42 and you change the Text style below,
            // hot reload will update the style BUT _count stays at 42
            Text(
              'Count: \$_count',
              style: TextStyle(
                fontSize: 32,           // Change this...
                color: Colors.purple,   // ...or this
              ),
            ),
            Text(_message),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => setState(() => _count++),
        child: Icon(Icons.add),
      ),
    );
  }
}

In this example, if you have tapped the button 42 times and then change the fontSize from 32 to 48, hot reload will update the text size but _count will remain at 42. You do not need to tap 42 times again to get back to the same state.

Tip: State preservation is especially valuable when working on screens that require navigation. If you are debugging a screen three levels deep in navigation, hot reload lets you change code and see results without navigating back to that screen every time.

Practical Examples -- Live UI Tweaking

Let’s walk through a practical workflow of using hot reload to iteratively design a card widget.

Step 1: Start with a Basic Card

Card(
  child: Padding(
    padding: EdgeInsets.all(16),
    child: Text('My Card'),
  ),
)

Step 2: Hot Reload -- Add Elevation and Shape

Card(
  elevation: 8,
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(12),
  ),
  child: Padding(
    padding: EdgeInsets.all(16),
    child: Text('My Card'),
  ),
)

Step 3: Hot Reload -- Add Color and More Content

Card(
  elevation: 8,
  color: Colors.teal.shade50,
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(12),
  ),
  child: Padding(
    padding: EdgeInsets.all(16),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          'Card Title',
          style: TextStyle(
            fontSize: 20,
            fontWeight: FontWeight.bold,
          ),
        ),
        SizedBox(height: 8),
        Text('This is the card description.'),
      ],
    ),
  ),
)

Each change takes less than a second to appear on screen. This rapid feedback loop allows you to experiment with different designs quickly without waiting for full rebuilds.

Hot Reload Keyboard Shortcuts Summary

Here is a quick reference of all the keyboard shortcuts for reloading:

Keyboard Shortcuts Reference

# Terminal (flutter run)
r    -- Hot reload
R    -- Hot restart
q    -- Quit
d    -- Detach (leave app running)
h    -- Show help menu
v    -- Open Flutter DevTools
w    -- Dump widget tree
t    -- Dump render tree
p    -- Toggle debug paint

# VS Code
Ctrl+S / Cmd+S        -- Save (triggers hot reload)
Ctrl+Shift+F5         -- Hot restart
F5                    -- Start/continue debugging
Shift+F5              -- Stop debugging

# Android Studio / IntelliJ
Ctrl+S / Cmd+S        -- Save (triggers hot reload)
Ctrl+F5               -- Hot restart
Shift+F10             -- Run
Ctrl+F2               -- Stop

Limitations and Gotchas

Be aware of these additional limitations:

  • Native code changes: Modifying AndroidManifest.xml, Info.plist, Gradle files, or Podfile requires a full restart
  • Adding new packages: After running flutter pub add or modifying pubspec.yaml, you need a full restart
  • Asset changes: Adding new images or fonts to the assets folder requires a full restart
  • Main function changes: Changes to the main() function or runApp() need a hot restart
  • Compile errors: If your code has errors, hot reload will fail. Fix the errors and try again
  • Web platform: Hot reload on web uses a different mechanism (hot restart) that does not preserve state
Note: When developing for Flutter web, the reload mechanism differs from mobile. Web uses a hot restart approach that rebuilds JavaScript, so state is not preserved. For mobile (Android/iOS), hot reload preserves state as described in this lesson.

Best Practices for Efficient Development

Follow these practices to get the most out of hot reload:

  • Keep widgets small: Smaller widgets mean smaller rebuild scopes, making hot reload even faster
  • Use const constructors: Widgets marked as const are not rebuilt during hot reload, improving performance
  • Save frequently: Configure your editor to auto-save, which triggers hot reload automatically
  • Watch the console: The terminal shows hot reload status and any warnings or errors
  • Know when to restart: If something looks wrong after hot reload, do a hot restart before spending time debugging

Summary

In this lesson you learned:

  • Hot reload injects updated code into the running Dart VM in under a second
  • The difference between hot reload (preserves state), hot restart (loses state but fast), and full restart (complete rebuild)
  • Hot reload works for widget changes, build methods, and function body updates
  • Hot reload does NOT work for global variables, initState changes, enums, and generic type changes
  • State is preserved during hot reload, which is invaluable for iterative UI development
  • Essential keyboard shortcuts: r for hot reload, R for hot restart
  • When to use each type of reload for maximum productivity

Practice Exercise

Create a simple Flutter app with a StatefulWidget that has a counter and a text field. Run the app and increment the counter to 10. Then, without stopping the app, use hot reload to: (1) change the text style, (2) change the background color, (3) add a new button. Verify the counter stays at 10 after each hot reload. Then try changing the initial value in initState() and observe that hot reload does NOT apply the change -- you need hot restart.