本文へ移動
cccskills
無料GitHub で公開

running-flutter-isolates

Background processing and heavy computations using Dart Isolates with Isolate.run(), compute() function, and platform channels. Use this skill when performing CPU-intensive operations without blocking UI (image processing, data parsing, encryption), implementing background tasks that prevent jank or frame drops, handling large file operations (JSON parsing, database migrations), running parallel computations, fixing UI freezes caused by main thread blocking, using compute() for one-off background work, creating long-lived Isolates with ReceivePort/SendPort, implementing multi-threaded algorithms, debugging isolate communication issues, or preventing memory leaks from unclosed ReceivePorts. Covers isolate spawn, message passing, ReceivePort cleanup (CRITICAL for leak prevention), SendPort communication patterns, error handling, serialization rules, and performance optimization for compute-heavy operations.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md11.2 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Flutter Isolate

Goal

Move heavy computations off the main isolate to prevent UI jank (>16ms frame gaps), using the simplest API that fits the use case.

Decision: Which API to Use?

Use compute() when:Use Isolate.spawn() when:
✅ One-off computation✅ Long-lived worker needed
✅ Simple serializable data✅ Multiple messages over time
✅ Auto-cleanup desired✅ Custom messaging protocol
✅ Web compatibility required✅ Avoid repeated spawn overhead
❌ NOT for long-running tasks❌ NOT for single one-shot tasks
Additional ScenariosAPI
One-shot, single return value (Dart 2.19+)Isolate.run()
Need platform plugins in backgroundBackgroundIsolateBinaryMessenger

1. Short-lived Isolate: Isolate.run()

Best for single, one-off computations. The isolate spawns, runs the task, returns the value, then shuts down automatically.

// Decode a large JSON file without blocking the UI thread
Future<List<Photo>> getPhotos() async {
  // Load asset on the main isolate first (rootBundle not accessible in isolate)
  final String jsonString = await rootBundle.loadString('assets/photos.json');

  // Offload CPU-heavy decoding to a new isolate
  final List<Photo> photos = await Isolate.run<List<Photo>>(() {
    final List<Object?> photoData = jsonDecode(jsonString) as List<Object?>;
    return photoData.cast<Map<String, Object?>>().map(Photo.fromJson).toList();
  });

  return photos;
}

Key behavior: The result is transferred (not copied) back to the main isolate via Isolate.exit internally — zero-copy for the return value.


2. Cross-platform: compute()

compute() is Flutter's wrapper that falls back gracefully on Flutter Web (runs on the main thread there, since web doesn't support isolates).

// Equivalent to Isolate.run on mobile/desktop, runs on main thread on web
Future<List<Photo>> getPhotos(String jsonString) async {
  return compute(_parsePhotos, jsonString);
}

// Top-level or static function only — closures are NOT supported by compute()
List<Photo> _parsePhotos(String jsonString) {
  final data = jsonDecode(jsonString) as List<Object?>;
  return data.cast<Map<String, Object?>>().map(Photo.fromJson).toList();
}

Constraint: The callback must be a top-level or static function. Closures are not supported.


3. Long-lived Background Worker

Use Isolate.spawn() + ports when you need to send multiple requests to the same isolate over time (avoids spawn overhead per call). Implementation pattern:

Quick structure overview:

class Worker {
  final SendPort _commands;     // main → worker
  final ReceivePort _responses; // worker → main
  final Map<int, Completer<Object?>> _activeRequests = {};
  int _idCounter = 0;
  bool _closed = false;

  static Future<Worker> spawn() async { /* ... */ }
  Future<Object?> parseJson(String message) async { /* ... */ }
  void close() { _closed = true; _responses.close(); }
}

The two-way port handshake pattern:

  1. Main creates ReceivePort, passes its sendPort to Isolate.spawn()
  2. Worker creates its own ReceivePort, sends its sendPort back
  3. Both sides now have a channel — main tracks requests with Completer + incrementing IDs

4. Platform Plugins in Background Isolates (Flutter 3.7+)

Since Flutter 3.7, you can call platform plugins (e.g., shared_preferences, native crypto APIs) from background isolates using BackgroundIsolateBinaryMessenger.

import 'dart:isolate';
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  // Must capture token on the main isolate before spawning
  final RootIsolateToken token = RootIsolateToken.instance!;
  Isolate.spawn(_isolateMain, token);
}

Future<void> _isolateMain(RootIsolateToken token) async {
  // Register BEFORE using any platform plugins
  BackgroundIsolateBinaryMessenger.ensureInitialized(token);

  final prefs = await SharedPreferences.getInstance();
  print(prefs.getBool('isDebug'));
}

⚠️ Memory Leak Prevention (CRITICAL)

