What is Flutter? #
Cross-platform app development has gone through an incredible transformation over the past decade. To ship an app on multiple operating systems like Android and iOS, development teams used to have to choose between top-notch performance at double the cost (native development) or cost efficiency with performance sacrificed (hybrid web-based development). This documentation dives deep into Flutter, a Google technology that turned that paradigm on its head by offering native-grade performance and the efficiency of a single codebase at the same time. We’ll break down the core concepts, the unique rendering mechanism, the internal architecture, and how to write your first structured Flutter code.
The Fundamental Definition of Flutter #
At its most basic level, Flutter is an open-source UI Software Development Kit (SDK) designed by Google for building beautiful, high-performance user interfaces that compile to native code for multiple platforms from a single codebase. Officially supported platforms include Android, iOS, Web, Windows, macOS, and Linux.
A common misconception is that Flutter is just a “UI framework” like React or Vue in the web world. In fact, Flutter is a complete SDK. This means that when you install Flutter, you get:
- A UI Framework: A rich collection of UI libraries, from simple buttons and text to complex components like animation systems, navigation, and gesture management.
- The Dart Programming Language: A modern, object-oriented language designed specifically to optimize client-side rendering.
- A C++ Rendering Engine: The graphics component responsible for drawing every UI element directly to the screen with no native OS intermediaries.
- Command Line Tools (CLI) & Compiler: The toolchain for compiling your code into native binary instructions (ARM/x64) for each target platform.
The core philosophy of building apps with Flutter is “Everything is a Widget”. In Flutter, almost everything you define in your code structure is a widget — from structural elements (like Scaffold, Row, Column), to visual elements (like Text, Image, Button), to behavioral or functional elements (like GestureDetector, Theme, MediaQuery). These widgets are arranged in a declarative tree hierarchy (widget tree), where the UI is automatically redrawn every time the data (state) inside it changes.
How Flutter Works Technically #
The fundamental difference between Flutter and other cross-platform technologies lies in how the UI is drawn and executed on the target device.
Most traditional cross-platform frameworks use one of two approaches:
- WebView Hybrid: The app is essentially a mini web app (HTML/CSS/JS) wrapped inside a native container (WebView). Communication with device sensors must go through a special API wrapper like Cordova or Capacitor. The UI is drawn by the operating system’s built-in browser engine.
- Native Bridge (React Native): Business logic is written in JavaScript, but the UI uses the operating system’s own native components (for example, a
<View>element in React Native is translated toandroid.view.Viewon Android andUIViewon iOS). This process requires an intermediary module (bridge) that translates JavaScript instructions to the native thread in real time.
Flutter rejects both approaches. Flutter brings its own canvas and rendering engine (Impeller or Skia) into the app.
When a Flutter app runs, the target operating system only provides an empty window (surface/canvas). The Flutter Engine then draws every pixel of the interface — including buttons, text, page transitions, and animations — independently on top of that window using GPU graphics acceleration (via graphics APIs like Vulkan on Android, Metal on iOS, or WebGL/WebAssembly on the Web).
This direct-to-canvas rendering mechanism is illustrated in the diagram below:
flowchart TD
subgraph WebViewBased["WebView Approach (Hybrid)"]
A["App Code (HTML/JS)"] -->|"Runs in"| B["WebView Container"]
B -->|"Rendered by"| C["OS Browser Engine"]
C -->|"Displayed on"| D["Device Screen"]
end
subgraph NativeBridge["Native Bridge Approach (React Native)"]
E["App Code (JavaScript)"] -->|"Sends instructions through"| F["JS Bridge (Bottleneck)"]
F -->|"Calls components"| G["Native OS Widgets (Android/iOS View)"]
G -->|"Displayed on"| H["Device Screen"]
end
subgraph FlutterDirect["Flutter Approach (Direct Rendering)"]
I["App Code (Dart)"] -->|"Compiled directly to"| J["Native Binary Code (ARM/x64)"]
J -->|"Instructs"| K["Graphics Engine (Impeller/Skia)"]
K -->|"Draws directly on"| L["Blank Canvas (GPU Surface)"]
L -->|"Displayed on"| M["Device Screen"]
end
style WebViewBased stroke:#f57c00,stroke-width:2px
style NativeBridge stroke:#d32f2f,stroke-width:2px
style FlutterDirect stroke:#388e3c,stroke-width:2pxBy eliminating the intermediary layer (bridge) and the OS’s built-in native components, Flutter solves three major problems:
- Perfect UI Consistency: Because Flutter draws its own pixels, your app’s UI on Android 9 will look exactly the same as on Android 14, or on the latest iOS. Operating system updates won’t break your app’s layout.
- Stable 60–120 FPS Performance: There’s no computational cost for shuffling data back and forth across a bridge. Dart’s Ahead-of-Time (AOT) compilation ensures code executes at native speed.
- Unlimited Customization: Because every element is drawn on a canvas, you can create highly complex custom designs, dynamic shadow effects, and custom animated transitions without being limited by the platform’s built-in native APIs.
Breaking Down the Three-Layer Architecture #
Flutter’s architecture is designed as a series of modular layers that complement each other. Each layer has a clearly defined abstraction and responsibility.
flowchart TD
subgraph Framework["Framework Layer (Dart)"]
direction TB
Material["Material & Cupertino (UI Components)"]
Widgets["Widgets (State & Lifecycle)"]
Render["Rendering (Layout, Paint, & Clip)"]
Base["Animation, Painting, Gestures, Foundation"]
Material --> Widgets --> Render --> Base
end
subgraph Engine["Engine Layer (C++)"]
direction TB
Graphics["Graphics Engine (Impeller / Skia)"]
VM["Dart VM & Runtime"]
TextCompositing["Text Layout & Compositing"]
end
subgraph Embedder["Platform Embedder (Native)"]
direction TB
OSLauncher["OS Launcher (Java/Kotlin/Swift/C++)"]
Surface["Surface & GPU Window Wrapper"]
end
Framework --> Engine
Engine --> Embedder
style Framework stroke:#0288d1,stroke-width:2px
style Engine stroke:#388e3c,stroke-width:2px
style Embedder stroke:#f57c00,stroke-width:2pxLet’s examine each of the layers above in depth:
1. Framework Layer (Dart) #
This topmost layer is written entirely in the Dart programming language and is the main area where you write your app code. It consists of:
- Foundation: The base library containing utility classes, data structures, and fundamental APIs like
ValueNotifierfor basic data reactivity. - Animation, Painting, & Gestures: Provides low-level abstractions for handling animated transitions, visual effect manipulation (such as clipping, blending, color filters), and recognizing user touch input (gesture recognition).
- Rendering: Responsible for computing the layout and painting of every widget. This layer builds a tree of render objects (RenderObject Tree) that interacts directly with the screen’s coordinate system layout.
- Widgets: The component abstraction layer that provides the
StatelessWidgetandStatefulWidgetclasses. This is where Flutter’s declarative concept is orchestrated. - Material & Cupertino: A collection of ready-to-use UI components implementing Google’s design guidelines (Material Design) and Apple’s (Cupertino/iOS style).
2. Engine Layer (C++) #
The Engine is Flutter’s technical heart, written in C++. This layer handles the heavy lifting behind graphics rendering, file and network input/output (I/O), text layout, and the Dart runtime architecture.
- Graphics Library: This is where the actual graphics rendering happens. Flutter previously used Skia, a mature and stable 2D graphics engine. However, since Flutter 3.x, Google has been gradually migrating to Impeller. Impeller was designed specifically for Flutter to take advantage of modern graphics APIs like Metal (iOS) and Vulkan (Android). The goal is to eliminate shader compilation jank (animation stuttering while shaders compile for the first time at runtime).
- Dart VM & Runtime: Provides the execution environment for your Dart code. In development, the Dart VM supports Just-in-Time (JIT) compilation to enable the Hot Reload feature. In production, the Dart VM acts as a lightweight wrapper for running binary code that has been compiled Ahead-of-Time (AOT).
- Text Layout: Handles the complexity of cross-language text typesetting (including font fallbacks, text styling, and non-Latin characters).
3. Platform Embedder (Native) #
The Platform Embedder is the bottommost layer, written in the native programming language specific to each target platform (Java/Kotlin for Android, Objective-C/Swift for iOS, C++ for Windows, macOS, and Linux, and JavaScript for the Web).
The Embedder acts as the host of the application. Its main tasks are:
- Providing a graphics surface (surface/window) where the Flutter Engine can draw its interface.
- Initializing the event loop and forwarding user input (screen taps, keyboard presses, orientation changes) to the Engine.
- Managing interactions with the operating system’s low-level services (such as memory management, local file access, and app lifecycle).
- Providing Platform Channels that allow Dart code to communicate with the platform’s native SDK code.
Comprehensive Comparison: Flutter vs Other Cross-Platform Frameworks #
Before adopting Flutter, it’s important to objectively compare it with the popular cross-platform alternatives available today so you understand its strengths and weaknesses.
| Evaluation Criteria | Flutter | React Native | Ionic | Xamarin / MAUI |
|---|---|---|---|---|
| Programming Language | Dart | JavaScript / TypeScript | JavaScript / HTML / CSS | C# / F# |
| Rendering Mechanism | Draws on its own canvas via Impeller/Skia | Translates to native OS components via Bridge/JSI | Uses WebView (the OS’s built-in browser engine) | Translates to native OS components |
| Graphics Performance | ✅ Very high (60–120 FPS with no bridge bottleneck) | ⚡ High (potential bridge communication bottleneck) | ⚠️ Moderate (limited by the OS browser engine’s performance) | ⚡ High |
| UI Appearance Consistency | ✅ 100% Identical (because it draws every pixel itself) | ⚠️ Needs adjustment (appearance follows the target OS components) | ✅ 100% Identical (CSS-based) | ⚠️ Needs adjustment (follows native OS rendering) |
| Desktop Support | ✅ Stable & Official (Windows, macOS, Linux) | ⚠️ Limited (depends on community/third-party contributions) | ⚠️ Limited (must be wrapped with Electron) | ✅ Stable & Official (Windows, macOS) |
| Developer Experience | ✅ Outstanding (instant Hot Reload, mature debugger) | ✅ Very good (Fast Refresh, but sometimes unstable) | ⚡ Good (browser-based live reload) | ⚠️ Moderate (slow compilation and hot reload) |
| Compilation Type | JIT (Development) & AOT (Production) | JIT (JavaScript Engine / Hermes) | Interpreter (JavaScript runtime in WebView) | AOT (iOS) & JIT/AOT (Android) |
| App File Size | ⚠️ Fairly large (bundles its own C++ engine, min. ~4-6MB) | ⚡ Moderate (~5-7MB) | ✅ Very small (just web files, min. ~2-3MB) | ⚠️ Very large (bundles the .NET runtime, min. ~12-15MB) |
Dissecting Your First Flutter App #
To understand how Flutter’s declarative architecture is applied in practical code, let’s break down the simplest possible Flutter program below. This code displays a blank page with a top navigation bar (AppBar) and a welcome text in the middle of the screen.
// Import Flutter's built-in Material Design library
import 'package:flutter/material.dart';
// The application's main entry point
void main() {
// Run our app's root widget
runApp(const MyApp());
}
// The main widget, which is Immutable (Stateless)
class MyApp extends StatelessWidget {
// Constant constructor for more efficient rendering
const MyApp({super.key});
@override
Widget build(BuildContext context) {
// MaterialApp provides global design components and the routing system
return MaterialApp(
title: 'Belajar Flutter',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const MyHomePage(),
);
}
}
// Our app's main page widget
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
// Scaffold provides the basic visual layout structure of a Material Design page
return Scaffold(
appBar: AppBar(
// Display the title in the top navigation bar
title: const Text('Belajar Flutter Home'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: const Center(
// Center places its child exactly in the middle of the blank screen area
child: Text(
'Hello, Flutter! 👋',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
),
);
}
}
Let’s break down the important symbols and code structure above:
1. main() and runApp()
#
Every Flutter app must have a main() function as the starting point of program execution. Inside this function, we call the global runApp(Widget app) function. This function takes the widget you give it, attaches it as the root of the app’s widget tree, and asks the Flutter Engine to prepare for the first rendering pass (inflation).
2. StatelessWidget
#
In the example above, the MyApp and MyHomePage classes are subclasses of StatelessWidget. This type of widget is immutable (it cannot be changed once created). This means all properties inside it must be constant (final). StatelessWidget is very efficient and fast to re-render because it doesn’t maintain dynamic internal state.
- When to use it? When the UI depends purely on the initial input data passed through the class constructor, with no interactive changes on screen.
3. The build(BuildContext context) Method
#
Every widget must implement the build method. This is where we define the UI structure declaratively. The method receives a BuildContext parameter that represents information about the widget’s specific location within the global widget tree. We use context to access global configuration like color themes, screen size, or data shared from higher up the widget tree.
4. MaterialApp and Scaffold
#
MaterialAppis the main wrapper widget that must be present at the very top of the app. This widget automatically configures important global features like the navigation system (routing), language localization, accessibility, and injects Google’s Material Design theme into all of its descendants.Scaffoldis a helper widget that acts as the structural skeleton of a page. It provides standard layout slots for placing a top navigation bar (appBar), the main content area (body), a floating action button (floatingActionButton), a bottom navigation bar (bottomNavigationBar), and even a side drawer menu (drawer).
Example of Widget Structure Mistakes (Anti-Pattern vs Solution) #
When building interfaces with nested widgets, it’s important to keep widget rebuilds efficient.
// ANTI-PATTERN: Writing rendering logic inside one giant StatelessWidget class
class BadWidgetTree extends StatelessWidget {
const BadWidgetTree({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
const Text('App Title'),
// ✗ ANTI-PATTERN: Using a local function to render helper UI
_buildComplexCard(),
],
),
),
);
}
Widget _buildComplexCard() {
// This function has no lifecycle of its own.
// If the main widget is rebuilt, every complex component here is force-rebuilt too,
// which can drop the frame rate if it contains expensive layout calculations.
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
const Text('Complex card description...'),
Image.network('https://example.com/logo.png'),
],
),
),
);
}
}
// CORRECT: Breaking complex UI components into separate, independent widget classes
class GoodWidgetTree extends StatelessWidget {
const GoodWidgetTree({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Column(
children: [
Text('App Title'),
// ✓ CORRECT: Calling a separate widget class with a const constructor
ComplexCardWidget(),
],
),
),
);
}
}
// A separate, modular, and efficient widget class
class ComplexCardWidget extends StatelessWidget {
// Using a constant constructor so this widget gets cached by the Flutter Engine
const ComplexCardWidget({super.key});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
const Text('Complex card description...'),
Image.network('https://example.com/logo.png'),
],
),
),
);
}
}
When Should You Choose Flutter? #
The decision to pick a technology should be based on business needs, the availability of human resources, and the technical constraints of the project you’re facing.
CHOOSE Flutter if:
✓ You want to launch a product on Android and iOS at the same time, quickly.
✓ Your app's UI design is unique, custom, and needs smooth animated transitions.
✓ Your team's budget is too limited to hire separate native Android and iOS developers.
✓ You want to reuse your business logic and UI code for Web and Desktop platforms in the future.
✓ You value an efficient Developer Experience powered by instant visual feedback (Hot Reload).
DON'T CHOOSE if:
✗ Your app relies heavily on very specific, brand-new low-level hardware APIs.
✗ App file size (APK/IPA) is a critical success metric (must be under 2-3 MB).
✗ Your app is almost entirely a passive web UI with little dynamic client-side interaction.
✗ Your team is already very strong in native development (Kotlin/Swift) and has no time for migration.
Flutter’s Trade-off Analysis #
Although Flutter offers many advantages, you should be objective about the following trade-offs:
- Binary File Size (App Size): An empty Flutter app is at least 4–6 MB on Android/iOS because it must bundle the C++ graphics engine and Dart runtime in its distribution package.
- Ecosystem Dependency: If an operating system launches a new sensor or hardware feature, you may have to wait days or weeks for the community or the Flutter team to release a suitable wrapper (plugin wrapper), unless you’re willing to write your own native integration code using platform channels.
Summary #
- A Complete UI Toolkit — Flutter is not just an ordinary UI framework, but a comprehensive Software Development Kit (SDK) from Google that includes a framework, a C++ engine, UI libraries, a compiler, and the Dart language.
- Self-Drawn Pixels — Flutter draws every interface element independently on a blank canvas using a graphics engine (Impeller/Skia) with GPU acceleration, without depending on native OS components.
- Three-Layer Architecture — The internal structure is neatly divided into the Framework Layer (Dart), the Engine Layer (C++), and the Platform Embedder (Native) for a clean separation of system responsibilities.
- One Codebase for Everything — Lets you build consistent apps for Android, iOS, Web, Windows, macOS, and Linux from a single unified codebase.
- High Performance Efficiency — Eliminates the traditional performance bottleneck (JavaScript Bridge) and compiles Ahead-of-Time (AOT) to achieve smooth 60–120 FPS performance.
- Declarative Approach — Applies a declarative UI paradigm where the interface layout is represented as a widget tree that responds reactively to data changes.