Keentune

Flutter curriculum

18 chapters
·
134 concepts
·
free
Everything the adaptive question bank can teach and test in Flutter, from foundations through advanced practice. Work through it in order, or start practising and let the questions find your level.
New here? Read the Flutter guide
A free 18-minute primer — the mental model, the mistakes beginners make, and what to practise first.
A. Dart essentials a Flutter widget depends on
final is assigned once at runtime; const is fixed at compile time and can be used in a const widget
T? may hold null; a non-nullable T is a compile-time guarantee, not a runtime check
?. short-circuits the whole chain to null, ?? supplies a fallback, ??= assigns only when null
late defers the initializer; reading before the first write throws LateInitializationError
a local promotes to non-null after a check, but a public field or getter never does
named arguments are optional unless marked required, which is why widget constructors read as they do
Dart has no overloading; named and factory constructors fill the gap, and a factory may return a cached instance
== defaults to identity, so a value class must override == and hashCode together
..., ...?, and inline if/for build a children list without a helper method
B. The async model: futures, streams and isolates
one isolate runs sync code, drains the microtask queue, then takes one event; a frame is an event
an async function body runs synchronously up to its first await, and only then returns
dropping await yields Instance of 'Future', reorders the work, and loses the error
Future.wait overlaps independent requests; sequential awaits add their latencies together
try/catch only catches a future you awaited; an unawaited failure surfaces as an unhandled zone error
a single-subscription stream accepts one listener ever; a broadcast stream fans out to many
async* with yield builds a lazy stream that produces nothing until something listens
isolates have separate heaps and exchange copied messages, so a closure or handle cannot cross
Isolate.run / compute moves a CPU-bound function off the UI isolate so frames keep rendering
await releases the loop but long synchronous work still blocks it and drops frames
C. Widgets and the three trees
a widget is an immutable description of a piece of UI, which is why constructing one is cheap
the widget tree is configuration, the element tree holds identity and state, the render tree does layout and paint
the element persists across rebuilds and holds the State; the widget object is thrown away each frame
RenderObjects size, position, paint and hit-test; most widgets compose others and never create one
a StatelessWidget renders from its fields alone; a StatefulWidget owns state that outlives its widget
behavior is added by wrapping a widget, not by subclassing it, so padding and alignment are widgets
build can run many times and must not start work, mutate state, or show a dialog
a const widget is canonicalized, so the framework can skip rebuilding that subtree entirely
D. StatefulWidget and the State lifecycle
createState runs once when the element mounts; the returned State then survives every later widget instance
initState runs once before the first build and cannot do inherited lookups such as MediaQuery.of
runs right after initState and again whenever an inherited widget this State depends on changes
fires when the parent rebuilds with a new widget of the same type and key; compare oldWidget before reacting
dispose releases controllers, subscriptions and tickers; deactivate runs first and can still be undone by reinsertion
setState marks this element dirty for the next frame; its callback is synchronous and must never be async
calling setState after dispose throws, so an async completion must check mounted first
the framework replaces State.widget on every rebuild, so copying its values into fields goes stale
addPostFrameCallback defers work until the tree is laid out, which build itself cannot see
E. Keys and element reuse
an element is reused when the new widget has the same runtimeType and the same key; otherwise it is torn down
reordering or removing stateful siblings without keys leaves the old state attached to the wrong item
the key must go on the top widget of the item's subtree; a key placed deeper does not preserve the item
ValueKey matches on an equal value, ObjectKey on the identity of the backing object
a UniqueKey() constructed in build differs every frame, so the subtree remounts and loses its state
a GlobalKey reaches a State from elsewhere and can move a subtree, but must be unique tree-wide and is comparatively costly
F. BuildContext and the inherited lookup
a BuildContext is the handle for one element's position in the tree, not a global application object
X.of(context) searches ancestors from that position up and can never find a sibling or a descendant
the context of the widget that built a Scaffold sits above it, so the lookup fails until a Builder interposes
dependOnInheritedWidgetOfExactType registers a rebuild dependency; getInheritedWidgetOfExactType only reads
an element can be defunct once an await returns; check context.mounted before navigating or showing a snack bar
G. Constraints go down, sizes go up
a parent passes constraints down, the child chooses a size within them, and the parent then sets its position
a child cannot pick a size outside its constraints, so width: 50 under a tight 200 still renders 200
a tight constraint has min equal to max; a loose one has min zero and lets the child be smaller
Center and Align loosen the incoming constraints, which is how a child gets its natural size back
an unbounded axis has an infinite max, and a widget that tries to fill it throws instead of rendering
a Container with no child expands to fill; with a child it wraps it, unless a size or alignment is given
LayoutBuilder builds after constraints arrive, so it can adapt to them but never resize its own parent
asking for an intrinsic width or height re-runs layout on the subtree and is documented as costly
H. The layout widgets
MainAxisAlignment distributes along the flex direction, CrossAxisAlignment positions across it
a Row or Column claims all available main-axis space unless mainAxisSize is set to min
a Column gives its non-flex children unbounded height, which is what makes a nested ListView throw
Expanded forces the child to fill its share of free space; Flexible allows it to stay smaller
flex values divide only the space remaining after the inflexible children have been laid out
Expanded, Flexible and Positioned must be direct children of their Flex or Stack or the build throws
a Stack sizes itself to its non-positioned children, and Positioned children are placed against its edges
SizedBox imposes a tight size on its child; ConstrainedBox only narrows the constraints it received
the builder constructors create children on demand; the default constructor builds the whole list up front
I. Interactivity, gestures and forms
GestureDetector recognizes taps and drags but paints nothing, so an empty area needs an opaque hit-test behavior
InkWell draws its ripple onto an ancestor Material, so without one the feedback is invisible
competing recognizers compete in an arena and exactly one wins, which is why a nested tap can swallow a parent drag
a TextEditingController is the field's source of truth and must be disposed with the State
a Form plus GlobalKey<FormState> runs every field's validator and reports one combined result
J. State management
state used by a single widget belongs in its State; state several screens read does not
move shared state to the nearest common ancestor and pass data down while passing callbacks up
InheritedWidget exposes data to a whole subtree with a constant-time lookup and rebuilds only its dependents
dependents rebuild only when updateShouldNotify returns true for the replacing instance
ChangeNotifier broadcasts notifyListeners to its listeners and must itself be disposed
ValueNotifier with ValueListenableBuilder rebuilds only the builder's subtree, with no package needed
context.watch subscribes and belongs in build; context.read is a one-shot lookup for callbacks
Riverpod keeps providers out of the widget tree, so lookup is compile-time checked rather than context-scoped
BLoC maps a stream of events onto a stream of states, isolating transition logic from widgets
K. Navigation and routing
Navigator manages a stack of routes; push covers the current route and pop uncovers it
the future from push completes with whatever value the popped route passes back
a routes map with pushNamed covers the static cases; onGenerateRoute handles dynamic and unknown paths
pushReplacement and pushNamedAndRemoveUntil stop the user from returning to a login or splash screen
the Navigator API mutates a stack; the Router API rebuilds the entire stack from application state
a Hero animates between routes only when the identical tag exists on both, and a duplicate tag on one route throws
L. Animations and transitions
an AnimatedFoo widget animates to the new value whenever it is rebuilt with a different target
implicit widgets interpolate their own properties only, so swapping the child or coordinating two widgets needs an explicit animation
AnimationController drives a value from 0 to 1 over a duration and needs a TickerProvider
SingleTickerProviderStateMixin binds the ticker to the widget so it stops off-screen, and the controller must be disposed
a Tween maps the controller's 0-to-1 value onto real values and stores no state of its own
a CurvedAnimation reshapes the passage of time, changing the feel without changing the duration
AnimatedBuilder rebuilds only its builder, and the child it is handed is built once and reused every tick
M. Assets, fonts and pubspec
an asset is unreadable at runtime until it is listed under flutter: assets: at the correct YAML indentation
an entry ending in a slash includes that directory's files but not the files in its subdirectories
2.0x/ and 3.0x/ sibling folders are selected automatically from the device pixel ratio
Image.asset resolves from the bundle; Image.network fetches and therefore needs loading and error states
a font family is declared once with one asset entry per weight or style, otherwise the style is synthesized
^1.2.3 means at least 1.2.3 and below 2.0.0, but for a 0.x version the caret pins the minor number
N. Platform channels and plugins
a MethodChannel is matched purely by its name string, which must be identical on both sides
invokeMethod returns a Future and its arguments are serialized by the standard codec, so only supported types cross
PlatformException means the native handler failed; MissingPluginException means no handler was registered
an EventChannel streams native events into Dart, where a MethodChannel only answers one call at a time
channel handlers run on the platform's main thread, so slow native work has to be moved off it
dart:ffi calls native C directly with no serialization; channels carry structured messages across the boundary
O. Testing
unit tests exercise logic, widget tests a widget tree in a fake environment, integration tests a real app on a device
pumpWidget mounts a tree and renders one frame; most trees need a MaterialApp or Directionality ancestor to build
pump() renders exactly one frame, optionally after advancing the fake clock by a duration
pumpAndSettle repeats until no frame is scheduled, so it times out on a repeating animation
tap and enterText dispatch the event but do not rebuild; assert only after pumping
find.text, byType, byKey and byIcon locate widgets lazily and are re-evaluated at each use
findsOneWidget, findsNothing and findsNWidgets assert on how many widgets the finder matched
a widget test runs in a fake async zone where real timers, network calls and platform channels never fire
P. Common errors and debugging
the yellow-and-black stripes mean a Row or Column's children exceed its main axis; make a child flexible or scrollable
"viewport was given unbounded height" comes from a scrollable inside a Column; wrap it in Expanded or give it a height
marking a widget dirty while a build is already in progress throws; defer it to a post-frame callback
a future created inside build restarts on every rebuild; create it once in initState and store it
"Incorrect use of ParentDataWidget" means an Expanded or Positioned is not directly inside its Flex or Stack
mounting the same GlobalKey in two places at once throws, usually from a key reused across list items
the red error screen appears in debug builds; release shows a grey box unless ErrorWidget.builder is replaced
Q. Performance and the rebuild traps
setState dirties the whole State's subtree, so state held higher than it needs to be rebuilds far more widgets
a const child is the identical instance across rebuilds, so the framework skips it entirely
handing an unchanging subtree to a builder's child parameter keeps it out of the per-tick rebuild
rebuilding widgets is inexpensive; layout, painting and rasterization are what miss the frame budget
the timeline separates UI-thread build time from raster time, and each of the two points at a different fix
Opacity, clips with anti-aliasing and shadows can force a saveLayer, which is one of the most expensive raster operations
a RepaintBoundary isolates a frequently repainting subtree at the cost of an extra layer, so it can also make things worse
performance must be measured in profile mode, because debug builds are unoptimized and assert-heavy
R. Build, release and tooling
debug runs the JIT with asserts, profile keeps tracing enabled, release compiles ahead of time and strips both
hot reload injects changed code and rebuilds the tree while preserving State, so main and initState do not run again
a hot restart reruns main and discards all state, and is required for changes hot reload cannot apply
store distribution expects an app bundle; a universal APK carries every architecture in one larger file
a release build must be signed with your own key, since the debug signing key is rejected by the stores
--obfuscate with --split-debug-info renames symbols and makes the saved symbol file mandatory to read a crash trace
flavors build the same source against different application ids, assets and endpoints from one project
--analyze-size, icon-font tree shaking and deferred components are the documented ways to shrink a build
Keentune is not affiliated with or endorsed by the organizations whose documentation informs these maps.
All about Flutter practice
Also on your phone
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.
© 2026 SportaApp LLC