Flutter Reload: What’s Under the Hood
What really happens when you click on reload button
Introduction
Flutter’s hot reload feature is a game-changer for developers, allowing us to see changes in our app almost instantly. Have you ever wondered what actually happens when you hit that reload button? Let’s dive deep into the internals of Flutter’s hot reload process and uncover the magic behind this powerful feature.
Note: All the code samples in the article are sourced directly from Flutter and its Engine, with minor edits made for clarity and better understanding.
To understand the process, let’s first look at a high-level flow chart of what happens during a hot reload
Now, let’s break down each step of this process
1. User Clicks Hot Reload
When you trigger a hot reload, the HotRunner
class in the Flutter tools takes control. This class is responsible for orchestrating the entire process.
- The
HotRunner
first checks if the app is in a state that allows for hot reload. - It identifies which files have been modified since the last compilation.
- It communicates with the Dart VM using the VM Service Protocol.
Future<OperationResult> restart({...}) async {
final List<Uri> updatedFiles = await _getChangedFiles();
final bool success = await _reloadSources(updatedFiles);
if (!success) {
return OperationResult(1, 'Hot reload failed');
}
// ...
}
Reference: hot_runner.dart
2. Update Source Code (Dart VM code injection)
The Dart VM receives the updated source code and injects it into the running application.
- The VM creates new versions of the modified libraries.
- It uses a copy-on-write mechanism to efficiently handle the updates.
- The old versions of the code are kept in memory to allow for rollback if needed.
Future<bool> _reloadSources(List<Uri> files) async {
final vm_service.VM vm = await _vmService.getVM();
final vm_service.IsolateRef isolateRef = vm.isolates.first;
final vm_service.ReloadReport report = await _vmService.reloadSources(isolateRef.id);
return report.success;
}
Reference: app_snapshot.cc
3. Compile Code Using JIT
The Dart VM’s Just-In-Time (JIT) compiler quickly compiles the new code.
- The JIT compiler uses tiered compilation, starting with interpreted code and progressively optimizing hot code paths.
- It employs techniques like inline caching and type feedback for optimization.
- The compiler tries to reuse as much of the previous compilation as possible to speed up the process.
class DartVM {
Future<void> compileJIT(List<Library> modifiedLibraries) async {
for (var library in modifiedLibraries) {
await _jitCompiler.compile(library);
}
}
}
Reference: compiler.cc
4. Compare Old and New Widget Trees
Flutter’s framework compares the old widget tree with the new one to determine what needs to be updated.
- This process uses the
Element
tree, which is Flutter's reconciliation layer between widgets and the render objects. - It employs an efficient diffing algorithm that can handle widget identity and state preservation.
- The comparison is optimized to minimize the number of rebuilds required.
class Element {
void update(Widget newWidget) {
_widget = newWidget;
markNeedsBuild();
}
void markNeedsBuild() {
if (!_dirty) {
_dirty = true;
owner.scheduleBuildFor(this);
}
}
}
Reference: widget_framework.dart
5. Identify and Rebuild Affected Widgets
Based on the comparison, Flutter identifies which widgets need to be rebuilt and schedules their reconstruction.
- The
BuildOwner
class manages this process, maintaining a list of "dirty" elements that need rebuilding. - It uses a depth-first traversal to rebuild elements efficiently.
- State objects are preserved where possible to maintain the current app state.
class BuildOwner {
void buildScope(Element context, [VoidCallback callback]) {
_dirtyElements.sort(Element._sort);
_dirtyElements.reversed.forEach((Element element) {
if (element._dirty)
element.rebuild();
});
}
}
Reference: widgets_framework.dart
6. Manage Memory and Garbage Collection
During the hot reload process, Flutter needs to carefully manage memory to prevent leaks and ensure smooth performance.
- The Dart VM’s garbage collector runs to clean up any objects that are no longer needed.
- Flutter’s framework includes mechanisms for properly disposing of resources held by widgets.
- The framework tries to reuse existing objects where possible to minimize memory churn.
class State<T extends StatefulWidget> {
@protected
void dispose() {
// Clean up resources here
}
}
Reference: dart_heap.cc
7. Update UI in Real-Time
Finally, Flutter updates the UI to reflect the changes made during the hot reload.
- This involves updating the render tree, which is the low-level representation of the UI.
- The
RendererBinding
class orchestrates this process, managing layout, painting, and compositing. - Flutter uses a retained-mode rendering system, which allows it to efficiently update only the parts of the UI that have changed
class RendererBinding extends BindingBase with SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding {
void drawFrame() {
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
renderView.compositeFrame();
}
}
Reference: rendering_binding.dart
Conclusion
Flutter’s hot reload is a powerful feature that speeds up development significantly. It instantly applies code changes without losing the app’s state, enabling quick iteration and experimentation. This sophisticated process — involving code injection, efficient widget tree comparison, and smart UI updating — highlights Flutter’s focus on developer productivity. While it has some limitations, hot reload shows how clever engineering can vastly improve the app development workflow, leading to better, more polished applications.