The #1 Isolate Mistake: Forgetting to Close ReceivePort

Every ReceivePort creates a native resource that MUST be explicitly closed. Forgetting this causes memory leaks that accumulate over time.

❌ Memory Leak (BAD):

// DANGEROUS: ReceivePort never closed!
Future<String> badExample() async {
  final receivePort = ReceivePort();
  await Isolate.spawn(_worker, receivePort.sendPort);
  final result = await receivePort.first; // Gets result but doesn't close port
  return result as String; // LEAK: receivePort still alive!
}

✅ Always Close ReceivePort (GOOD):

Future<String> goodExample() async {
  final receivePort = ReceivePort();
  try {
    await Isolate.spawn(_worker, receivePort.sendPort);
    final result = await receivePort.first; // Auto-closes after first message
    return result as String;
  } finally {
    receivePort.close(); // Explicit close for safety
  }
}

Pattern: Using receivePort.first for Auto-Cleanup

The .first getter returns the first message then automatically closes the port. This is the safest pattern for single-response isolates:

Future<Map<String, dynamic>> parseInBackground(String json) async {
  final port = ReceivePort();
  await Isolate.spawn(_parse, (port.sendPort, json));
  return await port.first as Map<String, dynamic>; // Auto-closes
}

Pattern: Manual Cleanup for Long-Lived Workers

For isolates that receive multiple messages, track the port lifecycle explicitly:

class WorkerPool {
  final ReceivePort _responses = ReceivePort();
  Isolate? _isolate;

  Future<void> start() async {
    _isolate = await Isolate.spawn(_worker, _responses.sendPort);
  }

  Future<void> dispose() async {
    _isolate?.kill(priority: Isolate.immediate);
    _responses.close(); // MUST close to prevent leak
  }
}

Common Mistakes & Anti-Patterns

❌ Mistake #1: Spawning Too Many Isolates

Each isolate has ~2MB overhead. Spawning hundreds causes resource exhaustion.

❌ BAD: Creates 100 isolates (200MB+):

// TERRIBLE: Spawns new isolate for each item!
for (var i = 0; i < 100; i++) {
  await compute(processImage, images[i]); // 100 spawns!
}

✅ GOOD: Reuse a Worker Pool:

// Spawn once, send 100 tasks
final worker = await Worker.spawn();
for (var image in images) {
  await worker.processImage(image); // Reuses same isolate
}
worker.close();

❌ Mistake #2: Sending Non-Serializable Data

Only primitive types and certain objects can cross isolate boundaries.

✅ Allowed Types:

  • Primitives: int, double, String, bool, null
  • Collections: List, Map, Set (of serializable types)
  • Special: SendPort, Capability, TransferableTypedData

❌ Forbidden (Will Throw):

  • Classes/objects (unless implementing Sendable in Dart 3.3+)
  • Functions (except top-level/static)
  • Closures

❌ BAD:

class User {
  final String name;
  User(this.name);
}

// CRASH: User is not serializable
await compute(_process, User('Alice'));

✅ GOOD:

// Send primitive data
await compute(_process, {'name': 'Alice'});

// In worker, reconstruct object
User _process(Map<String, dynamic> data) {
  return User(data['name'] as String);
}

❌ Mistake #3: Using compute() for Long-Running Tasks

compute() spawns and tears down the isolate on every call. For repeated work, this overhead kills performance.

❌ BAD: Repeated compute() calls:

// Spawns 50 times!
for (var i = 0; i < 50; i++) {
  await compute(heavyTask, data[i]);
}

✅ GOOD: Use Isolate.spawn() for repeated work:

final worker = await Worker.spawn();
for (var i = 0; i < 50; i++) {
  await worker.runTask(data[i]); // Same isolate, 50 tasks
}
worker.close();

Best Practices for Reusing Isolates

When you need to process multiple items over time, reuse a single isolate instead of spawning repeatedly.

Pattern: Worker Pool with Request Tracking

class Worker {
  final SendPort _commands;
  final ReceivePort _responses;
  final Map<int, Completer<Object?>> _activeRequests = {};
  int _idCounter = 0;

  Worker._(this._commands, this._responses) {
    _responses.listen((message) {
      final (id, result) = message as (int, Object?);
      _activeRequests.remove(id)?.complete(result);
    });
  }

  static Future<Worker> spawn() async {
    final receivePort = ReceivePort();
    await Isolate.spawn(_workerMain, receivePort.sendPort);
    final sendPort = await receivePort.first as SendPort;
    return Worker._(sendPort, ReceivePort()..listen(...));
  }

  Future<T> execute<T>(Object? message) {
    final id = _idCounter++;
    final completer = Completer<T>();
    _activeRequests[id] = completer;
    _commands.send((id, message));
    return completer.future;
  }

