Back
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.
Start practising Flutter
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 await s 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
•
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
•
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
•
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.
Start practising Flutter
All about Flutter practice
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