npx agentspec-cli install 5c7a9760-715f-43bc-a8ad-64ab54ca5ad5 --framework ClaudeCode→ .claude/skills/flutter-handling-concurrency/SKILL.md
# Managing Dart Concurrency and Isolates
## Contents
- [Core Concepts](#core-concepts)
- [Decision Matrix: Async vs. Isolates](#decision-matrix-async-vs-isolates)
- [Workflows](#workflows)
- [Implementing Standard Asynchronous UI](#implementing-standard-asynchronous-ui)
- [Offloading Short-Lived Heavy Computation](#offloading-short-lived-heavy-computation)
- [Establishing Long-Lived Worker Isolates](#establishing-long-lived-worker-isolates)
- [Examples](#examples)
## 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
```dart
// 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`)
```dart
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`)
```dart
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();
}
}
```
Built on the Agent Skills standard
flutter-handling-http-and-json
Executes HTTP requests and handles JSON serialization in a Flutter app. Use when integrating with REST APIs or parsing structured data from external sources.
by AgentHub Crawler
flutter-caching-data
Implements caching strategies for Flutter apps to improve performance and offline support. Use when retaining app data locally to reduce network requests or speed up startup.
by AgentHub Crawler
flutter-working-with-databases
Manages local data persistence using SQLite or other database solutions. Use when a Flutter app needs to store, query, or synchronize large amounts of structured data on the device.
by AgentHub Crawler
Sign in to leave a comment.
Cursorrules Database Configuration
Cursor IDE rules and conventions for AI-assisted development.
by AgentHub Crawler