Flutter Architecture
Understand the layered architecture of Flutter framework.
What is it?
Flutter Architecture is the layered design that powers the Flutter framework. It consists of three main layers: the Embedder, the Engine, and the Framework. Each layer builds upon the previous one, providing a complete system for building cross-platform applications.
Why does it exist?
Flutter Architecture exists to:
- Provide a clean separation of concerns
- Enable cross-platform compatibility
- Support high-performance rendering
- Allow platform-specific customization
- Make the framework extensible
- Simplify development and debugging
- Enable hot reload functionality
The Three Layers
Flutter is built on three layers: Embedder, Engine, and Framework.
┌─────────────────────────────────────────────┐
│ Framework (Dart) │
│ ┌─────────────────────────────────────────┐ │
│ │ Material │ Cupertino │ Widgets │ │
│ ├─────────────┼─────────────┼────────────┤ │
│ │ Rendering Layer │ │
│ ├─────────────────────────────────────────┤ │
│ │ Widgets Layer │ │
│ ├─────────────────────────────────────────┤ │
│ │ Foundation Layer │ │
│ └─────────────────────────────────────────┘ │
├─────────────────────────────────────────────┤
│ Engine (C/C++) │
│ ┌─────────────────────────────────────────┐ │
│ │ Skia │ Dart VM │ Text Layout │ │
│ ├─────────┼────────────┼─────────────────┤ │
│ │ GPU │ IO │ Platform │ │
│ └─────────────────────────────────────────┘ │
├─────────────────────────────────────────────┤
│ Embedder (Platform Specific) │
│ ┌─────────────────────────────────────────┐ │
│ │ Android │ iOS │ Web │ Desktop │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
What's happening here? - Embedder is platform-specific code - Engine handles rendering and Dart execution - Framework is what developers interact with - Each layer has specific responsibilities - Layers communicate through defined interfaces
Embedder Layer
The Embedder is the platform-specific layer that integrates Flutter with the host operating system.
// Not directly used in Dart code
// But here's what it handles:
// Android Embedder (Java/Kotlin)
// - Activity lifecycle
// - SurfaceView for rendering
// - Platform channels
// - Input handling
// iOS Embedder (Objective-C/Swift)
// - UIViewController lifecycle
// - Metal/OpenGL rendering
// - Platform channels
// - Touch events
// Web Embedder (JavaScript)
// - DOM manipulation
// - Canvas rendering
// - WebGL support
// - Browser events
// Desktop Embedder (C++)
// - Window management
// - OpenGL/DirectX
// - Input events
// - File system access
What's happening here? - Embedder connects Flutter to OS - Handles window creation and events - Manages thread lifecycle - Provides platform-specific APIs - Different for each platform
Engine Layer
The Engine is written in C/C++ and provides the core functionality of Flutter.
// Engine components (conceptual)
// Skia Graphics Engine
SkCanvas canvas;
canvas.drawRect(rect, paint);
canvas.drawText("Hello", position);
// Dart VM
Dart_Isolate isolate;
Dart_EnterScope();
Dart_Invoke(object, method, args);
// Text Layout
Paragraph paragraph = ParagraphBuilder(style)
.addText("Hello World")
.build()
.layout(containerWidth);
// Platform Channels
PlatformChannel channel = PlatformChannel(name);
channel.setMethodCallHandler((call) async {
return platformMethod(call);
});
// GPU Thread
void renderFrame() {
frame_scheduler.scheduleFrame();
rasterizer.draw(frame);
}
// IO Thread
void loadImage(String url) {
image_cache.load(url);
}
What's happening here? - Skia handles all graphics rendering - Dart VM executes Dart code - Text layout is handled by Skia - Platform channels enable native communication - GPU thread handles rendering - IO thread manages assets and networking
Framework Layer
The Framework is written in Dart and is what developers use to build Flutter apps.
// Framework components
// 1. Foundation Layer
// Basic utilities and core classes
import 'dart:ui'; // Canvas, Paint, etc.
import 'package:flutter/foundation.dart'; // ChangeNotifier, etc.
// 2. Widgets Layer
// Core widget system
import 'package:flutter/widgets.dart';
// - Widget
// - StatelessWidget
// - StatefulWidget
// - InheritedWidget
// - RenderObjectWidget
// 3. Rendering Layer
// Layout and painting
import 'package:flutter/rendering.dart';
// - RenderBox
// - RenderObject
// - PaintingContext
// - Layout constraints
// 4. Material Library
// Material Design widgets
import 'package:flutter/material.dart';
// - MaterialApp
// - Scaffold
// - AppBar
// - ElevatedButton
// 5. Cupertino Library
// iOS-style widgets
import 'package:flutter/cupertino.dart';
// - CupertinoApp
// - CupertinoNavigationBar
// - CupertinoButton
What's happening here? - Foundation provides utilities - Widgets layer is the UI building system - Rendering handles layout and painting - Material provides Android-style widgets - Cupertino provides iOS-style widgets
Framework Components in Detail
Foundation Layer
// Foundation provides core utilities
import 'package:flutter/foundation.dart';
// ChangeNotifier for state management
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // Notify listeners of changes
}
}
// ValueNotifier for simple values
final counter = ValueNotifier<int>(0);
counter.value++; // Triggers rebuild
// Key for widget identification
Key key = UniqueKey();
Key valueKey = ValueKey<int>(42);
// Debug tools
debugPrint('Debug message');
assert(condition, 'Error message');
What's happening here? - ChangeNotifier enables reactive UI - ValueNotifier for simple reactive values - Keys for widget identity - Debug tools for development
Widgets Layer
// Widgets layer provides the widget system
import 'package:flutter/widgets.dart';
// Basic widget hierarchy
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Row(
children: [
Text('Hello'),
Icon(Icons.star),
],
),
);
}
}
// BuildContext provides widget tree information
@override
Widget build(BuildContext context) {
// Access theme
ThemeData theme = Theme.of(context);
// Access inherited widgets
var data = InheritedData.of(context);
// Media query for screen size
var size = MediaQuery.of(context).size;
return Container();
}
What's happening here? - Widgets are immutable configurations - BuildContext locates widgets in tree - Inherited widgets share data - MediaQuery provides screen information
Rendering Layer
// Rendering layer handles layout and painting
import 'package:flutter/rendering.dart';
// RenderObject for custom rendering
class MyRenderObject extends RenderBox {
@override
void paint(PaintingContext context, Offset offset) {
final canvas = context.canvas;
canvas.drawRect(
offset & size,
Paint()..color = Colors.blue,
);
}
@override
void performLayout() {
size = constraints.constrain(Size(100, 100));
}
}
// RenderObjectWidget for custom widgets
class MyRenderWidget extends LeafRenderObjectWidget {
@override
RenderObject createRenderObject(BuildContext context) {
return MyRenderObject();
}
}
What's happening here? - RenderObject handles painting - PerformLayout determines size - Paint draws to canvas - Layout constraints flow downward
Data Flow in Flutter
Data flows in one direction: from the top of the widget tree down.
// Top-down data flow
class ParentWidget extends StatelessWidget {
final String data = 'Hello';
@override
Widget build(BuildContext context) {
return ChildWidget(data: data);
}
}
class ChildWidget extends StatelessWidget {
final String data;
const ChildWidget({required this.data});
@override
Widget build(BuildContext context) {
return Text(data); // Receives data from parent
}
}
// Events flow bottom-up
class ButtonWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
// Event flows up through callbacks
Navigator.push(context, route);
},
child: Text('Tap me'),
);
}
}
What's happening here? - Data flows down from parent to child - Events flow up through callbacks - State management sits in between - Widgets receive data through parameters
Rendering Pipeline
The rendering pipeline consists of three trees: Widget, Element, and RenderObject.
// 1. Widget Tree (Configuration)
// Immutable widgets
Widget widget = Container(
color: Colors.blue,
child: Text('Hello'),
);
// 2. Element Tree (State)
// Mutable elements that hold widget state
Element element = widget.createElement();
// 3. RenderObject Tree (Layout/Paint)
// Actual rendering objects
RenderObject renderObject = element.renderObject;
// The pipeline process:
// 1. Build widgets
// 2. Update elements
// 3. Layout render objects
// 4. Paint to canvas
// 5. Composite layers
// 6. Display on screen
What's happening here? - Widgets are the blueprint - Elements hold state - RenderObjects handle painting - Pipeline runs every frame
Platform Channels
Platform channels enable communication between Dart and native code.
// Dart side
import 'package:flutter/services.dart';
class PlatformService {
static const MethodChannel _channel =
MethodChannel('com.example.app/platform');
static Future<String> getPlatformVersion() async {
try {
final String result = await _channel.invokeMethod('getPlatformVersion');
return result;
} on PlatformException catch (e) {
return 'Failed: ${e.message}';
}
}
static Future<void> showNativeToast(String message) async {
await _channel.invokeMethod('showToast', {'message': message});
}
}
// Usage
void main() async {
String version = await PlatformService.getPlatformVersion();
print('Platform: $version');
await PlatformService.showNativeToast('Hello from Flutter!');
}
// Android side (Kotlin)
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
MethodChannel(flutterEngine.dartExecutor, "com.example.app/platform")
.setMethodCallHandler { call, result ->
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else if (call.method == "showToast") {
Toast.makeText(this, call.argument("message"), Toast.LENGTH_SHORT).show()
result.success(null)
} else {
result.notImplemented()
}
}
}
}
// iOS side (Swift)
class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
let channel = FlutterMethodChannel(
name: "com.example.app/platform",
binaryMessenger: controller.binaryMessenger
)
channel.setMethodCallHandler { (call, result) in
if call.method == "getPlatformVersion" {
result("iOS \(UIDevice.current.systemVersion)")
} else if call.method == "showToast" {
// Show native toast
result(nil)
} else {
result(FlutterMethodNotImplemented)
}
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
What's happening here? - MethodChannel enables two-way communication - Platform channels are asynchronous - Data must be serializable (JSON-like) - Errors are propagated via PlatformException - Works across all platforms
Best Practices
Understand the Layers
// Use the right layer for the right task
// Framework: Build UI
// Engine: Rendering, platform integration
// Embedder: Platform-specific integration
// Most development happens in Framework
import 'package:flutter/material.dart';
// For custom rendering, use RenderObject
import 'package:flutter/rendering.dart';
// For platform integration, use Platform Channels
import 'package:flutter/services.dart';
Follow Data Flow
// Data flows down
// Events flow up
// Keep state management at appropriate levels
// Good
class Parent extends StatelessWidget {
final String data;
@override
Widget build(BuildContext context) {
return Child(data: data);
}
}
// Bad
class Child extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Accessing data from too high
return Text(globalData);
}
}
Common Mistakes
Overusing Platform Channels
Wrong:
// Too many platform calls
for (var item in items) {
await platformChannel.invokeMethod('process', item);
}
Correct:
// Batch operations
await platformChannel.invokeMethod('processBatch', items);
Not Handling Platform Exceptions
Wrong:
Future<void> callPlatform() async {
await channel.invokeMethod('doSomething');
}
Correct:
Future<void> callPlatform() async {
try {
await channel.invokeMethod('doSomething');
} on PlatformException catch (e) {
print('Platform error: ${e.message}');
// Handle error gracefully
}
}
Summary
Flutter Architecture consists of three layers: Embedder (platform-specific), Engine (C/C++ core), and Framework (Dart). Understanding these layers helps you build better apps and debug issues effectively.
Next Steps
Did You Know?
- The Flutter Engine is about 3MB in size
- Flutter can run without the Framework
- The Dart VM runs in AOT or JIT mode
- Flutter supports custom platform channels