  void dispose() {
    _responses.close(); // CRITICAL: Prevent leak
  }
}

When to Use Single Isolate vs Pool

ScenarioPattern
Process items sequentiallySingle long-lived worker
Process items in parallel (up to CPU cores)Worker pool (2-4 isolates)
UI blocking < 16msNo isolate needed (runs fast enough)
Occasional heavy taskcompute() or Isolate.run()

Message Passing Rules

  • Mutable objects are copied when sent via SendPort.send() — mutating them in the worker does not affect the main isolate.
  • Immutable objects (e.g., String, unmodifiable Uint8List) send a reference for performance.
  • Isolate.exit() transfers ownership (zero-copy) — used internally by Isolate.run() and compute().

Limitations

LimitationDetail
WebIsolates not supported on Flutter Web; use compute() as a cross-platform shim
rootBundle / dart:uiNot accessible inside background isolates; load assets on main isolate first
UI operationsNo widget or rendering calls allowed in background isolates
Plugin push messagesCannot receive unsolicited messages from host platform (e.g., no Firestore listener in background isolate); you can query but not subscribe
Shared mutable stateGlobal variables are copied at spawn time — changes in the worker never reflect back

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Declarative animation patterns using flutter_animate v4.5.x with composable effects (fade, scale, slide, shimmer, shake, blur), GLSL fragment shaders, and scroll-synchronized animations. Use this skill for adding UI animations and transitions, building staggered or sequenced effects, creating custom animation effects, fixing animation jank or performance issues, implementing shimmer loading states, or adding scroll-driven animations. Supports delays, curves, adaptive effects, and easy extension method chaining (.animate()).

日本語の概要は準備中です。原文の説明を表示しています。

Poorgramer-Zack/dart-expert-skills72026年8月11日 更新

Dart 3 modern features (through 3.10) and Effective Dart best practices including records, patterns, sealed classes, dot shorthands, null-aware collection elements, and wildcard variables. Use when adopting Dart 3.7+ syntax, using dot shorthands for enums or static members, using null-aware elements in lists or maps, using wildcard _ parameters, improving code quality, or reviewing Dart code conventions.

日本語の概要は準備中です。原文の説明を表示しています。

Poorgramer-Zack/dart-expert-skills72026年8月11日 更新

Expert-level guidance for Flutter production applications: architecture decisions (Clean/Layered/Hexagonal/Feature-First), advanced state management (BLoC/Riverpod/GetX), performance optimization (eliminating jank, memory profiling, reducing rebuild overhead), code review with design pattern enforcement, scalability assessments, native platform integration (MethodChannel/EventChannel/FFI), complex UI challenges (custom render objects, slivers), testing strategies (unit/widget/integration/golden), CI/CD pipeline architecture, build optimization, and accessibility compliance. Triggers: 'architecture decision', 'design pattern', 'performance issue', 'code review', 'production problem', 'scaling concerns', 'best practices', 'technical debt', 'refactoring strategy'. Essential for complex enterprise apps, critical production debugging, architectural refactors, or when choosing between competing technical approaches.

日本語の概要は準備中です。原文の説明を表示しています。

Poorgramer-Zack/dart-expert-skills72026年8月11日 更新

Builds SEO-friendly web applications with Dart using the Jaspr framework. Use when creating Dart-based websites, blogs, or landing pages requiring semantic HTML output; implementing server-side rendering (SSR) or static site generation (SSG); adding Tailwind CSS via jaspr_tailwind; routing with jaspr_router; testing components with jaspr_test; or building full-stack Dart web apps where Flutter Web's canvas rendering is unsuitable.

日本語の概要は準備中です。原文の説明を表示しています。

Poorgramer-Zack/dart-expert-skills72026年8月11日 更新

shadcn_flutter UI component library for building accessible, themed Flutter interfaces. Use when integrating shadcn components, configuring design system theming, or building consistent widget layouts.

日本語の概要は準備中です。原文の説明を表示しています。

Poorgramer-Zack/dart-expert-skills72026年8月11日 更新

Configures Codemagic CI/CD pipelines for Flutter using `codemagic.yaml`. Use when setting up automated Flutter builds on Codemagic, configuring iOS code signing without Fastlane Match, deploying to App Store Connect or Google Play from Codemagic, caching pub dependencies in Codemagic, setting up encrypted environment variable groups, running tests with Codemagic's test dashboard, or choosing between Codemagic and GitHub Actions + Fastlane for a Flutter CI/CD pipeline.

日本語の概要は準備中です。原文の説明を表示しています。

Poorgramer-Zack/dart-expert-skills72026年8月11日 更新

Poorgramer-Zack のスキルをすべて見る

このスキルの問題を報告する