Quick take: Flutter is Google's open-source UI framework for one-codebase apps that ship to iOS, Android, web, Windows, macOS, and Linux. You write in Dart, the framework renders with Skia (transitioning to Impeller), and the developer experience centres on sub-second hot reload. Compared to React Native, Flutter draws every pixel itself instead of bridging to native widgets, which gives consistent visuals at the cost of native-look fidelity, structured as a tutorial, with practical examples, an overview of each option.
Flutter shipped 1.0 in December 2018. By the State of Flutter survey in 2024, over 1 million apps had been published using it, and the framework supports six target platforms from one codebase. That's a real productivity story, but the trade-offs matter. Flutter isn't a magic free lunch, it's a specific architectural bet that pays off in some domains and hurts in others.
I've shipped two Flutter apps for clients (a fitness app and an internal logistics tool) and tried Flutter for web on a third project. Honest take: I'd reach for Flutter again for the same kinds of apps. I wouldn't pick it for a graphics-heavy game, a deeply native-feeling iOS app, or a content-driven website. The framework's choices push you toward certain kinds of products and away from others.
What Is Flutter and Why Does It Exist?
Flutter is a UI framework, not a mobile platform. It runs on top of whichever OS the user has, draws its own widgets onto a canvas via Skia (or Impeller, the next-gen renderer), and compiles your Dart code to native ARM machine code for release builds. The pitch: write once, ship everywhere, without losing performance.
The "draw your own pixels" decision is what makes Flutter different. React Native, Xamarin, and Cordova all bridge from a non-native language to platform widgets, you write JS or C#, and a UIButton or an Android Button appears under the hood. Flutter skips that bridge entirely. The button you see on screen is a Flutter-drawn button, not a UIButton.
Why does that matter? Two reasons:
- Visual consistency: your app looks the same on iOS 17 and Android 14, on Pixel and on Samsung. Designers love this.
- No bridge tax: no marshalling data between JS and native, which used to be React Native's main performance ceiling.
Trade-off: your iOS app doesn't feel exactly like a native iOS app. Cupertino widgets help (Flutter ships an iOS-style component library), but the small details (the spring animation on a UISegmentedControl, the haptic on a UIPicker) need work to replicate. Some teams care, some don't.
How Does Flutter Use Dart?
Dart is the language. It's a Google-designed, class-based, sound-null-safe language with a syntax close to a mix of TypeScript and Java. The full language tour at dart.dev/language is the canonical reference. Quick taste:
void main() {
final greeting = sayHello('World');
print(greeting);
}
String sayHello(String name) {
return 'Hello, $name!';
}
class User {
final String name;
final int age;
const User({required this.name, required this.age});
String describe() => '$name, age $age';
}
A few things to notice:
finaldeclares a one-time-assignable variable (the equivalent of Kotlin'sval)- String interpolation uses
$variableor${expression} - Constructors can use named parameters with
requiredand default values - Single-expression methods use
=>instead of{ return ...; } - Sound null safety means
Stringis non-nullable,String?is nullable, enforced at compile time
Dart compiles two ways: JIT for development (gives you hot reload) and AOT for release (compiles to ARM machine code, ships in your IPA or APK). That dual-mode is the technical underpinning of Flutter's developer experience.
The downside of Dart: it's almost exclusively a Flutter language. Outside Flutter, the Dart ecosystem is small. If you stop doing Flutter, your Dart skills don't transfer to many other jobs. Worth knowing before you commit a year of your career to it.
What Is the Widget Tree?
Everything in Flutter is a widget. Buttons are widgets. Padding is a widget. Centering is a widget. Even your app's root is a widget. The entire UI is a tree of nested widgets, rebuilt whenever state changes.
A minimal Flutter app:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Hello Flutter',
home: Scaffold(
appBar: AppBar(title: const Text('Hello')),
body: const Center(
child: Text('Hello, Flutter', style: TextStyle(fontSize: 24)),
),
),
);
}
}
The tree here: MyApp -> MaterialApp -> Scaffold -> (AppBar + Center) -> Text. Every node implements a build method that returns more widgets. There's no XML, no separate template language, no JSX-style hybrid, just Dart code.
StatelessWidget vs StatefulWidget
Two flavours of widget matter day to day:
StatelessWidget: pure function of its inputs. Rebuilds when the parent rebuilds it with new constructor args. No internal state.StatefulWidget: holds mutable state in a pairedStateobject. CallssetStateto trigger a rebuild.
Quick counter example:
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int count = 0;
void increment() {
setState(() {
count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $count'),
ElevatedButton(onPressed: increment, child: const Text('Add')),
],
);
}
}
That's the entire stateful pattern. setState schedules a rebuild on the next frame, the framework diffs the new widget tree against the old, and only the changed parts actually repaint. For state that lives beyond one widget, real apps add a state-management library, Provider, Riverpod, Bloc, or signals-style Solid Signals. Each has its fans, and the Flutter docs cover the trade-offs at docs.flutter.dev/data-and-backend/state-mgmt/options.
How Does Hot Reload Actually Work?
Hot reload injects new Dart source into the running Dart VM, rebuilds the widget tree from the current root, and preserves your app's state. Press r in the terminal (or save in your editor with auto-reload) and the change appears in under a second.
Why does this matter? Because UI work is iteration-heavy. Tweak a colour, check the result. Adjust a spacing, check again. With native iOS or Android, every change is a full rebuild and restart, maybe 30 seconds. With Flutter hot reload, it's under one second. Over a day of UI work, that compounds into hours saved.
When hot reload doesn't work: changes to main(), top-level fields, enums, generic type parameters, or class hierarchies. For those, you need a hot restart (R), which is slower but still much faster than a full rebuild.
In my experience, hot reload is the single feature most likely to convert a sceptical developer. Watch someone tweak a button colour and see it apply mid-app without losing state, and the case for Flutter makes itself.
Flutter vs React Native vs Kotlin Multiplatform
The three obvious cross-platform options in 2026. The honest comparison:
| Flutter | React Native | Kotlin Multiplatform | |
|---|---|---|---|
| Language | Dart | JavaScript or TypeScript | Kotlin |
| UI rendering | Skia, custom widgets | Native widgets via bridge | Native UI (SwiftUI / Compose) |
| Code sharing | UI + logic, all platforms | UI + logic, mostly mobile | Logic only, UI is native |
| Performance | Near-native, AOT compiled | Good with new architecture | Native (since UI is native) |
| Native look | Needs work for iOS feel | Inherits from native | Native by definition |
| Best for | Branded UI, custom design | Web-team-friendly mobile | Shared business logic, native UI |
There's no universal winner. Flutter wins when you have a distinct brand and want pixel-perfect consistency across platforms. React Native wins when your team is already a React shop and the app is mostly views over an API. Kotlin Multiplatform wins when you want to ship truly native UI on each platform but share the data layer, the choice for teams that already have separate iOS and Android specialists.
I picked Flutter for the fitness app because the client had a strong brand and wanted the same visual feel everywhere. I'd pick KMP today for a team that already has both iOS and Android engineers and doesn't want to throw out their existing native UI work.
What Should You Build First?
Run flutter create hello_flutter, open the project in VS Code or Android Studio, and start the default counter app. That generated app is the closest thing Flutter has to a Hello World, and it shows StatefulWidget, the build method, and hot reload all in one. Modify the increment to multiply, save, and watch the running app update.
From there, the typical next steps: add a Container widget around the text, swap Column for Row, try a ListView of items. Container is the layout widget you'll reach for most, and the linked guide below covers every property worth knowing. Build five or six small UI samples and the widget mental model clicks fast.
Related
- Container in Flutter - the layout box you use for padding, margins, colour, borders, and decoration
- Android Introduction - the Android platform context for cross-platform comparisons
- Android Kotlin Introduction - Kotlin language basics, relevant if you're weighing KMP against Flutter
- TypeScript Generics Explained - relevant if you're coming from React Native and considering the typing model