Core concepts &
mental model
You already think in components, state, and routing. Flutter maps to all of it — but the rendering engine is completely different, and that changes everything.
The parallel universes
Next.js world
You write JSX that maps to HTML elements. The browser's layout engine handles rendering. CSS positions elements, the DOM is mutated, React reconciles virtual DOM diffs.
Flutter world
You write widget trees that map to drawn shapes. Flutter's engine renders to a canvas — no browser, no HTML. Layout is handled by Flutter's own constraint system.
File equivalents
CLI commands
Widgets are just
components
A widget is Flutter's React component. Same idea: composable, reusable UI primitives arranged in a tree. The API looks different but the mental model is identical.
Common widget equivalents
App lifecycle
in depth
Click any lifecycle event on the phone simulator to see exactly what fires on Android, iOS, and inside Flutter — with the Dart code that handles it.
AppLifecycleState enum — but each platform has subtleties that affect when and
how you respond. Click each scenario below to explore.
to simulate it
Widget-level lifecycle vs React hooks
paused.
Save critical data in inactive. Restore connections in resumed.
Never rely on detached — it may not fire on iOS at all.Navigation
in depth
Flutter's navigation is a physical back-stack of screens. Use the interactive simulator below to push, pop, replace, and nest routes — and see exactly what code each operation generates.
go_router adds URL-style path matching on top of this stack. Use the simulator
below to feel the difference between every navigation operation.
go_router vs Next.js — setup comparison
Returning data from a screen — a Flutter superpower
await the result when the
user pops. Like a Promise that resolves when the user dismisses the screen. Try the
pop(result) operation in the simulator above.
State
management
Flutter has three tiers: setState for local UI, Riverpod for shared global state, and BLoC for complex event-driven business logic. Each maps to something you already know from React.
setState
Built-in to StatefulWidget. For UI-only state: toggles, form inputs, tab selection.
≈ useStateRiverpod
Type-safe providers, auto-dispose, works outside BuildContext. The modern recommended solution.
≈ Zustand / JotaiBLoC / Cubit
Event-driven stream-based state. Strict separation of UI and logic. Great for large teams.
≈ Redux + RTKref.watch() =
subscribe and rebuild. ref.read() = one-time read without subscribing (use
inside callbacks). ref.listen() = run a side-effect when state changes,
like useEffect([dep]) in React.items: [...state.items, event.item],
total: state.total + event.item.price,
))
items: items.where(i ≠ id),
total: total - item.price,
))
await placeOrder() →
emit(success | error)
Data
persistence
Mobile apps need offline-first storage. Flutter has a rich ecosystem — from simple key-value to full SQL, all via pub.dev packages.
| Use case | Flutter package | Web equivalent | Notes |
|---|---|---|---|
| Simple key-value | shared_preferences | localStorage | Sync-like API. Strings, bools, ints. Great for settings, theme. |
| Secure storage | flutter_secure_storage | httpOnly cookie | Android Keystore / iOS Keychain. For JWT tokens, passwords. |
| Local SQL | sqflite | SQLite (server-side) | Full SQLite on device. Raw SQL queries. |
| ORM / type-safe DB | drift | Prisma / Drizzle | Code-gen schema, typed queries, reactive streams. |
| NoSQL document | isar | Firestore / MongoDB | Very fast. Schema in Dart. Great for offline-first apps. |
| File storage | path_provider | fs (Node.js) | Gets app directories: documents, temp, cache. |
| Fast cache | hive | SWR / RQ cache | No codegen. Boxes = tables. Great for caching API responses. |
pubspec.yaml
deep dive
One file replaces package.json, next.config.js, .nvmrc, and your asset pipeline — annotated for Next.js developers.
1.0.0+1 — before + is
the user-facing semver, after is the build number. App stores use the build number for
internal versioning. Increment it on every release. No web equivalent — unique to mobile
distribution.Dart for
JS developers
Dart feels like TypeScript with stricter null safety and no build toolchain anxiety. Most JS patterns translate directly — with some pleasant surprises.
The Dart features JS doesn't have
Named parameters
Flutter widgets use named params by default. required makes them mandatory.
This is why Flutter widget calls read like English:
Sound null safety
Stricter than TypeScript. String? can be null. String cannot —
the compiler enforces it, no runtime surprises.
String must = null; // ❌ compile error
Pattern matching (Dart 3)
Switch expressions with exhaustive matching — like TypeScript discriminated unions but built into the language:
Status.ok => 'All good',
Status.error => 'Failed',
_ => 'Unknown',
};
Extension methods
Add methods to any type — even SDK types — without subclassing. Scoped to import.
bool get isEmail => contains('@');
}
'user@email.com'.isEmail; // true