DEV Community

kanta13jp1
kanta13jp1

Posted on

Dart Async Programming: Future, Stream, and Isolate — A Complete Guide

Dart Async Programming: Future, Stream, and Isolate — A Complete Guide

Freezes and jank in Flutter apps usually trace back to misunderstanding async. Three concepts, one clear guide.

Why Async Matters

Blocking the UI thread (main isolate) means:
  → Frames miss the 16ms deadline
  → Jank / dropped frames
  → ANR / UI freeze

Solutions:
  Future  → one-shot async operation (API calls, DB queries)
  Stream  → continuous data flow (Supabase Realtime, sensors)
  Isolate → offload CPU-heavy work to a background thread
Enter fullscreen mode Exit fullscreen mode

Future: One-Shot Async Operations

// Basic form
Future<String> fetchUserName(String userId) async {
  final response = await supabase
    .from('users')
    .select('name')
    .eq('id', userId)
    .single();
  return response['name'] as String;
}

// Error handling
Future<String?> fetchUserNameSafe(String userId) async {
  try {
    return await fetchUserName(userId);
  } on PostgrestException catch (e) {
    debugPrint('DB error: ${e.message}');
    return null;
  }
}

// Parallel execution (faster than sequential)
final results = await Future.wait([
  fetchUserName(userId),
  fetchUserPlan(userId),
  fetchUserStats(userId),
]);
Enter fullscreen mode Exit fullscreen mode

Common mistake:

// ❌ Returns a Future but never awaits it
void loadData() {
  fetchUserName(userId); // result ignored
}

// ✅ Await it
Future<void> loadData() async {
  final name = await fetchUserName(userId);
  setState(() => _name = name);
}
Enter fullscreen mode Exit fullscreen mode

Stream: Continuous Data Flow

// Supabase Realtime as a Stream
class _ChatPageState extends State<ChatPage> {
  late final Stream<List<Map<String, dynamic>>> _messagesStream;

  @override
  void initState() {
    super.initState();
    _messagesStream = supabase
      .from('messages')
      .stream(primaryKey: ['id'])
      .eq('room_id', widget.roomId)
      .order('created_at');
  }

  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
      stream: _messagesStream,
      builder: (context, snapshot) {
        if (!snapshot.hasData) return const CircularProgressIndicator();
        return ListView.builder(
          itemCount: snapshot.data!.length,
          itemBuilder: (_, i) => MessageTile(message: snapshot.data![i]),
        );
      },
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Custom Stream with StreamController:

class KpiService {
  final _controller = StreamController<KpiData>.broadcast();
  Stream<KpiData> get stream => _controller.stream;

  void update(KpiData data) => _controller.add(data);
  void dispose() => _controller.close();
}
Enter fullscreen mode Exit fullscreen mode

Isolate: Offload CPU-Heavy Work

// compute() — Flutter's easy Isolate wrapper
Future<List<ProcessedItem>> processLargeDataset(
  List<RawItem> rawItems,
) async {
  return compute(_processItems, rawItems);
}

// Must be a top-level function (Isolate constraint)
List<ProcessedItem> _processItems(List<RawItem> items) {
  return items.map((item) => ProcessedItem(
    id: item.id,
    hash: sha256.convert(utf8.encode(item.data)).toString(),
  )).toList();
}
Enter fullscreen mode Exit fullscreen mode

Long-running background worker with Isolate.spawn:

Future<void> startBackgroundWorker() async {
  final receivePort = ReceivePort();
  await Isolate.spawn(_workerEntryPoint, receivePort.sendPort);

  await for (final message in receivePort) {
    if (message is WorkerResult) _handleResult(message);
  }
}

void _workerEntryPoint(SendPort sendPort) {
  while (true) {
    // heavy work...
    sendPort.send(WorkerResult(data: 'result'));
  }
}
Enter fullscreen mode Exit fullscreen mode

When to Use What

Future  → API / DB / file I/O (completes once)
Stream  → Realtime / WebSocket / sensors (arrives continuously)
Isolate → heavy computation / bulk data / encryption (pegs the CPU)
Enter fullscreen mode Exit fullscreen mode

Summary

Rules:
  1. Never block the UI thread — use async/await
  2. Parallelize independent work with Future.wait
  3. Realtime data = Stream + StreamBuilder
  4. CPU-heavy work = compute() → Isolate
  5. Always handle errors with try/catch
Enter fullscreen mode Exit fullscreen mode

Most Flutter "slowness" is fixable with compute(). Start there.

Top comments (0)