Diff between flutter widgetBinding and MicroTask? And architectural flow diagram how they renderer?

#flutter#dart#microtask#event-loop#future#async#widgets-binding

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

text
WidgetsBinding
frame callbacks — is essential for controlling execution order in Flutter.


Dart 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

text
void
— you cannot
text
await
it or chain
text
.then()
.

dart
import '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

text
Future
— so you can
text
await
it or chain
text
.then()
.

dart
void 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
scheduleMicrotask()
text
Future.microtask()
QueueMicroTask QueueMicroTask Queue
Returns
text
void
text
Future<T>
AwaitableNoYes
Chainable (.then)NoYes
Can return valueNoYes
Error handlingUnhandled (crashes zone)Caught by Future error handling
Use caseFire-and-forget side effectsAsync operations needing result
Import
text
dart:async
text
dart:async
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

dart
import '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?

text
Step 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 QueueGoes to Event Queue
text
scheduleMicrotask(() {})
text
Future(() {})
text
Future.microtask(() {})
text
Future.delayed(duration, () {})
text
Future.value(x).then(() {})
text
Timer(duration, () {})
text
completer.complete(x)
text
.then()
text
Stream.listen()
callbacks
Already resolved Future's
text
.then()
I/O callbacks (HTTP, file)
text
WidgetsBinding
frame callbacks

Nested MicroTasks — The Starvation Problem

MicroTasks can schedule more microtasks, and ALL of them run before any Event:

dart
void 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

text
WidgetsBinding
callbacks are part of Flutter's rendering pipeline, which runs as events in the Event Queue.

dart
class 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

text
Priority (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

dart
import '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

ScenarioUseWhy
Quick side effect before next event
text
scheduleMicrotask()
Fire-and-forget, highest async priority
Async operation that returns a value
text
Future.microtask()
Awaitable, runs before events
API call, file I/O, delay
text
Future()
/
text
Future.delayed()
Event Queue — doesn't block microtasks
Access widget size after render
text
addPostFrameCallback()
Runs after build + layout + paint
Ensure code runs after setState rebuild
text
addPostFrameCallback()
Guarantees frame is complete
Ensure code runs before next event
text
scheduleMicrotask()
Microtasks drain before events

Summary Table

FeaturescheduleMicrotaskFuture.microtaskFuture()WidgetsBinding Callback
QueueMicroTaskMicroTaskEventFrame (Event-based)
PriorityHighest asyncHighest asyncLowerAfter frame render
Returns
text
void
text
Future<T>
text
Future<T>
text
void
AwaitableNoYesYesNo
Runs before build?YesYesMaybeNo (after paint)
Use caseSide effectsAsync with resultI/O, delaysWidget 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.

text
scheduleMicrotask
is fire-and-forget while
text
Future.microtask
returns a Future you can await. Use
text
Future()
for I/O and delays. Use
text
WidgetsBinding
callbacks when you need the frame to be fully rendered first.

Learn more at Dart Event Loop and Flutter Rendering Pipeline.