This roadmap is about Flutter Developer
Flutter Developer roadmap starts from here
Advanced Flutter Developer Roadmap Topics
By Dhaval K.
9 years of experience
My name is Dhaval K. and I have over 9 years of experience in the tech industry. I specialize in the following technologies: React Native, Mobile App Development, React, JavaScript, Android App Development, etc.. I hold a degree in Master of Computer Applications (MCA), Bachelor of Business Administration (BBA). Some of the notable projects I’ve worked on include: Holy Reads: Your Companion for Spiritual Growth and Daily Inspiration, UBMe: Real-Time Check-In and Chat for Enhanced Social Connectivity, Shake Hands: Your Ultimate Pet Care and Shopping Companion, NestEase Property Management Platform Development, Suncart - Grocery Delivery, etc.. I am based in Mumbai, India. I've successfully completed 15 projects while developing at Softaims.
My expertise lies in deeply understanding and optimizing solution performance. I have a proven ability to profile systems, analyze data access methods, and implement caching strategies that dramatically reduce latency and improve responsiveness under load. I turn slow systems into high-speed performers.
I focus on writing highly efficient, clean, and well-documented code that minimizes resource consumption without sacrificing functionality. This dedication to efficiency is how I contribute measurable value to Softaims’ clients by reducing infrastructure costs and improving user satisfaction.
I approach every project with a critical eye for potential bottlenecks, proactively designing systems that are efficient from the ground up. I am committed to delivering software that sets the standard for speed and reliability.
key benefits of following our Flutter Developer Roadmap to accelerate your learning journey.
The Flutter Developer Roadmap guides you through essential topics, from basics to advanced concepts.
It provides practical knowledge to enhance your Flutter Developer skills and application-building ability.
The Flutter Developer Roadmap prepares you to build scalable, maintainable Flutter Developer applications.

