Diff between flutter widgetBinding and MicroTask? And architectural flow diagram how they renderer?
Answer
Overview
Flutter's execution model is built on Dart's event loop, which has two queues: the MicroTask Queue and the Event Queue. Understanding how these work — along with
WidgetsBindingDart Event Loop — The Core Concept
Dart is single-threaded. All async code runs on one thread using an event loop with a strict priority order:
text┌──────────────────────────────────────────────┐ │ DART EVENT LOOP │ │ │ │ Step 1: Run ALL synchronous code │ │ (top to bottom, blocking) │ │ ↓ │ │ Step 2: Drain ENTIRE MicroTask Queue │ │ (ALL microtasks before ANY event) │ │ ↓ │ │ Step 3: Pick ONE event from Event Queue │ │ (Future, Timer, I/O callback) │ │ ↓ │ │ Step 4: Go back to Step 2 │ │ (check microtasks again) │ └──────────────────────────────────────────────┘
Critical Rule: The MicroTask Queue is fully drained before each event from the Event Queue is processed.
scheduleMicrotask() vs Future.microtask()
Both add tasks to the MicroTask Queue, but they work differently.
scheduleMicrotask()
A low-level Dart function that directly schedules a callback on the microtask queue. It returns
voidawait.then()dartimport 'dart:async'; void main() { print('1. Sync start'); scheduleMicrotask(() { print('3. scheduleMicrotask executed'); }); print('2. Sync end'); } // Output: // 1. Sync start // 2. Sync end // 3. scheduleMicrotask executed
Future.microtask()
A higher-level wrapper that schedules a callback on the microtask queue AND returns a
Futureawait.then()dartvoid main() async { print('1. Sync start'); Future.microtask(() { print('3. Future.microtask executed'); return 'result'; // Can return a value }).then((value) { print('4. Got: $value'); }); print('2. Sync end'); } // Output: // 1. Sync start // 2. Sync end // 3. Future.microtask executed // 4. Got: result
scheduleMicrotask vs Future.microtask Comparison
| Feature | text | text |
|---|---|---|
| Queue | MicroTask Queue | MicroTask Queue |
| Returns | text | text |
| Awaitable | No | Yes |
| Chainable (.then) | No | Yes |
| Can return value | No | Yes |
| Error handling | Unhandled (crashes zone) | Caught by Future error handling |
| Use case | Fire-and-forget side effects | Async operations needing result |
| Import | text | text |
dart// scheduleMicrotask — fire and forget scheduleMicrotask(() { print('Side effect — no way to get result'); }); // Future.microtask — can await result final result = await Future.microtask(() { return computeSomething(); }); print('Got: $result');
MicroTask Queue vs Event Queue (Future)
This is the most important distinction. MicroTasks ALWAYS run before Events.
The Proof — Execution Order
dartimport 'dart:async'; void main() { print('1. Sync code — start'); // Event Queue Future(() => print('6. Future (Event Queue)')); // Event Queue (delayed) Future.delayed(Duration.zero, () => print('7. Future.delayed (Event Queue)')); // MicroTask Queue scheduleMicrotask(() => print('3. scheduleMicrotask (MicroTask Queue)')); // MicroTask Queue Future.microtask(() => print('4. Future.microtask (MicroTask Queue)')); // MicroTask Queue (Future.value resolves as microtask) Future.value('resolved').then((_) => print('5. Future.value.then (MicroTask Queue)')); print('2. Sync code — end'); } // Output: // 1. Sync code — start // 2. Sync code — end // 3. scheduleMicrotask (MicroTask Queue) // 4. Future.microtask (MicroTask Queue) // 5. Future.value.then (MicroTask Queue) // 6. Future (Event Queue) // 7. Future.delayed (Event Queue)
Why This Order?
textStep 1: Run sync code → prints 1, 2 Step 2: Drain MicroTask Queue → prints 3, 4, 5 (scheduleMicrotask, Future.microtask, Future.value.then) Step 3: Pick first Event → prints 6 (Future) Step 4: Check MicroTask Queue → empty Step 5: Pick next Event → prints 7 (Future.delayed)
What Goes Where?
| Goes to MicroTask Queue | Goes to Event Queue |
|---|---|
text | text |
text | text |
text | text |
text text | text |
| Already resolved Future's text | I/O callbacks (HTTP, file) |
text |
Nested MicroTasks — The Starvation Problem
MicroTasks can schedule more microtasks, and ALL of them run before any Event:
dartvoid main() { print('1. Sync'); Future(() => print('5. Event Queue — delayed because microtasks keep running')); scheduleMicrotask(() { print('2. MicroTask 1'); scheduleMicrotask(() { print('3. MicroTask 2 (nested)'); scheduleMicrotask(() { print('4. MicroTask 3 (nested again)'); }); }); }); } // Output: // 1. Sync // 2. MicroTask 1 // 3. MicroTask 2 (nested) // 4. MicroTask 3 (nested again) // 5. Event Queue — delayed because microtasks keep running
Warning: If microtasks keep scheduling more microtasks infinitely, the Event Queue starves — Futures never execute, UI never updates, app freezes.
WidgetsBinding — Flutter's Frame Callbacks
WidgetsBindingdartclass MyWidget extends StatefulWidget { _MyWidgetState createState() => _MyWidgetState(); } class _MyWidgetState extends State<MyWidget> { void initState() { super.initState(); // Runs AFTER the frame is rendered (Event Queue — frame callback) WidgetsBinding.instance.addPostFrameCallback((_) { print('4. PostFrameCallback — after build + layout + paint'); // Safe to access widget dimensions here final size = (context.findRenderObject() as RenderBox).size; print(' Widget size: $size'); }); // MicroTask — runs before the frame scheduleMicrotask(() { print('2. MicroTask — before frame renders'); }); // Future — runs before the frame (Event Queue, but before frame) Future(() { print('3. Future — before frame renders'); }); print('1. initState — sync'); } Widget build(BuildContext context) { print(' build() called'); return Container(width: 200, height: 100); } } // Output: // 1. initState — sync // 2. MicroTask — before frame renders // build() called // 3. Future — before frame renders // 4. PostFrameCallback — after build + layout + paint // Widget size: Size(200.0, 100.0)
Complete Execution Priority
textPriority (highest to lowest): 1. Synchronous code → Runs immediately, blocks everything 2. MicroTask Queue → scheduleMicrotask(), Future.microtask() 3. Event Queue → Future(), Future.delayed(), Timer() 4. Frame scheduling → WidgetsBinding.addPostFrameCallback() 5. Persistent callbacks → WidgetsBinding.addPersistentFrameCallback()
Complete Example — All Together
dartimport 'dart:async'; import 'package:flutter/material.dart'; class EventLoopDemo extends StatefulWidget { _EventLoopDemoState createState() => _EventLoopDemoState(); } class _EventLoopDemoState extends State<EventLoopDemo> { void initState() { super.initState(); print('1. [SYNC] initState start'); // Event Queue Future(() => print('5. [EVENT] Future()')); // Event Queue (delayed) Future.delayed(Duration.zero, () => print('6. [EVENT] Future.delayed')); // MicroTask Queue scheduleMicrotask(() => print('3. [MICRO] scheduleMicrotask')); // MicroTask Queue Future.microtask(() => print('4. [MICRO] Future.microtask')); // Frame callback WidgetsBinding.instance.addPostFrameCallback((_) { print('7. [FRAME] PostFrameCallback'); }); print('2. [SYNC] initState end'); } Widget build(BuildContext context) { print(' [BUILD] build() called'); return Center(child: Text('Check console')); } } // Output: // 1. [SYNC] initState start // 2. [SYNC] initState end // 3. [MICRO] scheduleMicrotask // 4. [MICRO] Future.microtask // [BUILD] build() called // 5. [EVENT] Future() // 6. [EVENT] Future.delayed // 7. [FRAME] PostFrameCallback
When to Use What
| Scenario | Use | Why |
|---|---|---|
| Quick side effect before next event | text | Fire-and-forget, highest async priority |
| Async operation that returns a value | text | Awaitable, runs before events |
| API call, file I/O, delay | text text | Event Queue — doesn't block microtasks |
| Access widget size after render | text | Runs after build + layout + paint |
| Ensure code runs after setState rebuild | text | Guarantees frame is complete |
| Ensure code runs before next event | text | Microtasks drain before events |
Summary Table
| Feature | scheduleMicrotask | Future.microtask | Future() | WidgetsBinding Callback |
|---|---|---|---|---|
| Queue | MicroTask | MicroTask | Event | Frame (Event-based) |
| Priority | Highest async | Highest async | Lower | After frame render |
| Returns | text | text | text | text |
| Awaitable | No | Yes | Yes | No |
| Runs before build? | Yes | Yes | Maybe | No (after paint) |
| Use case | Side effects | Async with result | I/O, delays | Widget measurements |
Key Takeaway: Dart's event loop follows a strict order: Sync → MicroTasks (all) → Event (one) → MicroTasks (all) → Event (one) → .... MicroTasks always have priority over Events.
is fire-and-forget whiletextscheduleMicrotaskreturns a Future you can await. UsetextFuture.microtaskfor I/O and delays. UsetextFuture()callbacks when you need the frame to be fully rendered first.textWidgetsBinding
Learn more at Dart Event Loop and Flutter Rendering Pipeline.