Dart and Flutter: The Cross-Platform Mobile Stack That Actually Delivers
A practical guide to Dart and Flutter for cross-platform mobile development — how it works, where it shines, where it doesn't, and how to get started building real apps.
Cross-platform mobile development has been promising "write once, run everywhere" for over a decade. Most of those promises were broken. Cordova wrapped web apps in a native shell and they felt like web apps in a native shell. React Native bridged JavaScript to native widgets and the bridge became the bottleneck. Xamarin required you to write C# and still often needed platform-specific code.
Flutter took a different approach: skip the bridge entirely, render everything from scratch using its own rendering engine, and use Dart — a language most developers hadn't heard of — as the programming language. It sounded like a bad idea. It turned out to be a good one.
Why Dart
This is the first question everyone asks, and it's fair. Why not JavaScript? Why not Kotlin? Why a relatively obscure language developed by Google?
Dart was chosen because it has specific properties that Flutter needs:
Ahead-of-time (AOT) compilation for production. Dart compiles to native ARM and x86 code. No interpreter, no JIT warmup, no bridge to cross. The result is startup times and runtime performance comparable to native apps. Just-in-time (JIT) compilation for development. During development, Dart runs in a VM with hot reload — you change code, and the app updates in under a second without losing state. This is Flutter's killer developer experience feature. Sound null safety. Dart's type system prevents null reference errors at compile time. If a variable can be null, the type system forces you to handle it. Single-threaded with isolates. Dart is single-threaded like JavaScript, which simplifies UI programming (no race conditions on widget state). For CPU-intensive work, it uses isolates — like threads but with no shared memory, so no locks or mutexes needed. Garbage collected. Automatic memory management with a generational GC optimized for the short-lived object allocation patterns common in UI frameworks.Dart isn't the world's most exciting language on its own. But for Flutter's specific requirements — fast development cycle, fast production performance, safe typing, and UI-friendly concurrency model — it's a remarkably good fit.
What Dart Code Looks Like
If you know Java, Kotlin, TypeScript, or C#, Dart will feel immediately familiar:
// Dart basics — classes, null safety, async/await
class User {
final String name;
final String email;
final int? age; // nullable — might not have age
User({required this.name, required this.email, this.age});
String get displayName {
if (age != null) {
return '$name (age $age)';
}
return name;
}
}
Future<List<User>> fetchUsers() async {
final response = await http.get(Uri.parse('https://api.example.com/users'));
final List<dynamic> data = jsonDecode(response.body);
return data.map((json) => User(
name: json['name'],
email: json['email'],
age: json['age'],
)).toList();
}
// Pattern matching (Dart 3+)
String describeShape(Shape shape) => switch (shape) {
Circle(radius: var r) when r > 10 => 'Large circle',
Circle(radius: var r) => 'Small circle with radius $r',
Rectangle(width: var w, height: var h) => 'Rectangle ${w}x$h',
_ => 'Unknown shape',
};
// Records — lightweight data structures
(String, int) getUserInfo() => ('Alice', 30);
void main() {
var (name, age) = getUserInfo();
print('$name is $age years old');
}
Dart 3 added sealed classes, pattern matching, and records, which brought it much closer to Kotlin in expressiveness. It's a language that keeps getting better.
How Flutter Actually Works
This is important to understand because it explains both Flutter's strengths and its limitations.
Most cross-platform frameworks try to map to native UI components. React Native's becomes a UIView on iOS and an android.view.View on Android. This means your app looks native, but the bridge between JavaScript and native code introduces latency and complexity.
Flutter doesn't use native UI components at all. It ships its own rendering engine (Impeller, replacing the older Skia-based engine) that draws every pixel directly. A Flutter Button isn't a native iOS UIButton or Android MaterialButton — it's a widget that Flutter draws from scratch using the GPU.
The implications:
- Pixel-perfect consistency: your app looks identical on iOS and Android (or as different as you want it to — Flutter has both Material and Cupertino widget libraries).
- No bridge bottleneck: there's no serialization between your code and the UI. Everything runs in the same process.
- Custom UI is easy: since Flutter already draws everything, fancy animations and custom designs don't require "escaping" into platform-specific code.
- Platform conventions may differ: a Flutter app might not feel 100% like a native iOS app because it's not using native iOS controls. This matters more on iOS, where users have strong expectations about UI behavior.
Building a Flutter App
Flutter uses a declarative, widget-based UI model. Everything is a widget — layouts, buttons, padding, gestures, animations.
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: 'Task Manager',
theme: ThemeData(
colorSchemeSeed: Colors.indigo,
useMaterial3: true,
),
home: const TaskListScreen(),
);
}
}
class TaskListScreen extends StatefulWidget {
const TaskListScreen({super.key});
@override
State<TaskListScreen> createState() => _TaskListScreenState();
}
class _TaskListScreenState extends State<TaskListScreen> {
final List<String> _tasks = ['Buy groceries', 'Write blog post', 'Deploy app'];
final TextEditingController _controller = TextEditingController();
void _addTask() {
if (_controller.text.isNotEmpty) {
setState(() {
_tasks.add(_controller.text);
_controller.clear();
});
}
}
void _removeTask(int index) {
setState(() {
_tasks.removeAt(index);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Tasks')),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Add a task...',
border: OutlineInputBorder(),
),
onSubmitted: (_) => _addTask(),
),
),
const SizedBox(width: 8),
FilledButton(
onPressed: _addTask,
child: const Text('Add'),
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: _tasks.length,
itemBuilder: (context, index) => ListTile(
title: Text(_tasks[index]),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => _removeTask(index),
),
),
),
),
],
),
);
}
}
If you've used React, the mental model is similar — UI is a function of state, and when state changes, the framework efficiently updates what's on screen. The setState call triggers a rebuild of the widget tree, but Flutter is smart about only re-rendering what actually changed.
State Management — The Inevitable Discussion
Flutter has approximately 47 state management solutions. This is both a strength (you can pick what fits) and a weakness (analysis paralysis). Here's the practical guide:
For simple apps:setState and InheritedWidget (or Provider, which wraps InheritedWidget). Don't overthink it.
// Provider — the most common state management for Flutter
class TaskNotifier extends ChangeNotifier {
final List<String> _tasks = [];
List<String> get tasks => List.unmodifiable(_tasks);
void add(String task) {
_tasks.add(task);
notifyListeners();
}
void remove(int index) {
_tasks.removeAt(index);
notifyListeners();
}
}
For medium apps: Riverpod (Provider's successor by the same author) gives you compile-safe dependency injection and better testability.
For complex apps: Bloc provides a structured pattern with events, states, and clear separation of business logic from UI. It's verbose but predictable.
What to actually do: Start with Provider or Riverpod. Switch to Bloc only if your app's complexity demands it. Don't start with Bloc for a todo app.
Flutter Beyond Mobile
Flutter now targets six platforms: iOS, Android, web, Windows, macOS, and Linux. The mobile experience is polished and production-ready. The other platforms are at different maturity levels.
Web: works, but has tradeoffs. Flutter web apps are larger than equivalent React/Vue apps (the rendering engine must be downloaded), SEO is limited, and accessibility support is improving but not at parity with HTML. Good for web apps (dashboards, tools), not great for content sites. Desktop (Windows, macOS, Linux): maturing rapidly. The rendering is solid, platform integrations are growing, and apps like the Google Ads desktop client prove it's production-viable. But the ecosystem of desktop-specific plugins is smaller than mobile. Embedded: Flutter is showing up on embedded devices — car infotainment systems, smart displays, kiosks. Toyota uses Flutter for in-vehicle systems.The practical advice: use Flutter confidently for mobile. Evaluate carefully for web and desktop — it might be perfect for your use case, or a native approach might serve you better.
Comparison to Alternatives
Flutter vs React Native: React Native uses JavaScript/TypeScript and maps to native UI components. Flutter uses Dart and renders everything with its own engine. React Native apps look more "native" by default; Flutter apps are more visually consistent across platforms. React Native has a larger community and more third-party libraries. Flutter has better performance for complex animations and custom UIs. For most business apps, both are fine choices. For heavily custom UIs, Flutter wins. For leveraging an existing JavaScript/TypeScript team, React Native wins. Flutter vs Kotlin Multiplatform (KMP): KMP shares business logic across platforms but uses native UI on each platform (Jetpack Compose on Android, SwiftUI on iOS). This gives you fully native UIs but means writing UI code twice. KMP is ideal when platform-native look and feel is critical and you have strong native development skills. Flutter is ideal when development speed and visual consistency matter more. Flutter vs native (Swift + Kotlin): Native always wins on platform integration, API access, and "feeling right" on the platform. But native means two codebases, two teams, and double the development time. Flutter's 80-90% code sharing across platforms is a compelling tradeoff for most teams.The Honest Downsides
App size. A minimal Flutter app is 5-10 MB. A minimal native app might be 1-2 MB. The rendering engine adds baseline overhead. For most apps this doesn't matter, but if you're targeting markets with limited storage or slow connections, it's a consideration. Not truly native UI. Flutter simulates platform UI conventions but doesn't use native components. Subtle differences in scrolling physics, text selection, and accessibility behavior exist. iOS users tend to be pickier about this than Android users. Dart's ecosystem is smaller. Fewer libraries than JavaScript, Python, or Kotlin. The pub.dev package repository has grown significantly, but you'll occasionally find a gap where a critical library doesn't exist or isn't well maintained. Hot reload has limits. It's great for UI tweaks but can't handle all code changes. Sometimes you need a full restart, which takes 10-30 seconds on a physical device.Getting Started
# Install Flutter SDK
# Download from flutter.dev or use a version manager
# Verify installation
flutter doctor
# Create a new project
flutter create my_app
cd my_app
# Run on connected device or emulator
flutter run
# Run on Chrome (web)
flutter run -d chrome
Flutter's tooling is excellent. flutter doctor checks your environment and tells you exactly what's missing. The VS Code and Android Studio extensions provide hot reload, widget inspection, and performance profiling.
Start with the official codelabs at flutter.dev. Build the counter app, then build a list-based app, then add navigation and HTTP requests. The learning curve is gentle if you're comfortable with any typed language.
The key mental shift is thinking in widgets. Everything is a widget. Layout is widgets. Styling is widgets. Gestures are widgets. Once that clicks, building UIs becomes fast and intuitive.
Explore Dart and Flutter development with hands-on coding challenges on CodeUp.