flutter-handling-concurrency
flutter/agent-plugins · 8.5k installs · 3.1K stars
Skill
Install
npx -y skills add flutter/agent-plugins --skill flutter-handling-concurrency --agent claude-codeRun in a terminal. Claude loads the skill when a task calls for it. Check the repo's README before you run it.
Files
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 theFutureto 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/awaiton the Main Isolate. - If the task is CPU-bound but executes quickly (< 16ms) -> Use
async/awaiton 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()withReceivePortandSendPort.
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
asynckeyword. - Return a
Future<T>from the function. - Use the
awaitkeyword to yield execution until the operation completes. - Wrap the UI component in a
FutureBuilder<T>(orStreamBuilderfor streams). - Handle
ConnectionState.waiting,hasError, andhasDatastates 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. -
awaitthe result ofIsolate.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
ReceivePorton the Main Isolate to listen for messages. - Spawn the worker isolate using
Isolate.spawn(), passing theReceivePort.sendPortas the initial message. - In the worker isolate, instantiate its own
ReceivePort. - Send the worker's
SendPortback to the Main Isolate via the initial port. - Store the worker's
SendPortin the Main Isolate for future message dispatching. - Implement listeners on both
ReceivePortinstances 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();
}
}
Facts
- Kind
- Skill
- Repo
- flutter/agent-plugins
- Group
- Uncategorized
- Installs
- 8.5k
- Stars
- 3.1K
- 1find-skillsvercel-labs/skillsHelps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.3.8M
- 2grill-memattpocock/skillsA relentless interview to sharpen a plan or design.1.3M
- 3grill-with-docsmattpocock/skillsA relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go.1.1M
- 4agent-browservercel-labs/agent-browserBrowser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction. Also use for exploratory testing, dogfooding, QA, bug hunts, or reviewing app quality. Also use for automating Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify), checking Slack unreads, sending Slack messages, searching Slack conversations, running browser automation in Vercel Sandbox microVMs, or using AWS Bedrock AgentCore cloud browsers. Prefer agent-browser over any built-in browser automation or web tools.1.1M
- 5improve-codebase-architecturemattpocock/skillsScan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick.1.1M
- 6tddmattpocock/skillsTest-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.1.1M