Mobile Development 12 min read

How Flutter’s Hot Reload Works Under the Hood

This article explains Flutter's hot‑reload mechanism by detailing its JIT/AOT compilation model, the flutter_tools architecture, file‑change detection, incremental Dart Kernel generation, DevFS synchronization, widget‑tree reassembly, and the scenarios where hot‑reload fails versus hot‑restart.

NetEase Media Technology Team
NetEase Media Technology Team
NetEase Media Technology Team
How Flutter’s Hot Reload Works Under the Hood

Flutter Compilation Modes

Flutter uses two compilation strategies: JIT (Just‑In‑Time) for fast startup during development, and AOT (Ahead‑Of‑Time) for optimized native code in release builds. The development phase relies on JIT, which enables the hot‑reload feature by injecting changed Dart files into the running VM.

flutter_tools Overview

The flutter_tools package is a Dart program located at path/to/flutter/packages/flutter_tools. Its entry point is the main function in flutter_tools.dart which forwards arguments to executable.main(args). The executable assembles a list of FlutterCommand objects such as AnalyzeCommand, RunCommand, BuildCommand, etc., and executes the appropriate command based on the CLI input.

HotRunner and Hot Reload

When the flutter run command is issued, RunCommand creates either a HotRunner or a ColdRunner depending on the --no-hot flag. In debug mode, hot mode is enabled by default.

Key classes involved in hot reload: VMService: connection to the device’s Dart VM. _DevFSHttpWriter: writes files to the device via the VMService HTTP address. ResidentCompiler: compiles changed files and produces a CompilerOutput object. DevFS: holds the compiled incremental files and synchronizes them to the device through _DevFSHttpWriter.

Detecting Changed Files

The method _updateDevFS in flutter_tools/lib/src/run_hot.dart calls ProjectFileInvalidator.findInvalidated to scan the project directory. It compares each file’s updatedAt.millisecondsSinceEpoch with the last compilation timestamp; files newer than the last compile are added to invalidatedFiles.

Generating Incremental Kernel and Syncing

Invalidated files are passed to generator.recompile, which produces an incremental Dart Kernel file (e.g., app.dill.incremental.dill). This file is wrapped in a Map<Uri, DevFSContent> called dirtyEntries and sent to the device via _DevFSHttpWriter. The device stores the file under its code cache directory, matching the local build output.

Triggering Widget Tree Reassembly

After the VM receives the incremental kernel, it merges it with the existing kernel and reloads the code. The VM then calls Element.reassemble(), which marks the element tree for rebuild and recursively reassembles child elements, causing the Flutter widget tree to update.

Overall Hot‑Reload Flow

Scan project files with ProjectFileInvalidator.findInvalidated to collect changed URIs.

Recompile those files using generator.recompile to create app.dill.incremental.dill.

Transfer the incremental kernel to the device via _DevFSHttpWriter.

The Dart VM merges the new kernel with the existing one and reloads it.

The VM triggers Element.reassemble(), rebuilding the widget tree.

When Hot Reload Does Not Work

Hot reload only rebuilds the widget tree; it does not restart the application. Therefore, it fails in the following situations:

Changes to global variables or static fields.

Modifications to the main() function logic.

Changing the root widget of the tree.

Switching a widget from StatelessWidget to StatefulWidget (or vice‑versa).

Altering code inside initState().

In these cases, a hot restart is required.

Hot Reload vs. Hot Restart

Both actions are accessible from the Android Studio toolbar or via console commands ( r for reload, R for restart). The underlying difference is the fullRestart flag passed to residentRunner.restart. Key distinctions:

Hot reload preserves the current State objects; hot restart discards all state.

Hot reload outputs build/app.dill.incremental.dill; hot restart outputs the full build/app.dill.

Hot restart takes longer because it performs a full application rebuild.

Logging and Debugging flutter_tools

flutter_tools prints many internal logs via printTrace. By default, on non‑Windows platforms the logger is StdoutLogger. Overriding its printTrace method to prepend [FlutterTools] enables visibility of these logs in the console.

After modifying the logger, delete path/to/flutter/bin/cache/flutter_tools.snapshot and run any Flutter command again to regenerate the tool with the new logging behavior.

Debugging the Tool Itself

Create a test Flutter project (e.g., t_flutter_app).

Open the flutter_tools source in Android Studio at path/to/flutter/packages/flutter_tools.

Set breakpoints in the desired locations (e.g., _updateDevFS, _commonTerminalInputHandler).

Add a Debug Configuration for the flutter_tools project and start debugging.

Run the test app, press r after making a code change, and observe the breakpoint being hit.

These steps allow developers to step through the hot‑reload implementation and understand its inner workings.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

DARTDebuggingFlutterMobile DevelopmentCompilationhot-reloadHot Restart
NetEase Media Technology Team
Written by

NetEase Media Technology Team

NetEase Media Technology Team

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.