flutter-handling-concurrency

Executes long-running tasks in background isolates to keep the UI responsive. Use when performing heavy computations or parsing large datasets.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "flutter-handling-concurrency" with this command: npx skills add flutter/skills/flutter-skills-flutter-handling-concurrency

Managing Dart Concurrency and Isolates

Contents

Core Concepts

Dart utilizes a single-threaded execution model driven by an Event Loop (comparable to the iOS main loop). By default, all Flutter application code runs on the Main Isolate.

  • Asynchronous Operations (async/await): Use for non-blocking I/O tasks (network requests, file access). The Event Loop continues processing other events while waiting for the Future to complete.
  • Isolates: Dart's implementation of lightweight threads. Isolates possess their own isolated memory and do not share state. They communicate exclusively via message passing.
  • Main Isolate: The default thread where UI rendering and event handling occur. Blocking this isolate causes UI freezing (jank).
  • Worker Isolate: A spawned isolate used to offload CPU-bound tasks (e.g., decoding large JSON blobs) to prevent Main Isolate blockage.

Decision Matrix: Async vs. Isolates

Apply the following conditional logic to determine the correct concurrency approach:

  • If the task is I/O bound (e.g., HTTP request, database read) -> Use async/await on the Main Isolate.
  • If the task is CPU-bound but executes quickly (< 16ms) -> Use async/await on the Main Isolate.
  • If the task is CPU-bound, takes significant time, and runs once (e.g., parsing a massive JSON payload) -> Use Isolate.run().
  • If the task requires continuous or repeated background processing with multiple messages passed over time -> Use Isolate.spawn() with ReceivePort and SendPort.

Workflows

Implementing Standard Asynchronous UI

Use this workflow to fetch and display non-blocking asynchronous data.

Task Progress:

  • Mark the data-fetching function with the async keyword.
  • Return a Future<T> from the function.
  • Use the await keyword to yield execution until the operation completes.
  • Wrap the UI component in a FutureBuilder<T> (or StreamBuilder for streams).
  • Handle ConnectionState.waiting, hasError, and hasData states within the builder.
  • Run validator -> review UI for loading indicators -> fix missing states.

Offloading Short-Lived Heavy Computation

Use this workflow for one-off, CPU-intensive tasks using Dart 2.19+.

Task Progress:

  • Identify the CPU-bound operation blocking the Main Isolate.
  • Extract the computation into a standalone callback function.
  • Ensure the callback function signature accepts exactly one required, unnamed argument (as per specific architectural constraints).
  • Invoke Isolate.run() passing the callback.
  • await the result of Isolate.run() in the Main Isolate.
  • Assign the returned value to the application state.

Establishing Long-Lived Worker Isolates

Use this workflow for persistent background processes requiring continuous bidirectional communication.

Task Progress:

  • Instantiate a ReceivePort on the Main Isolate to listen for messages.
  • Spawn the worker isolate using Isolate.spawn(), passing the ReceivePort.sendPort as the initial message.
  • In the worker isolate, instantiate its own ReceivePort.
  • Send the worker's SendPort back to the Main Isolate via the initial port.
  • Store the worker's SendPort in the Main Isolate for future message dispatching.
  • Implement listeners on both ReceivePort instances to handle incoming messages.
  • Run validator -> review memory leaks -> ensure ports are closed when the isolate is no longer needed.

Examples

Example 1: Asynchronous UI with FutureBuilder

// 1. Define the async operation
Future<String> fetchUserData() async {
  await Future.delayed(const Duration(seconds: 2)); // Simulate network I/O
  return "User Data Loaded";
}

// 2. Consume in the UI
Widget build(BuildContext context) {
  return FutureBuilder<String>(
    future: fetchUserData(),
    builder: (context, snapshot) {
      if (snapshot.connectionState == ConnectionState.waiting) {
        return const CircularProgressIndicator();
      } else if (snapshot.hasError) {
        return Text('Error: ${snapshot.error}');
      } else {
        return Text('Result: ${snapshot.data}');
      }
    },
  );
}

Example 2: Short-Lived Isolate (Isolate.run)

import 'dart:isolate';
import 'dart:convert';

// 1. Define the heavy computation callback
// Note: Adhering to the strict single-argument signature requirement.
List<dynamic> decodeHeavyJson(String jsonString) {
  return jsonDecode(jsonString) as List<dynamic>;
}

// 2. Offload to a worker isolate
Future<List<dynamic>> processDataInBackground(String rawJson) async {
  // Isolate.run spawns the isolate, runs the computation, returns the value, and exits.
  final result = await Isolate.run(() => decodeHeavyJson(rawJson));
  return result;
}

Example 3: Long-Lived Isolate (ReceivePort / SendPort)

import 'dart:isolate';

class WorkerManager {
  late SendPort _workerSendPort;
  final ReceivePort _mainReceivePort = ReceivePort();
  Isolate? _isolate;

  Future<void> initialize() async {
    // 1. Spawn isolate and pass the Main Isolate's SendPort
    _isolate = await Isolate.spawn(_workerEntry, _mainReceivePort.sendPort);

    // 2. Listen for messages from the Worker Isolate
    _mainReceivePort.listen((message) {
      if (message is SendPort) {
        // First message is the Worker's SendPort
        _workerSendPort = message;
        _startCommunication();
      } else {
        // Subsequent messages are data payloads
        print('Main Isolate received: $message');
      }
    });
  }

  void _startCommunication() {
    // Send data to the worker
    _workerSendPort.send("Process this data");
  }

  // 3. Worker Isolate Entry Point
  static void _workerEntry(SendPort mainSendPort) {
    final workerReceivePort = ReceivePort();
    
    // Send the Worker's SendPort back to the Main Isolate
    mainSendPort.send(workerReceivePort.sendPort);

    // Listen for incoming tasks
    workerReceivePort.listen((message) {
      print('Worker Isolate received: $message');
      
      // Perform work and send result back
      final result = "Processed: $message";
      mainSendPort.send(result);
    });
  }

  void dispose() {
    _mainReceivePort.close();
    _isolate?.kill();
  }
}

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

flutter-layout

No summary provided by upstream source.

Repository SourceNeeds Review
1.2K-flutter
General

flutter-performance

No summary provided by upstream source.

Repository SourceNeeds Review
1.2K-flutter
General

flutter-theming

No summary provided by upstream source.

Repository SourceNeeds Review
1.1K-flutter
General

flutter-animation

No summary provided by upstream source.

Repository SourceNeeds Review
1.1K-flutter