Flutter is the only mobile framework that ships a custom rendering engine — Impeller — with your app rather than depending on platform widgets. That choice means pixel-perfect consistency across iOS and Android at the cost of a larger binary. In 2026, Flutter 3.22 extends that consistency to web and desktop. The trade-off is real: you get more rendering control and less access to native UI components. For most apps that matters less than it sounds.
What changed in 2026
- Impeller is the default everywhere. Impeller replaced Skia as the default renderer on iOS in Flutter 3.10 and Android in 3.16. Shader compilation jank — the stuttering on first render that plagued Flutter for years — is gone.
- Dart 3.4 sealed classes and patterns. Exhaustive pattern matching and sealed classes make state modelling cleaner.
switch expressions and if-case make Dart feel much more expressive.
- Flutter GPU API (preview). Direct GPU access for custom rendering — useful for games or data visualisations, still experimental.
- Riverpod 2 is the consensus state solution. It replaced the original Provider without the boilerplate of BLoC for most use cases.
Learning Dart first
Dart is approachable if you know TypeScript or Java. Key points:
// Dart 3.4 — sound null safety, pattern matching
void main() {
final nums = [1, 2, 3, null];
final doubled = nums.whereType<int>().map((n) => n * 2).toList();
print(doubled); // [2, 4, 6]
}
// Sealed classes for exhaustive matching
sealed class Shape {}
class Circle extends Shape { final double radius; Circle(this.radius); }
class Rect extends Shape { final double w, h; Rect(this.w, this.h); }
double area(Shape s) => switch (s) {
Circle(:final radius) => 3.14159 * radius * radius,
Rect(:final w, :final h) => w * h,
};
Spend 3–5 days on Dart before touching Flutter widgets. The official Dart tour at dart.dev takes about four hours.
The widget model
Flutter builds UI as a tree of immutable widgets. Widgets describe what to render; the framework diffs trees and updates the render layer.
class CounterApp extends StatefulWidget {
@override
State<CounterApp> createState() => _CounterAppState();
}
class _CounterAppState extends State<CounterApp> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: Text('$_count', style: const TextStyle(fontSize: 48))),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() => _count++),
child: const Icon(Icons.add),
),
);
}
}
setState triggers a rebuild of only the subtree that changed. This is the core mechanism before you add any state management library.
State management with Riverpod 2
import 'package:flutter_riverpod/flutter_riverpod.dart';
final counterProvider = StateProvider<int>((ref) => 0);
class CounterWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('$count');
}
}
Riverpod providers are global, lazy, testable, and disposable. They replace InheritedWidget boilerplate entirely.
Comparison: Flutter approaches
| Tool |
Purpose |
2026 status |
setState |
Local UI state |
Always valid for simple cases |
| Riverpod 2 |
App-wide state |
Recommended default |
| BLoC / Cubit |
Event-driven state |
Valid, higher ceremony |
| GetX |
State + routing |
Still popular, opinionated |
| Zustand-style (Signals) |
Fine-grained reactivity |
Emerging alternative |
How to pick Flutter vs React Native
| You prefer |
Choose |
| JavaScript / React |
React Native |
| Pixel-perfect custom UI |
Flutter |
| Single codebase for desktop too |
Flutter |
| Larger native plugin ecosystem |
React Native |
| Web performance |
React Native (React Native Web) |
Common mistakes
Calling setState at the wrong scope. Calling setState on a parent widget rebuilds the entire subtree. Extract widgets as early as possible to minimise rebuild scope.
Using Column without a Sized parent. Column in an unconstrained vertical space throws a render overflow error. Wrap in Expanded or a fixed-height container.
Not using const constructors. const widgets are cached and skip the rebuild entirely. Add const to every widget instantiation that does not depend on runtime data.
Ignoring flutter analyze. Dart's static analyser catches null safety violations, missing awaits, and unused imports. Run it in CI before merging.
What to skip
- Flutter Web for performance-critical apps. Flutter Web uses canvas rendering, which produces a DOM-free experience that is inaccessible without extra work. For content-heavy sites, Next.js is a better choice.
- Provider package for new projects. Provider is effectively superseded by Riverpod. Learning it first creates habits to unlearn.
- Manual platform channel code for most device APIs. The
pub.dev plugin ecosystem covers camera, Bluetooth, biometrics, and geolocation. Write platform channels only when no plugin exists.
FAQ
Is Flutter production-ready in 2026?
Yes. Google, Alibaba, BMW, and hundreds of other companies ship production Flutter apps. The Impeller renderer resolved the primary performance concern.
How large are Flutter apps?
A minimal Flutter app is ~7 MB on Android (release), ~20 MB on iOS. This is larger than native but smaller than React Native + JS bundle for complex apps.
Can Flutter target desktop?
Yes — macOS, Windows, and Linux are stable targets. Wayland support on Linux improved significantly in Flutter 3.19.
What is the best IDE for Flutter?
VS Code with the Flutter extension is the most popular. Android Studio provides better widget inspector integration. Both are free.
Where to go next
Deepen your mobile fundamentals with how to learn React Native in 2026 for comparison, how to handle API errors in 2026 for production data-layer patterns, and how to set up a dev environment in 2026 to optimise your Flutter toolchain.