Theory
Two seconds of doubt
Tap Book, and FestConnect Flutter spends 2 seconds saving to the server. Two seconds in which an unresponsive screen makes every student tap again: double bookings, support queue, chaos.
Unit 1 solved this on Android with a ProgressBar and visibility juggling. Flutter's version is leaner: 2 indicator widgets split by a single parameter, and: because everything is composition here: the professional loading overlay, built from a widget you already know: Stack, this topic's second name.
Theory
The 2 indicators, 1 deciding parameter
- LinearProgressIndicator: a horizontal bar
- CircularProgressIndicator: the spinner
Both obey one switch, the value: parameter:
- value: null (or simply omitted): indeterminate: animates forever, promising only "working": right for unknowable durations (network saves)
- value: 0.0 to 1.0: determinate: displays that fraction: 3 of 5 pass photos uploaded is value: 0.6
Note the scale: 0 to 1 as a double, not Android's setMax(100)/setProgress(60): the Unit 1 twin with different units, and a favourite compare question.
Practical
The loading overlay: Stack + indicator + a bool
class _BookingState extends State<BookingScreen> {
bool saving = false;
void book() {
setState(() { saving = true; }); // show the overlay
saveToServer().then((_) {
setState(() { saving = false; }); // hide in the completion path
});
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
bookingForm(), // the page itself
if (saving) ...[ // conditional children
Positioned.fill( // translucent blanket
child: Container(color: Colors.black54),
),
const Center( // the spinner on top
child: CircularProgressIndicator(), // value: null = spins
),
],
],
);
}
}
// Determinate cousin, for the 5-photo upload:
// LinearProgressIndicator(value: uploaded / 5) // 3 of 5 -> 0.6
Theory
Stack, the deeper pass
The invisible-widgets lesson introduced Stack as FrameLayout's twin; the overlay uses its 2 power tools:
- Positioned.fill: stretches a child over the ENTIRE stack: the translucent Container blankets the whole form, absorbing stray taps
- non-positioned children obey the stack's alignment (or a Center wrapper, as here): the spinner floats mid-screen
And the conditional if (saving) ...[ ] inside the children list is Flutter's idiom for widgets that exist only sometimes: the bool decides, setState flips it, the rebuild adds or removes the overlay: state deciding STRUCTURE, not just values.
Quiz
What is the difference between CircularProgressIndicator() and CircularProgressIndicator(value: 0.6)?
- None: value only changes the size
- The first spins forever (indeterminate); the second shows a fixed 60% arc (determinate)
- The first is invisible until value is supplied
- value: 0.6 makes it spin at 60% speed
Show the answer
The first spins forever (indeterminate); the second shows a fixed 60% arc (determinate)
One parameter carries the whole semantic split: null/omitted means "working, duration unknown": the eternal animation: while a 0-to-1 double means "this exact fraction done": a still arc at 60%. Choosing between them is honesty about what you know: a network save is indeterminate, a 5-photo upload is measurably determinate (uploaded / 5). Options C and D invent behaviours: the indicator is always visible once built, and value never modulates speed. And mind the scale trap for Android veterans: 0.6, never 60.
Think first
Audit the overlay's life cycle
Walk the listing's book() flow: what appears when, what does the black54 Container actually contribute besides dimming, and: recalling Unit 1's Android ProgressBar rule: what failure path is this simplified code not yet handling?
Show the answer
Tap: setState(saving = true): rebuild adds the blanket + spinner: the save runs behind them: .then setStates false: rebuild removes both. The Container's second job is armour: Positioned.fill means it INTERCEPTS every tap, so the double-booking double-tap dies at the blanket. The missing path: failure: if saveToServer errors, .then never runs and the overlay spins forever: Unit 1's rule (hide in EVERY outcome path) demands a .catchError that also setStates saving = false: the eternal-spinner bug, Flutter edition.
Watch out
Indicator and overlay slips
value: 60: the 0-to-1 scale means 60 is clamped nonsense; fractions, not percentages.
Overlay without the blanket: a naked spinner over a live form still lets taps through: Positioned.fill Container is the tap shield.
Hiding only on success: the reveal's eternal spinner: every path (success, error) must lower the flag.
Indeterminate for measurable work: a 5-photo upload with a mystery spinner wastes information the user would love to have.
Theory
Composition is the Flutter answer
Notice what Flutter did NOT give you: a LoadingOverlay widget. It gave you Stack, Positioned.fill, a Container, an indicator and a bool: and the overlay is their COMPOSITION, 10 honest lines. That is the framework's philosophy in one exhibit: few primitives, infinite assemblies: the same lesson as Unit 1's hand-composed ImageSlider. Next: the fest's event list returns one final time: Flutter's Lists, where ListView.builder completes the adapter story begun in this subject's very first lesson.
Summary
Key takeaways
- LinearProgressIndicator = bar, CircularProgressIndicator = spinner; one widget family, 2 shapes.
- value: null/omitted = indeterminate (spins forever); value: 0.0-1.0 = determinate fraction: 0-to-1, never 0-to-100.
- Indeterminate for unknowable waits (network), determinate for measurable ones (n of 5 uploads).
- The loading overlay = Stack + Positioned.fill translucent Container (tap shield) + centered spinner + a saving bool.
- if (cond) ...[ ] in a children list: state deciding structure.
- Hide the overlay in EVERY completion path or it spins forever.
- Memory hook: null spins, fractions fill, the blanket eats stray taps.