What this section is about Flutter is Google’s open UI toolkit for crafting natively compiled applications for mobile, web, and desktop from one Dart codebase.
Flutter is Google’s open UI toolkit for crafting natively compiled applications for mobile, web, and desktop from one Dart codebase. You describe interfaces as composable widgets; the engine paints pixels with Skia/Impeller, so you are not limited to stock platform controls.
This section orients you to the widget model, how UI updates flow, and how tooling like hot reload shortens the feedback loop compared with full restarts on every tweak.
You install the Flutter SDK, use flutter create for a starter app, and run flutter run on a device or simulator. The main() entry calls runApp, which mounts a root widget tree; everything below is nested widgets.
Hot reload injects Dart changes into the VM while keeping state where possible; hot restart rebuilds from scratch when you change main, globals, or init logic. Pair the official docs with small experiments in lib/main.dart to internalize the loop.
import 'package:flutter/material.dart';
void main() => runApp(
const MaterialApp(
home: Scaffold(
body: Center(child: Text('Hello, Flutter')),
),
),
);
What is a StatelessWidget and StatefulWidget? Almost everything in Flutter’s UI is a widget. A StatelessWidget is immutable: its configuration is fixed for a given build.
Almost everything in Flutter’s UI is a widget. A StatelessWidget is immutable: its configuration is fixed for a given build. A StatefulWidget splits into a configuration object and a mutable State subclass that can call setState to rebuild descendants when data changes.
Use StatelessWidget for pure presentation driven by constructor arguments. Use StatefulWidget when taps, timers, or animations need to change what the user sees without rebuilding the whole route manually.
Flutter calls build whenever the element tree needs an update. For stateful widgets, setState schedules a rebuild of that subtree; keep build cheap and push heavy work out of it.
Prefer lifting state up or using dedicated state-management packages when multiple screens share the same mutable data—local setState scales only so far.
class Greeting extends StatelessWidget {
const Greeting({super.key, required this.name});
final String name;
@override
Widget build(BuildContext context) {
return Text('Hello, $name');
}
}
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0;
@override
Widget build(BuildContext context) {
return FilledButton(
onPressed: () => setState(() => _count++),
child: Text('Count: $_count'),
);
}
}
What is hot reload and hot restart? Hot reload pushes updated library code into the running Dart VM and re-executes build methods so you see UI changes quickly.
Hot reload pushes updated library code into the running Dart VM and re-executes build methods so you see UI changes quickly. Application state in memory often survives, which is ideal for tuning layout and colors.
Hot restart tears the app down and starts again from main(), clearing in-memory state. Use it when you change static initializers, global singletons, or native plugin registration that only runs at startup.
Trigger reload from your IDE or press r in the flutter run console; R performs restart. If behavior looks “stuck,” assume you need a restart rather than fighting phantom state.
Profile release builds separately—debug hot reload never ships to users, and performance characteristics differ.
What is layout in Flutter? Layout is composition: you nest Row, Column, Stack, Flex, and sizing widgets like Expanded and Flexible to describe constraints and positions.
Layout is composition: you nest Row, Column, Stack, Flex, and sizing widgets like Expanded and Flexible to describe constraints and positions. Parents pass constraints down; children report sizes up.
Material and Cupertino libraries provide themed shells—Scaffold, AppBar, SafeArea—so you align with platform expectations while sharing most code.
Start from the outer constraints (often full screen), then subdivide. Use MainAxisAlignment and CrossAxisAlignment on flex widgets instead of hard-coded pixel stacks when possible.
When something overflows, read the error: Flutter paints yellow/black stripes and logs which widget violated max constraints.
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text('Title', style: TextStyle(fontSize: 20)),
Expanded(
child: Container(color: Colors.blue.shade50),
),
],
);
}
What is Navigator-based navigation? The Navigator manages a stack of routes—screens pushed on top and popped to go back.
The Navigator manages a stack of routes—screens pushed on top and popped to go back. Imperative APIs like push and pop mirror mobile user expectations and work with MaterialPageRoute or custom PageRoute subclasses.
For larger apps, teams adopt go_router or similar for URL-driven, declarative routing—especially on web—but the stack mental model remains foundational.
Obtain a Navigator with Navigator.of(context) or pass a callback. Pass data via constructors or RouteSettings.arguments, and type-check in the destination.
Prefer pop with a result value when returning a selection to the previous screen instead of shared globals.
void openDetail(BuildContext context) {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (context) => const DetailScreen(),
),
);
}
What is state management in Flutter? State management is how you connect data to widgets. setState is the built-in primitive for local ephemeral UI state.
State management is how you connect data to widgets. setState is the built-in primitive for local ephemeral UI state. Shared or async-driven data usually moves into InheritedWidgets, Listenables, or packages like Provider, Riverpod, or bloc.
Good state design keeps build methods pure: they read models and return widgets, while side effects live in controllers, notifiers, or blocs.
Pick the smallest solution that stays testable: start with ValueNotifier + ListenableBuilder, graduate to Provider or Riverpod when dependency injection and caching matter.
Avoid storing large mutable graphs directly inside State without clear update paths—immutable view models make diffs and testing easier.
What this section is about This stage covers the widgets you touch daily: text, images, containers, lists, and the shell widgets that frame a screen.
This stage covers the widgets you touch daily: text, images, containers, lists, and the shell widgets that frame a screen. Mastering them keeps UIs consistent and accessible.
Each widget documents its layout contract—some expand to fill parents, others shrink-wrap children. Confusing the two causes the majority of layout bugs.
Combine low-level widgets (Padding, Align) with higher-level Material widgets (Card, ListTile) for speed. Use Theme.of(context) for colors and typography instead of hard-coded literals.
Skim the widget catalog in the docs; when in doubt, read the “See also” links for related layout helpers.
What is the Text and RichText widgets? Text displays a single run of string data with optional TextStyle, textAlign, maxLines, and overflow handling.
Text displays a single run of string data with optional TextStyle, textAlign, maxLines, and overflow handling. It is the simplest way to render labels, captions, and body copy.
RichText (often paired with TextSpan trees) lets multiple styles, colors, and recognizers coexist in one paragraph—ideal for inline links, highlights, or mixed font weights.
Use Text.rich as a concise constructor when your span tree is small. For accessibility, ensure contrast ratios meet WCAG goals and that tappable spans expose semantics.
When text might scale with system settings, test with large accessibility fonts; wrap with FittedBox only when clipping is acceptable.
Text(
'Softaims',
style: Theme.of(context).textTheme.headlineSmall,
textAlign: TextAlign.center,
);
Text.rich(
TextSpan(
style: const TextStyle(fontSize: 16),
children: [
const TextSpan(text: 'Visit '),
TextSpan(
text: 'docs.flutter.dev',
style: const TextStyle(color: Colors.blue, decoration: TextDecoration.underline),
recognizer: TapGestureRecognizer()..onTap = () {},
),
],
),
);
What is the Image widget and asset bundles? Image loads raster or vector artwork from assets, memory, files, or the network (Image.network). Declaring assets in pubspec.
Image loads raster or vector artwork from assets, memory, files, or the network (Image.network). Declaring assets in pubspec.yaml bundles them into the app for offline use and predictable versioning.
Use Image.asset for packaged art, Image.file for user-selected media, and cache network images with cached_network_image when lists scroll heavily.
List every asset path (or directory with trailing /) under flutter: assets:; typos fail at runtime with clear errors. Provide @2x/@3x variants for crisp displays.
Set fit and alignment to avoid distortion; pair with AspectRatio when parents give unbounded constraints.
# pubspec.yaml
flutter:
assets:
- assets/logo.png
// Dart
Image.asset('assets/logo.png', width: 120)
What is Container and Padding? Padding is a single-responsibility widget: insets a child using EdgeInsets.
Padding is a single-responsibility widget: insets a child using EdgeInsets. Container is a convenience swiss-army widget that can paint a background, border, box shadow, alignment, and fixed sizing in one place.
Prefer Padding when you only need spacing; reach for Container when decoration and sizing combine.
Decoration is drawn behind the child; margin sits outside the decorated box. Use const constructors when values are compile-time constants to reduce rebuild work.
For responsive spacing, derive insets from MediaQuery or theme extensions rather than magic numbers scattered through the tree.
Padding(
padding: const EdgeInsets.all(16),
child: const Text('Inset copy'),
);
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.shade300),
),
child: const Text('Card-like'),
)
What is Row and Column? Row lays children along the horizontal axis; Column along the vertical axis.
Row lays children along the horizontal axis; Column along the vertical axis. Both are Flex widgets with mainAxisAlignment, crossAxisAlignment, and MainAxisSize knobs.
Non-flex children size themselves; Expanded and Flexible distribute remaining space along the main axis—critical inside scrollables and dialogs.
When a Row overflows horizontally, wrap with Expanded, Flexible, or switch to Wrap. In scrollable lists, prefer ListView/SingleChildScrollView instead of squeezing unbounded children into a Row.
Use Spacer (an expanded empty child) to push groups apart without manual SizedBox width math.
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Icon(Icons.home),
Icon(Icons.search),
Icon(Icons.person),
],
);
What is Scaffold and AppBar? Scaffold implements the classic Material screen structure: optional appBar, body, floatingActionButton, drawers, bottom sheets, and snack-bar hosts.
Scaffold implements the classic Material screen structure: optional appBar, body, floatingActionButton, drawers, bottom sheets, and snack-bar hosts. It knows safe areas and overlaps.
AppBar is the top toolbar with a leading control, title, and actions. SliverAppBar composes with CustomScrollView for collapsing headers.
Pass Scaffold a GlobalKey when you need to show snack bars or drawers from callbacks without a handy BuildContext from the body subtree.
Theme AppBarTheme once in ThemeData so every screen picks consistent elevation, colors, and iconography.
return Scaffold(
appBar: AppBar(title: const Text('Dashboard')),
body: const Center(child: Text('Content')),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.add),
),
);
What is ListView and GridView? ListView scrolls a linear list of children. Constructors like ListView.builder create items lazily—essential for large feeds.
ListView scrolls a linear list of children. Constructors like ListView.builder create items lazily—essential for large feeds. GridView tiles children in two dimensions with SliverGridDelegate controlling cross-axis count or max extent.
Both participate in sliver protocol under the hood, so they compose with CustomScrollView for advanced scrolling chrome.
Never build thousands of children eagerly—builder/separated variants instantiate on demand. Provide stable Keys when list order changes to preserve state.
For heterogeneous content, SliverList + SliverGrid inside a CustomScrollView keeps one scroll physics with multiple sections.
ListView.separated(
itemCount: 120,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) => ListTile(title: Text('Item $index')),
);
What is the Stack widget? Stack layers children in paint order (later children draw on top).
Stack layers children in paint order (later children draw on top). Use Positioned to anchor edges; non-positioned children align per alignment and loose constraints.
Stacks shine for badges, overlays, and hero animations where multiple widgets occupy the same visual space.
Give the stack bounded constraints—unbounded stacks with positioned children can confuse layout. Prefer Positioned.fill with a Center child for full-bleed backgrounds.
Watch hit testing: top widgets intercept taps unless you mark them IgnorePointer or AbsorbPointer.
Stack(
alignment: Alignment.center,
children: [
Container(width: 200, height: 200, color: Colors.blue.shade100),
const Icon(Icons.star, size: 64, color: Colors.amber),
],
);
What this section is about Forms capture user intent: text fields, toggles, pickers, and validation before you persist or submit data.
Forms capture user intent: text fields, toggles, pickers, and validation before you persist or submit data. Flutter models these as widgets plus controllers and Form state keyed by GlobalKey.
Accessible forms expose labels, hints, and error text so TalkBack/VoiceOver users understand failures without guessing from color alone.
Use TextEditingController to read or set field text imperatively; dispose controllers in State.dispose. Combine Form + TextFormField to run validators in one validate() call.
Debounce expensive validation (e.g. username checks) and surface server errors with setState or your state container.
final _formKey = GlobalKey<FormState>();
Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: const InputDecoration(labelText: 'Email'),
validator: (v) => (v == null || !v.contains('@')) ? 'Invalid email' : null,
),
FilledButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// submit
}
},
child: const Text('Submit'),
),
],
),
);
What is TextField and TextFormField? TextField is the low-level editable text widget. TextFormField wraps it with FormField integration so validators integrate with FormState.
TextField is the low-level editable text widget. TextFormField wraps it with FormField integration so validators integrate with FormState.save/validate.
Decorations (InputDecoration) provide floating labels, icons, counters, and error styles aligned with Material.
Listen to controller changes for live formatting (phone masks) but avoid rebuilding entire screens on each keystroke—use ValueListenableBuilder around the minimal subtree.
Set keyboardType and textInputAction to reduce friction on mobile keyboards.
TextFormField(
controller: emailController,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Work email',
border: OutlineInputBorder(),
),
);
What is Material buttons? ElevatedButton (filled), TextButton (text-only), and OutlinedButton share the same interaction model: onPressed is null when disabled.
ElevatedButton (filled), TextButton (text-only), and OutlinedButton share the same interaction model: onPressed is null when disabled. They pick colors from ButtonTheme / component themes.
Use IconButton for dense toolbars and ToggleButtons for multi-select segments.
Wrap async handlers: show progress indicators and disable the button while awaiting network work to prevent double submits.
Theme FilledButtonTheme in Material 3 apps to align corner radii and minimum touch targets (48dp) for accessibility.
Column(
children: [
FilledButton(onPressed: () {}, child: const Text('Continue')),
const SizedBox(height: 12),
OutlinedButton(onPressed: () {}, child: const Text('Back')),
const SizedBox(height: 12),
TextButton(onPressed: () {}, child: const Text('Skip')),
],
);
What is GestureDetector? GestureDetector listens for pointers: taps, long presses, drags, and scales.
GestureDetector listens for pointers: taps, long presses, drags, and scales. It sits in the widget tree and participates in the gesture arena with other recognizers.
When Material widgets already expose onPressed/onTap, prefer those for semantics. Use GestureDetector for custom paint or non-button hit targets.
Combine with Listener when you need raw pointer data. For complex gestures, consider RawGestureDetector or packaged solutions.
Set behavior: HitTestBehavior.opaque if transparent areas should still receive taps.
GestureDetector(
onTap: () => debugPrint('tapped'),
onLongPress: () => debugPrint('long'),
child: Container(
padding: const EdgeInsets.all(24),
color: Colors.teal.shade100,
child: const Text('Tap me'),
),
);
What is Form and GlobalKey? Form is an InheritedWidget ancestor that aggregates FormField descendants.
Form is an InheritedWidget ancestor that aggregates FormField descendants. Calling validate() walks registered fields; save() triggers onSaved callbacks—useful before HTTP posts.
GlobalKey
Create the key once per form state object—don’t rebuild new keys each build. Reset fields with controller.clear() plus formKey.currentState?.reset() when abandoning drafts.
For dynamic field lists, assign keys to fields so Flutter preserves focus sensibly when rows insert or delete.
What is DropdownButton? DropdownButton shows the current selection and opens a modal list of DropdownMenuItem values. It is a FormField-friendly way to pick one option from many.
DropdownButton shows the current selection and opens a modal list of DropdownMenuItem values. It is a FormField-friendly way to pick one option from many.
Under Material 3, consider DropdownMenu for searchable or larger catalogs.
Value must match exactly one item; mismatches assert in debug. Disable items by omitting onTap or using custom widgets.
For wide content, constrain width with DropdownButtonHideUnderline + SizedBox or switch to bottom sheets.
String? city;
DropdownButton<String>(
value: city,
hint: const Text('Choose city'),
items: const [
DropdownMenuItem(value: 'nyc', child: Text('New York')),
DropdownMenuItem(value: 'lon', child: Text('London')),
],
onChanged: (v) => setState(() => city = v),
);
What is Slider and Checkbox? Slider selects a value along a continuous range; Checkbox toggles a boolean. Both fire onChanged with the new value or null when disabled.
Slider selects a value along a continuous range; Checkbox toggles a boolean. Both fire onChanged with the new value or null when disabled.
They integrate with FormField wrappers (CheckboxListTile) for labeled, tappable rows.
Provide semantic labels for screen readers via Semantics or built-in semanticLabel where available.
Throttle expensive side effects—users can drag sliders rapidly; debounce persistence or preview updates.
Column(
children: [
Slider(
value: volume,
min: 0,
max: 100,
divisions: 10,
label: volume.round().toString(),
onChanged: (v) => setState(() => volume = v),
),
CheckboxListTile(
value: accepted,
onChanged: (v) => setState(() => accepted = v ?? false),
title: const Text('I agree to the terms'),
),
],
);
What is setState? setState schedules a rebuild of the current State object’s build method after synchronously running a callback where you mutate fields.
setState schedules a rebuild of the current State object’s build method after synchronously running a callback where you mutate fields. It is the lowest-level reactive primitive in StatefulWidget.
Keep mutations inside the closure to make intent obvious; avoid calling setState from asynchronous gaps without mounted checks.
Calling setState during build throws—compute derived values with didChangeDependencies or listeners instead.
For granular updates, extract a child StatefulWidget so only that subtree rebuilds.
void _increment() {
setState(() => _count++);
}
What is InheritedWidget? InheritedWidget publishes data to descendants without manual parameter drilling.
InheritedWidget publishes data to descendants without manual parameter drilling. dependOnInheritedWidgetOfExactType establishes a dependency so consumers rebuild when the inherited instance notifies.
Provider, Riverpod, and theme lookups all build on this mechanism.
Override updateShouldNotify to compare old/new values—return true only when dependents must refresh.
Prefer public helper methods (Foo.of(context)) that call dependOn... so misuse is harder.
What is the Provider package? Provider wraps InheritedWidget with ergonomic APIs: expose ChangeNotifier, ValueNotifier, or plain values, and consume them with context.
Provider wraps InheritedWidget with ergonomic APIs: expose ChangeNotifier, ValueNotifier, or plain values, and consume them with context.watch, read, or select extensions.
It reduces boilerplate versus hand-rolled InheritedWidgets and integrates with testing via ProviderScope overrides.
Place providers above the subtree that needs them—usually app or tab root. Use MultiProvider to compose several independent services.
Dispose notifiers in Provider’s dispose callback to avoid leaks from long-lived models.
What is ChangeNotifier? ChangeNotifier is a mixin from flutter/foundation.dart that maintains a list of listeners and exposes notifyListeners() after mutating model fields.
ChangeNotifier is a mixin from flutter/foundation.dart that maintains a list of listeners and exposes notifyListeners() after mutating model fields.
It pairs with ListenableBuilder or Provider’s ChangeNotifierProvider to drive UI updates.
Call notifyListeners after batching related field updates to avoid redundant rebuild storms.
In tests, replace async timers with addTearDown to dispose notifiers.
class Cart extends ChangeNotifier {
final List<String> items = [];
void add(String id) {
items.add(id);
notifyListeners();
}
}
What is Riverpod?
Riverpod is a reactive caching and DI framework that fixes Provider pain points: compile-safe overrides, async providers, and no BuildContext requirement for reads in some APIs.
Providers are declared globally or scoped; dependencies graph automatically.
Start with Provider and StateNotifier patterns; migrate incrementally from Provider if needed.
Use ref.listen for side effects (navigation/snackbars) instead of putting them inside build.
What is Bloc and Cubit? Cubit is a minimal class exposing methods that emit states; Bloc adds explicit events for richer analytics and replay.
Cubit is a minimal class exposing methods that emit states; Bloc adds explicit events for richer analytics and replay. Both encourage unidirectional data flow and testable pure functions.
The flutter_bloc package supplies BlocBuilder, BlocListener, and BlocConsumer widgets.
Keep event handlers thin: parse input, call repositories, emit success/failure states. Avoid calling repositories directly from UI widgets.
Use bloc_test for golden state sequences in CI.
What is ScopedModel? ScopedModel is an older pattern wrapping an InheritedWidget with a model base class.
ScopedModel is an older pattern wrapping an InheritedWidget with a model base class. New projects rarely start here, but you may encounter it in legacy codebases.
Understanding it clarifies how simpler APIs like Provider evolved.
If maintaining ScopedModel apps, plan migration slices: introduce Provider or Riverpod alongside, then retire ScopedModelDescendant usage module by module.
Prefer modern packages for new features so new hires onboard faster.
What this section is about Beyond basic push/pop, production apps need named routes, guards, deep links, and web URLs.
Beyond basic push/pop, production apps need named routes, guards, deep links, and web URLs. This section contrasts imperative Navigator usage with declarative routers like go_router.
Passing arguments safely and popping with results keeps flows composable and testable.
Centralize route tables to avoid string typos. On web, sync browser history with Router configuration.
Handle Android back stack and iOS interactive pop gestures by keeping route stacks shallow where possible.
What is Navigator.push and Navigator.pop?
push places a new route on the stack built by a RouteBuilder; pop removes the top route, optionally returning a value to the awaiting future.
Use pushNamed when routes are registered in MaterialApp.routes for consistency.
Prefer Navigator.maybePop when the caller might already be the root route.
For nested navigators, pass a dedicated navigatorKey or use Builder to capture the inner context.
final created = await Navigator.of(context).push<bool>(
MaterialPageRoute(builder: (_) => const EditPage()),
);
if (created == true) refresh();
What is named routes? Named routes map string paths to builders declared on MaterialApp (routes, onGenerateRoute). They simplify deep linking setup and analytics tagging.
Named routes map string paths to builders declared on MaterialApp (routes, onGenerateRoute). They simplify deep linking setup and analytics tagging.
Arguments travel through settings.arguments; cast carefully or use code generation.
Unknown routes should land on a 404 page—implement onUnknownRoute for graceful recovery.
As apps grow, migrate to go_router for path parameters and redirects without losing named-route mental models.
What is passing data between routes? Common patterns: constructor injection on the pushed page, ModalRoute.settings.
Common patterns: constructor injection on the pushed page, ModalRoute.settings.arguments, or shared state containers (Provider/Riverpod) scoped above the navigator.
Constructor injection keeps types strong and makes unit tests straightforward.
For return values, use Navigator.pop(context, result) and await push on the caller.
Avoid singleton globals for navigation payloads—they complicate testing and hot restart.
What this section is about Dart’s async model uses a single-threaded event loop with Future and Stream types. Flutter relies on async for I/O without blocking frames.
Dart’s async model uses a single-threaded event loop with Future and Stream types. Flutter relies on async for I/O without blocking frames.
Understanding microtasks versus event queue ordering explains subtle reorder bugs after await.
Use async/await for linear async code; fall back to Future.then chains when composing streams.
Always check context.mounted after await before calling setState or using context for navigation.
Future<String> loadUser() async {
final uri = Uri.parse('https://api.example.com/me');
final response = await http.get(uri);
return response.body;
}
What is Future? A Future represents a value or error arriving later. It can complete once; listeners attach via then, catchError, or async/await.
A Future
Many Flutter APIs (HttpClient, package:http) return futures for network and disk work.
Use FutureBuilder to bridge futures into widgets, supplying loading and error UI.
Prefer timeout and explicit error mapping so users see actionable messages.
What is async and await? async marks a function returning an implicit Future. await pauses the function until the future completes, without blocking the UI isolate.
async marks a function returning an implicit Future. await pauses the function until the future completes, without blocking the UI isolate.
Sequential awaits read clearly; parallelize independent work with Future.wait.
Wrap risky awaits in try/catch; map exceptions to user-visible states.
Use unawaited (with lint) when fire-and-forget is intentional.
Future<void> save() async {
try {
await repository.persist(formData);
} catch (e) {
showError(e);
}
}
What is Stream? Streams model sequences of events over time—sensor readings, WebSockets, or Firebase snapshots.
Streams model sequences of events over time—sensor readings, WebSockets, or Firebase snapshots. Single-subscription streams deliver in order; broadcast streams allow multiple listeners.
Async generators (async*) yield stream events for straightforward pagination loops.
Transform streams with map, where, asyncMap, and handleError before the UI layer.
Cancel subscriptions in dispose to prevent leaks from long-lived broadcast sources.
What is StreamBuilder? StreamBuilder listens to a Stream and rebuilds when new events arrive, exposing ConnectionState for loading snapshots.
StreamBuilder listens to a Stream and rebuilds when new events arrive, exposing ConnectionState for loading snapshots.
Pair with StreamController for test doubles or bridging imperative APIs.
Provide a stable stream instance across rebuilds—recreating the stream each build restarts subscriptions.
For complex UIs, consider StreamConsumer patterns or Riverpod’s StreamProvider.
StreamBuilder<int>(
stream: counterStream,
builder: (context, snapshot) {
if (!snapshot.hasData) return const CircularProgressIndicator();
return Text('Value: ${snapshot.data}');
},
);
What this section is about Mobile clients fetch remote JSON, cache locally, and sometimes sync offline.
Mobile clients fetch remote JSON, cache locally, and sometimes sync offline. This section covers HTTP clients, serialization, embedded databases, and Firebase backends.
Security touches TLS, token storage, and least-privilege API scopes.
Model DTOs with immutable classes; validate JSON before trusting types from the wire.
Choose storage by query needs: key-value for flags, relational SQLite for structured offline data.
import 'dart:convert';
Map<String, dynamic> map = jsonDecode(response.body) as Map<String, dynamic>;
What is the http package? package:http offers a minimal API for GET/POST/PUT/DELETE with multi-platform support.
package:http offers a minimal API for GET/POST/PUT/DELETE with multi-platform support. It wraps Client instances so you can inject mocks or add interceptors via subclasses.
Higher-level clients like Dio add interceptors, retries, and download progress out of the box.
Share one Client for connection reuse; close it when tearing down tests.
Set headers for auth and content-type; parse status codes explicitly—2xx is not guaranteed.
final response = await http.get(Uri.parse('https://api.example.com/v1/items'));
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
}
What is JSON serialization in Dart? Convert JSON maps to typed objects with fromJson factories and back with toJson.
Convert JSON maps to typed objects with fromJson factories and back with toJson. Manual parsing is explicit; code generation (json_serializable) reduces boilerplate on large models.
Consider freezed or sealed unions when responses have discriminated variants.
Treat numbers carefully—JSON numbers may arrive as int or double. Validate required fields and fail fast.
Use compute or isolates for huge payloads to avoid jank on the UI thread.
class User {
User({required this.id, required this.name});
final String id;
final String name;
factory User.fromJson(Map<String, dynamic> j) =>
User(id: j['id'] as String, name: j['name'] as String);
}
What is local storage with shared_preferences? shared_preferences persists simple key-value pairs backed by platform stores (NSUserDefaults, SharedPreferences on Android).
shared_preferences persists simple key-value pairs backed by platform stores (NSUserDefaults, SharedPreferences on Android). Ideal for flags, tokens (if acceptable per threat model), and UI prefs.
Batch reads at startup instead of awaiting dozens of sequential gets on cold launch.
Not for large blobs—use files or SQLite instead. Encrypt sensitive values before writing if the platform store is insufficient.
Await setString completion before assuming durability across kills.
What is SQLite with sqflite? sqflite exposes SQLite on mobile/desktop for relational queries, migrations, and transactions.
sqflite exposes SQLite on mobile/desktop for relational queries, migrations, and transactions. Use Drift or Floor for type-safe query builders if schemas grow.
Use transactions when updating multiple tables so failures roll back cleanly.
Run migrations on upgrade paths; version the schema. Keep DB work off the UI isolate via compute or dedicated isolates for heavy batches.
Index columns you filter on; explain-query-plan performance regressions after releases.
What is Firebase integration? FlutterFire wraps Firebase SDKs: Auth, Firestore, Storage, Analytics, and more. Initialization calls Firebase.
FlutterFire wraps Firebase SDKs: Auth, Firestore, Storage, Analytics, and more. Initialization calls Firebase.initializeApp with platform-specific config files.
Enable Crashlytics and Performance Monitoring early so releases ship with signal.
Follow security rules on the server—never trust client-only checks for protected data.
Use emulators during development to avoid touching production projects.
What is json_serializable? The json_serializable builder generates fromJson/toJson and field renaming annotations, cutting errors on large APIs.
The json_serializable builder generates fromJson/toJson and field renaming annotations, cutting errors on large APIs.
Run dart run build_runner build when models change.
Commit generated parts or enforce CI generation—teams differ; pick one policy.
Use JsonKey for defaults, unknown enum handling, and custom converters.
What is Firebase Authentication? Firebase Auth supports passwordless, OAuth providers, phone OTP, and custom tokens. Flutter plugins expose streams of User state for reactive UIs.
Firebase Auth supports passwordless, OAuth providers, phone OTP, and custom tokens. Flutter plugins expose streams of User state for reactive UIs.
Link providers when users upgrade from anonymous sessions to permanent accounts.
Secure refresh tokens per platform guidance; combine with backend verification using Admin SDK.
Test sign-in flows on real devices—SMS and OAuth redirects vary by OS.
What this section is about Flutter’s layout algorithm passes constraints down and sizes up.
Flutter’s layout algorithm passes constraints down and sizes up. Understanding min/max widths, flex, and unbounded constraints explains most “RenderFlex overflowed” errors.
Responsive UIs combine MediaQuery, LayoutBuilder, and breakpoints rather than fixed pixel art from one phone mock.
Use Flutter DevTools’ layout explorer to see constraint trees during debugging.
Prefer intrinsic dimensions (IntrinsicHeight) sparingly—they are expensive on deep trees.
What is SizedBox? SizedBox enforces width and/or height on a child—or acts as a fixed gap when given only dimensions and child: null.
SizedBox enforces width and/or height on a child—or acts as a fixed gap when given only dimensions and child: null.
It is cheaper than Container when you do not need decoration.
Use SizedBox.expand to force a child to fill parent constraints.
In lists, combine with AspectRatio for media thumbnails.
const SizedBox(width: 48, height: 48, child: Icon(Icons.star));
What is LayoutBuilder? LayoutBuilder exposes the parent’s BoxConstraints in the builder so you can branch layouts (e.g. two-column tablet vs single-column phone).
LayoutBuilder exposes the parent’s BoxConstraints in the builder so you can branch layouts (e.g. two-column tablet vs single-column phone).
It rebuilds when constraints change—rotation, split-screen, or window resize on desktop.
Avoid doing heavy synchronous work inside the builder; cache derived layouts if needed.
Pair with MediaQuery.sizeOf when you also care about padding or text scale.
LayoutBuilder(
builder: (context, constraints) {
final wide = constraints.maxWidth > 600;
return wide ? const DesktopShell() : const MobileShell();
},
);
What is MediaQuery? MediaQuery surfaces device metrics: size, padding (notches), text scale factor, platform brightness, and accessibility settings. Use MediaQuery.
MediaQuery surfaces device metrics: size, padding (notches), text scale factor, platform brightness, and accessibility settings.
Use MediaQuery.of(context) in build methods; depend on specific aspects with MediaQuery.sizeOf extensions to minimize rebuilds.
Respect textScaler when designing tight layouts—never clip user-chosen font sizes without offering alternatives.
Combine with SafeArea to keep tappable targets outside display cutouts.
final padding = MediaQuery.paddingOf(context);
return Padding(
padding: EdgeInsets.only(top: padding.top),
child: content,
);
What is SafeArea? SafeArea pads its child by the OS-reported safe insets (status bar, notch, home indicator). It prevents accidental overlap with system UI.
SafeArea pads its child by the OS-reported safe insets (status bar, notch, home indicator). It prevents accidental overlap with system UI.
On Android, also consider display cutout APIs for landscape media.
Use minimum padding when you need a baseline but still respect safe zones.
For edge-to-edge art, place backgrounds outside SafeArea and keep interactive controls inside.
Scaffold(
body: SafeArea(
child: ListView(children: items),
),
);
What this section is about Motion reinforces affordances: implicit widgets animate property changes automatically; explicit animations use AnimationController timelines for custom
Motion reinforces affordances: implicit widgets animate property changes automatically; explicit animations use AnimationController timelines for custom effects.
Performance hinges on keeping animations on the GPU-friendly layers—avoid rebuilding heavy subtrees each tick.
Start with AnimatedOpacity, AnimatedAlign, and friends before writing controllers.
Dispose controllers and listeners in dispose to prevent ticker leaks.
What is implicit animations? Implicitly animated widgets wrap a target property and tween to a new value when it changes—no manual controller required.
Implicitly animated widgets wrap a target property and tween to a new value when it changes—no manual controller required.
They are ideal for quick polish: padding shifts, color fades, cross-fades between children.
Tune duration and curve for brand feel; use AnimationStyle (where available) for reusable presets.
Watch rebuild scope—wrap only the animating leaf when possible.
What is AnimatedContainer? AnimatedContainer tweens decoration, alignment, padding, size, and constraints changes. It is a convenient implicit animation for box-like UI.
AnimatedContainer tweens decoration, alignment, padding, size, and constraints changes. It is a convenient implicit animation for box-like UI.
Because it rebuilds decoration each frame, prefer simpler widgets if performance traces show jank.
Combine with Curve subclasses like Curves.easeOutCubic for natural motion.
AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: expanded ? 200 : 120,
color: expanded ? Colors.blue : Colors.grey,
);
What is AnimationController? AnimationController drives explicit animations over a duration; it ticks vsync-aligned frames via TickerProviderStateMixin or TickerProvider.
AnimationController drives explicit animations over a duration; it ticks vsync-aligned frames via TickerProviderStateMixin or TickerProvider.
Attach Animation
Always dispose controllers. Use repeat with Reverse flags for pulse effects.
For staggered sequences, chain futures or use Interval curves on a single controller.
What is CurvedAnimation? CurvedAnimation applies a non-linear Curve to a parent animation’s 0–1 progress—easing in/out, bouncing, elastic overshoot.
CurvedAnimation applies a non-linear Curve to a parent animation’s 0–1 progress—easing in/out, bouncing, elastic overshoot.
Nest curves carefully: parent ranges propagate; debug with FlippedCurve or Interval for partial segments.
Respect reduced-motion platform settings—offer shorter or non-animated alternatives.
What this section is about This pass focuses on Provider ergonomics: multi-providers, selectors, and async providers that mirror how production apps structure DI.
This pass focuses on Provider ergonomics: multi-providers, selectors, and async providers that mirror how production apps structure DI.
The aim is granular rebuilds and testable service seams.
Prefer ProxyProvider when one service depends on another’s updates.
Use context.select to listen to slices of models and avoid whole-tree rebuilds.
What is Provider and MultiProvider? Provider.value / constructor variants expose objects down-tree. MultiProvider nests multiple providers without pyramid indentation.
Provider.value / constructor variants expose objects down-tree. MultiProvider nests multiple providers without pyramid indentation.
Mark providers lazy: false when startup must warm caches—default lazy defers instantiation.
Place feature-scoped providers at route boundaries to free memory after pop.
What is Consumer? Consumer rebuilds using a builder whenever T notifies (for Listenable/ChangeNotifier). It scopes rebuilds tighter than context.watch in some layouts.
Consumer
Pass child subtrees that do not depend on T to avoid rebuilding heavy widgets.
Prefer Selector when only a field of a model changes.
What is Selector? Selector maps a model S to a view model M and rebuilds only when M changes per shouldRebuild. It reduces unnecessary paints on large ChangeNotifier graphs.
Selector maps a model S to a view model M and rebuilds only when M changes per shouldRebuild.
It reduces unnecessary paints on large ChangeNotifier graphs.
Keep selectors pure and fast—no I/O inside selector.
Combine with memoization patterns when deriving lists is expensive.
What is FutureProvider? FutureProvider resolves an async value once (or when invalidated) and exposes AsyncSnapshot states to widgets.
FutureProvider resolves an async value once (or when invalidated) and exposes AsyncSnapshot states to widgets.
Useful for remote config or one-shot bootstrap data.
Handle errors with AsyncValue patterns in Riverpod; in Provider, surface try/catch before exposing futures.
Invalidate or refresh explicitly when user pulls-to-refresh.
What is StreamProvider? StreamProvider listens to a stream and rebuilds dependents on each event—great for Firebase snapshots or websocket feeds.
StreamProvider listens to a stream and rebuilds dependents on each event—great for Firebase snapshots or websocket feeds.
Close underlying streams when providers dispose to avoid ghost listeners.
Throttle high-frequency events before they hit build methods.
What is ProxyProvider? ProxyProvider rebuilds its value when upstream dependencies change—wiring services that need fresh credentials or locale-aware formatters.
ProxyProvider rebuilds its value when upstream dependencies change—wiring services that need fresh credentials or locale-aware formatters.
Avoid circular proxy graphs; extract shared dependencies to a third provider.
Test proxies by overriding upstream fakes in widget tests.
What this section is about Flutter tests span unit (pure Dart), widget (component + interaction), and integration (full app) layers. Golden tests capture render regressions.
Flutter tests span unit (pure Dart), widget (component + interaction), and integration (full app) layers. Golden tests capture render regressions.
CI should run flutter test on every PR with deterministic fonts and animations disabled where needed.
Use testWidgets and WidgetTester for taps, drags, and pump timings.
Mock platform channels with TestDefaultBinaryMessenger when plugins lack fakes.
testWidgets('shows title', (tester) async {
await tester.pumpWidget(const MaterialApp(home: HomePage()));
expect(find.text('Welcome'), findsOneWidget);
});
What is unit testing? Unit tests exercise pure functions, serializers, and view models without widgets. They are fast and pinpoint algorithm regressions.
What is widget testing? Widget tests render components in a fake environment, simulate gestures, and inspect the element tree.
Widget tests render components in a fake environment, simulate gestures, and inspect the element tree.
They catch layout assertion failures and state integration bugs.
Call pumpAndSettle after animations; use pump with durations for explicit frames.
Golden tests (matchesGoldenFile) need stable fonts and themes across OS.
What is integration testing? Integration tests drive the app on devices or emulators via integration_test, validating end-to-end flows like login and checkout.
Integration tests drive the app on devices or emulators via integration_test, validating end-to-end flows like login and checkout.
They are slower but closest to user reality.
Gate a small smoke suite on CI; run fuller suites nightly.
Reset app state between tests with fresh testWidgets bindings or uninstall hooks.
What is mocking dependencies? Mocks isolate units under test: use mockito code generation or fake_async for timers. For HTTP, use http.MockClient or intercept adapters.
Mocks isolate units under test: use mockito code generation or fake_async for timers. For HTTP, use http.MockClient or intercept adapters.
Verify call order sparingly—prefer behavior assertions over implementation coupling.
Provide fake PlatformChannel responses for plugins without test hooks.
What this section is about Sometimes Dart alone cannot access a sensor or SDK.
Sometimes Dart alone cannot access a sensor or SDK. Platform channels (MethodChannel) marshal messages to Kotlin/Swift; FFI calls native C libraries for performance-critical code.
Plugins wrap these bridges with a stable Dart API across iOS and Android.
Keep channel contracts versioned and backward compatible when shipping plugin updates.
Marshal small payloads—large binary transfers belong in files or shared memory techniques.
What is platform channels? MethodChannel sends asynchronous messages to the host OS, invoking methods implemented in Kotlin/Java or Swift/Objective-C.
MethodChannel sends asynchronous messages to the host OS, invoking methods implemented in Kotlin/Java or Swift/Objective-C.
Event channels stream device updates like battery level or connectivity.
Handle MissingPluginException when running on web or desktop without implementations.
Move codec-heavy serialization to background threads on the native side.
What is Dart FFI? Dart FFI lets you call C functions directly with DynamicLibrary.open bindings—useful for codecs, cryptography, or reusing mature native libraries.
Dart FFI lets you call C functions directly with DynamicLibrary.open bindings—useful for codecs, cryptography, or reusing mature native libraries.
Code generation (ffigen) maps headers to Dart signatures.
Mind ABI stability across OS versions; ship fat binaries on iOS when needed.
Profile allocations—crossing the FFI boundary too frequently can negate gains.
What is url_launcher? The url_launcher plugin opens http(s), mailto, tel, and custom schemes when the platform allows.
The url_launcher plugin opens http(s), mailto, tel, and custom schemes when the platform allows.
Check canLaunchUrl before attempting; handle failures with user-visible errors.
On iOS, declare URL schemes in Info.plist; on Android, add queries for package visibility.
Prefer in-app WebView when you need auth cookies contained.
await launchUrl(Uri.parse('https://flutter.dev'),
mode: LaunchMode.externalApplication);
What is image_picker? image_picker selects photos or captures camera shots, returning XFile handles you can read bytes from.
image_picker selects photos or captures camera shots, returning XFile handles you can read bytes from.
Permissions differ by OS—request runtime prompts and explain rationale.
Resize or compress before upload to save bandwidth.
On web, fall back to file input semantics.
What is permission_handler? Centralizes runtime permission requests for camera, microphone, location, storage, and more with a unified API.
Centralizes runtime permission requests for camera, microphone, location, storage, and more with a unified API.
Always check status before assuming granted—users can revoke later.
Document permission strings in App Store Connect and Play Console privacy forms.
Degrade gracefully when denied; offer deep links to settings.
What is device_info_plus? Exposes model identifiers, OS versions, and hardware flags for diagnostics or feature gating.
Exposes model identifiers, OS versions, and hardware flags for diagnostics or feature gating.
Avoid using low-level IDs for analytics fingerprinting without disclosure.
Cache values that do not change during a session to reduce platform calls.
Abstract behind an interface for test doubles.
What this section is about Theming unifies color, type, and component defaults; localization adapts strings, formats, and layout direction for locales.
Theming unifies color, type, and component defaults; localization adapts strings, formats, and layout direction for locales.
Flutter supports gen-l10n code generation from ARB files for scalable translations.
Set ThemeData once and override with Theme subtrees for feature-specific accents.
Test pseudolocales and right-to-left layouts early.
What is ThemeData and Theme? ThemeData holds color schemes, typography, and component themes. The Theme widget applies data to descendants via Theme.of(context).
ThemeData holds color schemes, typography, and component themes. The Theme widget applies data to descendants via Theme.of(context).
Material 3 introduces ColorScheme.fromSeed for accessible tonal palettes.
Use ThemeExtension for custom design tokens not covered by core ThemeData.
Dark mode: provide ThemeData.dark or dynamic schemes from user settings.
MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
),
home: const Home(),
);
What is dark and light themes? Users expect automatic or manual theme toggles synchronized with OS MediaQuery.platformBrightness.
Users expect automatic or manual theme toggles synchronized with OS MediaQuery.platformBrightness.
Contrast ratios must hold in both modes—avoid hard-coded greys.
Persist user choice in shared_preferences and apply before first frame to avoid flashes.
Preview screenshots for both themes in store listings.
What is custom fonts? Declare font files in pubspec.yaml under fonts: with family names, then reference via TextStyle(fontFamily: ...).
Declare font files in pubspec.yaml under fonts: with family names, then reference via TextStyle(fontFamily: ...).
Variable fonts are supported on newer Flutter versions—verify target min SDK.
Subset fonts to reduce APK/IPA size.
License fonts correctly in your credits screen.
flutter:
fonts:
- family: SoftaimsSans
fonts:
- asset: assets/fonts/SoftaimsSans-Regular.ttf
What is flutter_localizations? Provides translated Material/Cupertino strings and delegates wired through localizationsDelegates on MaterialApp.
Provides translated Material/Cupertino strings and delegates wired through localizationsDelegates on MaterialApp.
Include GlobalMaterialLocalizations.delegate alongside your app-specific ARB delegates.
Set supportedLocales explicitly to control store metadata alignment.
What is gen_l10n? flutter gen-l10n generates type-safe AppLocalizations getters from ARB files, catching missing keys at compile time.
What is the intl package? intl formats dates, numbers, and currencies per locale—critical for receipts, dashboards, and compliance.
intl formats dates, numbers, and currencies per locale—critical for receipts, dashboards, and compliance.
Always pass explicit locales when formatting server timestamps to UTC.
Cache DateFormat instances where docs recommend—construction can be costly.
What is TextDirection? TextDirection.ltr vs rtl affects alignment, padding mirroring, and EdgeInsetsDirectional.
TextDirection.ltr vs rtl affects alignment, padding mirroring, and EdgeInsetsDirectional.
Combine with Directionality widget for embedded mixed-direction snippets.
Test Arabic or Hebrew locales with real copy, not placeholder gibberish.
Icons that imply direction may need mirroring—audit your asset set.
What this section is about Performance work targets jank (missed frames), memory churn, and startup time. Flutter DevTools profiles GPU vs UI thread costs.
Performance work targets jank (missed frames), memory churn, and startup time. Flutter DevTools profiles GPU vs UI thread costs.
Micro-optimizations matter only after measuring—avoid premature const everywhere without profiling.
Run flutter run --profile on hardware for truthful timings.
Use RepaintBoundary sparingly to isolate expensive paints.
What is performance profiling? CPU Profiler samples Dart frames; Memory view tracks heap growth and retained objects. Timeline captures build/layout/paint phases per frame.
CPU Profiler samples Dart frames; Memory view tracks heap growth and retained objects.
Timeline captures build/layout/paint phases per frame.
Record sessions during worst-case user journeys—scrolling long lists, cold start, image-heavy grids.
Share traces with annotations when handing off to specialists.
What is render performance? Reduce rebuild scope with ListView.builder, split widgets, and memoized children. Avoid synchronous file I/O on UI isolate.
Reduce rebuild scope with ListView.builder, split widgets, and memoized children. Avoid synchronous file I/O on UI isolate.
Shader compilation jank can be warmed up with placeholder scenes on first launch.
Watch for excessive Opacity and Clip usage—both can force offscreen buffers.
Use Image.memory cacheWidth/cacheHeight to decode smaller bitmaps.
What is const constructors? const widgets compile to canonical instances, skipping rebuild work when inputs do not change. Apply to leaf widgets with immutable arguments.
const widgets compile to canonical instances, skipping rebuild work when inputs do not change.
Apply to leaf widgets with immutable arguments.
Do not mark widgets const if they depend on runtime theming unless values are compile-time constants.
Combine with KeyedSubtree patterns carefully—keys affect element reuse.
const Icon(Icons.check_circle, color: Colors.green);
What is image caching? Use cached_network_image or similar to avoid re-downloading avatars on every rebuild. Tune max cache bytes/eviction policies to respect device storage.
Use cached_network_image or similar to avoid re-downloading avatars on every rebuild.
Tune max cache bytes/eviction policies to respect device storage.
Provide memory cache dimensions matching on-screen pixel sizes.
Clear caches on logout for privacy-sensitive apps.
What this section is about Large apps blend architectural patterns—clean architecture, MVVM, BLoC—with clear module boundaries and test seams.
Large apps blend architectural patterns—clean architecture, MVVM, BLoC—with clear module boundaries and test seams.
The goal is isolated domain logic and swappable data sources.
Draw dependency arrows inward: UI → application services → domain → infrastructure.
Adopt feature modules to scale teams without merge conflicts in monolithic lib/.
What is the BLoC pattern? Business Logic Components separate event handling from widgets, emitting states that the UI observes.
Business Logic Components separate event handling from widgets, emitting states that the UI observes.
flutter_bloc standardizes wiring with predictable lifecycles.
Keep events granular for analytics; aggregate when reducers become noisy.
Use hydrated_bloc cautiously—version persisted state carefully.
What is Cubit? Cubit exposes methods that emit states without explicit event objects—lighter weight than full Bloc when inputs are simple.
Cubit exposes methods that emit states without explicit event objects—lighter weight than full Bloc when inputs are simple.
Still log transitions in debug builds to trace rare race conditions.
Unit test cubits by calling methods and expecting state sequences.
What is clean architecture? Layers: entities, use cases, interfaces/adapters, frameworks. Flutter sits in the outer UI layer while domain rules stay pure Dart.
Layers: entities, use cases, interfaces/adapters, frameworks. Flutter sits in the outer UI layer while domain rules stay pure Dart.
Use dependency inversion so repositories hide whether data is remote or local.
Avoid leaking BuildContext into domain services.
What is MVVM in Flutter? Model-View-ViewModel maps UI state to view models exposed as ChangeNotifier, Riverpod notifiers, or BLoCs.
Model-View-ViewModel maps UI state to view models exposed as ChangeNotifier, Riverpod notifiers, or BLoCs.
Bindings are manual compared with SwiftUI, but tests focus on view models.
Keep view models platform-agnostic—no Navigator calls inside; emit effects to the UI layer.
Use code generation sparingly; prefer readable hand-written models early.
What is flutter_riverpod? ConsumerWidget and ConsumerStatefulWidget obtain WidgetRef to read providers without BuildContext indirection in many cases.
ConsumerWidget and ConsumerStatefulWidget obtain WidgetRef to read providers without BuildContext indirection in many cases.
Overrides enable test fakes and flavor-specific endpoints.
Use AsyncValue helpers for loading/error UI consistency.
Scope providers with ProviderScope nesting for modals and routes.
What this section is about Custom painters draw directly on a canvas for charts, signatures, and bespoke visuals.
Custom painters draw directly on a canvas for charts, signatures, and bespoke visuals. Fragment shaders (dart:ui) unlock GPU effects on supported targets.
These APIs trade convenience for control—profile early.
Clip and save/restore canvas state to avoid bleeding transforms.
Hot reload may not apply shader changes—use restart.
What is CustomPainter? Subclass CustomPainter with paint and shouldRepaint to describe vector artwork imperatively. Pair with CustomPaint widget sizing.
Subclass CustomPainter with paint and shouldRepaint to describe vector artwork imperatively.
Pair with CustomPaint widget sizing.
Cache Paint objects as fields rather than reallocating each frame.
Hit testing uses hitTest overrides for irregular shapes.
What is Canvas and Paint? Canvas methods draw rects, paths, images, and text. Paint configures stroke, fill, shaders, and blend modes.
What is Path? Path accumulates lines, curves, and conic sections for clipping or stroking complex shapes. How it works Close paths when filling to avoid interior leaks.
What is ClipPath? ClipPath uses a Path mask to clip children—great for rounded avatars or wave headers. Clipping triggers saveLayer when antialiasing intersects—watch GPU cost.
ClipPath uses a Path mask to clip children—great for rounded avatars or wave headers.
Clipping triggers saveLayer when antialiasing intersects—watch GPU cost.
Prefer ClipRRect when a rounded rectangle suffices—it can be cheaper.
Test with Impeller vs Skia backends on iOS for visual parity.
What is fragment shaders? Fragment shaders run per-pixel programs compiled from SPIR-V or supported formats, enabling gradients, blurs, and transitions on GPU.
Fragment shaders run per-pixel programs compiled from SPIR-V or supported formats, enabling gradients, blurs, and transitions on GPU.
Validate fallback UI when shaders fail to compile on older devices.
Precompile using warmup scenes where supported.
What is CustomScrollView? Composes multiple sliver children (SliverAppBar, SliverList, SliverGrid) with unified scrolling physics. Enables parallax headers and sticky sections.
Composes multiple sliver children (SliverAppBar, SliverList, SliverGrid) with unified scrolling physics.
Enables parallax headers and sticky sections.
Provide SliverChildBuilderDelegate for lazy slivers as in lists.
Keep SliverOverlapAbsorber/Injector patterns in mind for nested scrollables.
What this section is about Shipping Flutter apps means signing binaries, obfuscating Dart where appropriate, automating store uploads, and tracking native toolchain upgrades.
Shipping Flutter apps means signing binaries, obfuscating Dart where appropriate, automating store uploads, and tracking native toolchain upgrades.
Each store has metadata, privacy, and review requirements that change yearly.
Maintain separate schemes/flavors for dev, staging, and production endpoints.
Archive with matching Xcode/AGP versions pinned in CI.
What is app signing? Android uses keystore uploads to Play App Signing; iOS relies on certificates, provisioning profiles, and automatic signing in Xcode.
Android uses keystore uploads to Play App Signing; iOS relies on certificates, provisioning profiles, and automatic signing in Xcode.
Never commit private keys—use secret stores in CI.
Rotate keys per incident response plans; document backup keystore passwords offline.
Validate signed artifacts with apksigner/codesign in pipelines.
What is code obfuscation? Dart obfuscation renames symbols in release builds, raising the bar for reverse engineering—pair with stripping debug symbols appropriately.
Dart obfuscation renames symbols in release builds, raising the bar for reverse engineering—pair with stripping debug symbols appropriately.
Keep symbol maps private for crash deobfuscation.
Test obfuscated builds before release—reflection-heavy code may break.
Combine with jailbreak/root detection only when product policy requires.
What is CI/CD for Flutter? Pipelines run analyzer, tests, builds, and distribute to TestFlight/Play Internal using flutter build apk/appbundle/ipa or Fastlane wrappers.
Pipelines run analyzer, tests, builds, and distribute to TestFlight/Play Internal using flutter build apk/appbundle/ipa or Fastlane wrappers.
Cache Gradle/CocoaPods artifacts to keep wall-clock time reasonable.
Gate merges on integration smoke tests on real devices weekly.
Version pubspec.yaml+build numbers consistently with store rules.
What is flutter build? flutter build apk|appbundle|ipa|web|windows|macos|linux produces release artifacts per target.
flutter build apk|appbundle|ipa|web|windows|macos|linux produces release artifacts per target. Flags tune split-per-abi, tree shaking icons, and web renderers.
Run flutter build in clean CI containers to reproduce local issues.
Compare APK analyzer output when chasing size regressions.
What is Gradle and Xcode builds? Android embeds Flutter via Gradle plugins; iOS uses Xcode projects/Pods generated by Flutter tooling.
Android embeds Flutter via Gradle plugins; iOS uses Xcode projects/Pods generated by Flutter tooling.
Native dependencies may require manual Podfile/Gradle edits.
Pin compileSdk and minSdk with Flutter’s migration guides each release.
Open android/ and ios/ folders in native IDEs when debugging plugin issues.
