Platform Embedder #
The Platform Embedder is the lowest layer in Flutter’s system architecture, communicating directly with the device’s native operating system. Without the Platform Embedder, the C++ Engine would have no way to initialize graphics libraries, capture finger touch coordinates, play sound, read storage files, or interact with basic operating system services. The Platform Embedder is responsible for establishing a native host shell that acts as the parent container where the Flutter Engine is placed and run. It’s through this design that Flutter can run consistently on any platform — from smartphones, desktop computers, and web browsers to custom embedded systems.
The Embedder’s Main Roles #
Functionally, the Platform Embedder carries full responsibility for acting as the physical intermediary between the universal (platform-agnostic) Flutter Engine and the specific local operating system.
There are four main pillars of responsibility handled by the Platform Embedder:
flowchart TD
subgraph Engine["Flutter Engine (Platform-Agnostic C++)"]
EngCore["Core Engine (VM, Rasterizer, Compositor)"]
end
subgraph Embedder["Platform Embedder (Native Host)"]
direction TB
Surf["1. Rendering Surface (Metal / Vulkan / WebGL)"]
Input["2. Input Translators (Touch coordinates -> PointerDataPacket)"]
Life["3. Lifecycle Manager (OS state -> Flutter AppLifecycleState)"]
Chan["4. Platform Channels Binding (JNI / Swift / C++ Message Router)"]
end
subgraph OS["Operating System & Hardware"]
OSCore["Android, iOS, macOS, Windows, Linux, Web"]
end
Engine <-->|"Stable ABI (C API)"| Embedder
Embedder <-->|"Driver System & Window API"| OS
style Engine stroke:#388e3c,stroke-width:2px
style Embedder stroke:#f57c00,stroke-width:2px- Providing the Rendering Surface: The Embedder requests graphics memory allocation from the operating system (like a metal layer on iOS or a window handle on Windows) and hands that rendering surface to the Engine to draw on.
- Translating Input & Events: Captures physical hardware input events (digitizer touches, mouse movement, keyboard presses) and translates them into standardized logical coordinates before sending them to the Engine.
- Managing the Lifecycle: Listens for app status transition signals from the operating system (like the app being minimized or closed) and converts them into Flutter’s reactive lifecycle states.
- Bridging System Services: Provides asynchronous message routing binding libraries that underpin Platform Channels for accessing native hardware like GPS modules, cameras, and sensors.
Embedder Languages per Platform #
Because the Platform Embedder acts as a native app host, it’s written in the official programming languages natively supported by each target platform’s operating system ecosystem:
| Target Platform | Embedder Programming Language | Main Rendering Surface |
|---|---|---|
| Android | Java / Kotlin + C++ (JNI) | SurfaceView / TextureView |
| iOS | Swift / Objective-C / Objective-C++ | CAMetalLayer (Metal API) |
| macOS | Swift / Objective-C / Objective-C++ | NSView + CAMetalLayer (Metal) |
| Windows | C++ (Win32 API) | HWND (Win32 Window Handle) |
| Linux | C++ (GTK+ / Wayland API) | GTK Window / DRM Canvas |
| Web | JavaScript / WebAssembly (Wasm) | HTML5 Canvas + WebGL2/WebGPU |
The connective core between all these native programming languages and the C++ Engine is the Embedder C API, stably declared in the flutter_embedder.h header file. This library provides a static C ABI (Application Binary Interface), guaranteeing you can develop custom platform embedders without disassembling the C++ Engine’s internal code structure.
Android Embedder #
The Android Embedder is one of the most mature and complex embedder implementations, because Android was Flutter’s first target platform in the industry.
1. FlutterActivity vs FlutterFragment #
The Android Embedder provides two main native containers for displaying Flutter visuals:
FlutterActivity: A subclass of the standard native Android Activity. This is the easiest way to launch a Flutter app becauseFlutterActivityautomatically manages the entire engine initialization lifecycle, graphics view creation, and background memory cleanup.FlutterFragment: Used when you want to gradually integrate Flutter into an existing native Android app (Add-to-App).FlutterFragmentcan be inserted into native XML layouts alongside other native views.
// CORRECT: Using FlutterFragment to insert a Flutter screen into a Native Activity
class DashboardActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_dashboard)
// Creating a new fragment instance and attaching it to the XML container
val flutterFragment = FlutterFragment.withNewEngine()
.initialRoute("/cart-summary")
.build<FlutterFragment>()
supportFragmentManager.beginTransaction()
.replace(R.id.flutter_view_container, flutterFragment)
.commit()
}
}
2. SurfaceView vs TextureView #
The Android Embedder supports two rendering surface options with different performance characteristics:
flowchart TD
subgraph AndroidSurface["Android Rendering Surface Options"]
direction TB
SV["SurfaceView (Default)"] -->|"Dedicated Hardware Layer"| SV_P["High Performance & No Overdraw"]
SV -->|"Limitation"| SV_C["Cannot Overlay with Native Views"]
TV["TextureView (Fallback)"] -->|"Treated as a Regular View"| TV_P["Supports Transformations & Native View Overlay"]
TV -->|"Limitation"| TV_C["Higher RAM Usage & Texture Copy per Frame"]
end
style SV stroke:#4caf50,stroke-width:2px
style TV stroke:#f44336,stroke-width:2pxBy default, Flutter uses SurfaceView because its visual rendering performance is much faster and more power-efficient. You’re only advised to explicitly switch to TextureView when you need to insert Flutter visuals below or above other native components (like placing a Flutter button on top of a native Google Maps map).
3. Android Lifecycle to Flutter App Status #
The Android Embedder actively monitors the Android OS activity status changes and maps them into app lifecycle state representations (AppLifecycleState) on the Dart side:
flowchart TD
subgraph AndroidOS["Android Activity Lifecycle"]
direction TB
A_Create["onCreate() / onStart()"]
A_Resume["onResume()"]
A_Pause["onPause()"]
A_Stop["onStop()"]
A_Destroy["onDestroy()"]
end
subgraph FlutterOS["Flutter App Status (Dart)"]
direction TB
F_Resume["AppLifecycleState.resumed (Active)"]
F_Inactive["AppLifecycleState.inactive (Transition/Split-screen)"]
F_Paused["AppLifecycleState.paused (Background)"]
F_Detached["AppLifecycleState.detached (Shut down)"]
end
A_Create --> F_Resume
A_Resume --> F_Resume
A_Pause --> F_Inactive
A_Stop --> F_Paused
A_Destroy --> F_Detached
style AndroidOS stroke:#0288d1,stroke-width:2px
style FlutterOS stroke:#388e3c,stroke-width:2pxOn the Dart side, you can monitor these changes asynchronously using the WidgetsBindingObserver mixin:
class AppStateObserver extends StatefulWidget {
const AppStateObserver({super.key});
@override
State<AppStateObserver> createState() => _AppStateObserverState();
}
class _AppStateObserverState extends State<AppStateObserver> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this); // Register as an observer
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.paused) {
// CORRECT: Pause the websocket connection or save draft data when the app enters background
saveUserSessionDraft();
}
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this); // Must clean up
super.dispose();
}
@override
Widget build(BuildContext context) => const SizedBox.shrink();
}
iOS Embedder #
The iOS Embedder acts as a native iOS app host using the UIKit framework and Metal API rendering (through the Impeller graphics sub-system).
1. Initializing FlutterViewController #
Flutter’s main screen on iOS is packaged in a class object called FlutterViewController. When you present FlutterViewController to the screen, this controller dynamically coordinates the creation of a Metal rendering surface (CAMetalLayer) to display the Engine’s visual pixels.
// Swift: Presenting a Flutter screen from native iOS code
import UIKit
import Flutter
class MenuViewController: UIViewController {
func openSettingsPage() {
let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine
let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
// Opening the Flutter screen as a modal transition
self.present(flutterViewController, animated: true, completion: nil)
}
}
2. Cold Start Optimization: Pre-Warming the Engine #
Initializing a new C++ Engine takes about 100-200ms because the system must establish the Dart VM, allocate isolate memory, and launch the four task runners. If the user presses a button and has to wait 200ms before the screen opens, the app will feel laggy.
The solution is Pre-warming. You create a FlutterEngine instance early, when the app first opens in the AppDelegate, and keep it in memory cache, so when FlutterViewController is called, it can render the screen instantly with no initialization delay.
// AppDelegate.swift: Pre-warming the Flutter Engine at iOS app startup
import UIKit
import Flutter
@main
class AppDelegate: FlutterAppDelegate {
var flutterEngine = FlutterEngine(name: "cached_app_engine")
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Launching the engine first in the background
flutterEngine.run()
GeneratedPluginRegistrant.register(with: self.flutterEngine)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
Desktop Embedder #
The Desktop Embedder is developed using C++ (for Windows and Linux) and Objective-C++ (for macOS). This layer aligns desktop architectures with the Flutter paradigm.
As an illustration, the Windows desktop app boot flow runs through the following stages:
flowchart TD
WinMain["1. main() C++ Win32 Entry Point"] -->|"2. API Initialization"| WinInit["FlutterDesktopInit()"]
WinInit -->|"3. Create Window Shell"| WinVC["FlutterDesktopViewControllerCreate()"]
WinVC -->|"4. Enter Event Loop"| WinLoop["Win32 Message Loop (Listens for Clicks, Hover, Resize)"]
WinLoop -->|"5. App Shutdown"| WinDestroy["FlutterDesktopDestroyViewController()"]
style WinLoop stroke:#0288d1,stroke-width:2pxThe Desktop Embedder handles several important challenges that distinguish it from mobile embedders:
- Window Management System (Windowing): Mobile devices assume apps run fullscreen. On desktop, the embedder must respond to dynamic window resizing and report it to the engine so the layout adjusts instantly.
- Different Input Models: Mobile only detects finger touch events. The desktop embedder must detect mouse pointer movement, hover cursor states, right/middle clicks, mouse wheel scrolling, and physical keyboard shortcut combinations.
You can secure desktop visual functionality by detecting the target platform type at the Dart level:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
Widget buildSystemSpecificButton() {
// CORRECT: Statically checking the target platform type for UI adjustments
final isDesktop = defaultTargetPlatform == TargetPlatform.macOS ||
defaultTargetPlatform == TargetPlatform.windows ||
defaultTargetPlatform == TargetPlatform.linux;
if (isDesktop) {
return HoverListenerWidget(
child: OutlinedButton(onPressed: () {}, child: const Text('Desktop Action')),
);
}
return ElevatedButton(onPressed: () {}, child: const Text('Mobile Action'));
}
Web Embedder #
The Web Embedder is structurally radically different because it runs inside a browser sandbox environment without direct access to the machine’s operating system.
Currently, the Web Embedder provides two modern rendering options:
flowchart TD
subgraph WebPipelines["Flutter Web Rendering Options"]
direction TB
CanvasKit["CanvasKit Mode (Traditional)"] -->|"Dart to JS"| JSBundle["JS Bundle + Wasm CanvasKit (Skia)"]
JSBundle -->|"Render on WebGL Canvas"| CanvasKit_Out["Consistent but Large Size (~1.5MB)"]
WasmMode["Wasm Mode (Modern)"] -->|"Dart to Wasm"| WasmBundle["Wasm Bytecode + Skwasm (Impeller Wasm)"]
WasmBundle -->|"Render via WebGL2/WebGPU"| Wasm_Out["High Performance, Fast Startup & Efficient"]
end
style CanvasKit stroke:#ff9800,stroke-width:2px
style WasmMode stroke:#4caf50,stroke-width:2px- CanvasKit Mode: Compiles the Skia C++ library into WebAssembly (Wasm) binaries and renders all UI elements using WebGL. Produces visual rendering performance that’s 100% consistent with mobile, but the initial download size is around ~1.5MB, which is less SEO-friendly.
- Wasm Mode: Compiles your Dart code directly into Wasm binaries (not JavaScript). This mode uses a Skwasm-based rendering engine leveraging WebGL2 or WebGPU APIs for execution performance approaching native speed.
Custom Embedders — Flutter Anywhere #
Because the Flutter C++ Engine decouples itself from platform details and only communicates through a standardized C API, anyone can build a new Platform Embedder for any hardware.
Here’s a picture of minimal Flutter Engine initialization from a custom embedder using the C API:
// CORRECT: Initializing the C++ Engine using stable pointers from a custom embedder
void LaunchCustomFlutterApp() {
FlutterProjectArgs args = {};
args.struct_size = sizeof(FlutterProjectArgs);
args.assets_path = "/var/flutter/my_app/assets";
args.icu_data_path = "/var/flutter/icudtl.dat"; // Unicode text layout file
FlutterEngine engine = nullptr;
FlutterEngineResult result = FlutterEngineRun(
FLUTTER_ENGINE_VERSION,
&renderer_config, // GPU rendering configuration (Vulkan/Metal/GLES)
&args,
nullptr,
&engine
);
if (result == kSuccess) {
printf("Flutter Engine successfully launched on an IoT device!\n");
}
}
Several successful real-world custom embedder implementations in the industry today include:
- Toyota: Built a custom embedder to run the touchscreen infotainment system (in-dash infotainment) in the latest Toyota vehicles.
- Samsung Tizen: Wrote a custom embedder so Flutter apps can be installed and run on Tizen-based Smart TVs.
- Embedded Linux (elinux): A community project to run Flutter on Raspberry Pi-based IoT devices using DRM rendering without a desktop environment.
Platform Channels — The Dart-to-Native Bridge #
The Platform Embedder underpins the asynchronous Platform Channel communication system. When Dart calls a native system API, the Embedder acts as the data router translating binary messages across the runtime boundary.
sequenceDiagram
autonumber
participant Dart as Dart Code (UI Thread)
participant Engine as Flutter Engine (C++)
participant Emb as Platform Embedder (Native Host)
participant Native as Native Code (Kotlin / Swift)
Dart->>Engine: invokeMethod('getBatteryLevel')
Engine->>Engine: Serialize message to binary format
Engine->>Emb: Forward message via BinaryMessenger
Emb->>Native: Route message to registered MethodChannel handler
Native->>Native: Call OS Battery API (Android/iOS)
Native-->>Emb: Return result (Battery Level / Error)
Emb-->>Engine: Forward binary response
Engine-->>Dart: Deserialize response & resolve Future<int>Here’s a real Platform Channel implementation example:
// On the Dart side: Requesting battery info asynchronously
class BatteryInfoService {
static const _channel = MethodChannel('flutter.unisbadri.com/battery');
Future<int> fetchBatteryLevel() async {
try {
final int level = await _channel.invokeMethod('getBatteryLevel');
return level;
} on PlatformException catch (e) {
throw Exception('Failed to read battery: ${e.message}');
}
}
}
And here’s the message route handling on the Android Platform Embedder side (using Kotlin):
// On the Android side (Kotlin): Receiving messages and executing native APIs
import android.os.BatteryManager
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
private val CHANNEL = "flutter.unisbadri.com/battery"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result ->
if (call.method == "getBatteryLevel") {
val level = getAndroidBatteryLevel()
if (level != -1) {
result.success(level)
} else {
result.error("UNAVAILABLE", "Battery sensor not responding", null)
}
} else {
result.notImplemented()
}
}
}
private fun getAndroidBatteryLevel(): Int {
val batteryManager = getSystemService(BATTERY_SERVICE) as BatteryManager
return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
}
}
Summary #
- Bridge Definition — The Platform Embedder is a native shell acting as the initial host for the Flutter C++ Engine and Dart runtime on the local platform.
- Engine Task Isolation — The C++ Engine is platform-agnostic; all communication with graphics drivers, event loops, and OS lifecycles is fully managed by the Embedder.
- OS Language Implementations — Written in target platform native languages, like Java/Kotlin (Android), Swift/ObjC (iOS/macOS), C++ (Windows/Linux), and JS (Web).
- Pre-Warming Mechanism — Avoids the 100-200ms cold start delay on iOS/Android by initializing a
FlutterEngineinstance early in memory cache before the UI calls it.- Desktop Platform Support — The Desktop Embedder handles cursor pointer input models, hovering, right-clicks, resizable window layouts, and physical keyboard shortcuts.
- Web Embedder Abstractions — Provides accelerated rendering via CanvasKit (Wasm-Skia) plus modern Wasm Mode (Skwasm-Impeller) running directly on WebGL2/WebGPU.
- Embedder C API — Uses the stable C interface declared in
flutter_embedder.hto facilitate custom platform creation (like Toyota’s automotive systems).- Platform Channel Routing — Facilitates asynchronous binary communication (
MethodChannel) safely routing requests from the Dart UI thread to native OS APIs.