{"slug": "share-state-across-dart-isolates-without-losing-your-mind-enter-shared-map", "title": "Share State Across Dart Isolates Without Losing Your Mind: Enter shared_map", "summary": "Dart engineer Graciliano M. Passos created package:shared_map, a synchronized Map data structure that lets Dart and Flutter developers share in-memory state across isolates without hand-rolling ReceivePort/SendPort plumbing. The library exposes a SharedMap on the main isolate, a serializable sharedReference() token passed into Isolate.run workers, and SharedMap.fromSharedReference() to reconstitute a proxy whose reads and writes sync back to the authoritative instance.", "body_md": "*This is Part 3 of the **Dart and Flutter** series—practical guides, architectural deep dives, and hard-earned engineering lessons from the field. Each article is completely standalone.*\n\nDart’s concurrency model is built on **Isolates**. Unlike threads in Java, C++, or Go, Dart isolates share no memory. Each isolate has its own private heap and its own single-threaded event loop.\n\nThis \"share-nothing\" model is a brilliant design decision. It completely eliminates data races, deadlocks, mutex contention, and tricky thread-synchronization bugs.\n\n**Until, of course, you actually *need* to share data across isolates.**\n\nImagine this common production scenario:\n\nYou’re building a Flutter app that crunches heavy data in the background—perhaps resizing multiple images, decoding massive JSON payloads, computing cryptographic hashes, or running complex ML calculations. To keep your UI silky smooth at 120 FPS, you offload the work to background isolates using `Isolate.run`.\n\nNow suppose all these concurrent background workers need access to a **shared, in-memory cache** (like parsed metadata, authentication tokens, or shared computation results) to avoid duplicate work.\n\nHow do you do that in Dart?\n\nTraditionally, you only had two bad choices:\n\n`ReceivePort` and `SendPort`. You have to invent custom request/response DTOs, generate unique request correlation IDs, wire up response completers, and write 150 lines of brittle plumbing just to perform a simple key-value lookup.\nThere is a third, vastly superior option that almost nobody talks about: **[`package:shared_map`](https://pub.dev/packages/shared_map)**.\n\n`shared_map`?\nCreated by veteran Dart engineer Graciliano M. Passos, `shared_map` provides a versatile, synchronized `Map` data structure designed specifically to be shared across Dart isolates and asynchronous workflows.\n\nHere is what makes it an architectural gem:\n\n`get()`, `put()`, `putIfAbsent()`, and `update()`.\nInstead of you manually orchestrating ports, `shared_map` manages the cross-isolate communication protocol transparently under the hood.\n\nThe core mental model of `shared_map` is dead simple:\n\n`SharedMap` on your primary isolate (like your Flutter UI thread or main server loop). This instance acts as the authoritative source of truth.`.sharedReference()` to generate a lightweight, serializable token.`Isolate.run`). Inside the isolate, you reconstruct a proxy instance using `SharedMap.fromSharedReference(ref)`.\nAny reads, writes, or mutations performed by the worker isolate are automatically dispatched back to the main instance and synchronized across all isolates!\n\nLet’s write a complete, self-contained example. We'll simulate multiple concurrent worker isolates crunching data, reading from a shared cache, and populating cache entries on the fly:\n\n```\nimport 'dart:isolate';\nimport 'package:shared_map/shared_map.dart';\n\nvoid main() async {\n  // 1. Create a SharedStore and a SharedMap on the main isolate\n  final store = SharedStore('app_cache');\n  final userCache = await store.getSharedMap<String, String>('users');\n\n  // Seed an initial value\n  await userCache!.put('user_101', 'Randal (Admin)');\n\n  // 2. Extract the lightweight, serializable reference\n  final cacheReference = userCache.sharedReference();\n\n  print('--- Spawning Background Worker 1 ---');\n\n  // 3. Pass the reference into a background isolate\n  final worker1Result = await Isolate.run(() async {\n    // Reconstitute the synchronized map proxy\n    final workerMap = SharedMap<String, String>.fromSharedReference(cacheReference);\n\n    // Read the value previously stored by the main isolate:\n    final user = await workerMap.get('user_101');\n    print('[Worker 1] Read from shared cache: $user');\n\n    // Put a new value into the shared cache from this background worker:\n    await workerMap.put('user_102', 'Wilhelm (Engineer)');\n    return 'Worker 1 finished';\n  });\n\n  print(worker1Result);\n\n  print('--- Spawning Background Worker 2 ---');\n\n  // 4. Spawn a second isolate to prove cross-isolate synchronization\n  final worker2Result = await Isolate.run(() async {\n    final workerMap = SharedMap<String, String>.fromSharedReference(cacheReference);\n\n    // Worker 2 can immediately read what Worker 1 just wrote!\n    final user102 = await workerMap.get('user_102');\n    print('[Worker 2] Read value written by Worker 1: $user102');\n\n    // Use putIfAbsent atomically\n    final user103 = await workerMap.putIfAbsent('user_103', 'Guest User');\n    return '[Worker 2] Added: $user103';\n  });\n\n  print(worker2Result);\n\n  // 5. Verify the main isolate reflects all updates\n  print('--- Back on Main Isolate ---');\n  print('Total entries in cache: ${await userCache.length()}');\n  print('user_102 on main: ${await userCache.get('user_102')}');\n  print('user_103 on main: ${await userCache.get('user_103')}');\n}\n--- Spawning Background Worker 1 ---\n[Worker 1] Read from shared cache: Randal (Admin)\nWorker 1 finished\n--- Spawning Background Worker 2 ---\n[Worker 2] Read value written by Worker 1: Wilhelm (Engineer)\n[Worker 2] Added: Guest User\n--- Back on Main Isolate ---\nTotal entries in cache: 3\nuser_102 on main: Wilhelm (Engineer)\nuser_103 on main: Guest User\n```\n\nNotice what just happened:\n\n`Completer`, or serialization boilerplate line was written.` SharedMapCached`\nIf your worker isolates perform thousands of rapid reads, you might not want every single `get()` call to perform a cross-isolate message dispatch.\n\n`shared_map` includes a built-in subclass called **`SharedMapCached`**:\n\n```\nfinal cachedWorkerMap = SharedMapCached<String, String>.fromSharedReference(\n  cacheReference,\n  // Cache items locally in this isolate for high-throughput reads\n  timeout: const Duration(seconds: 30),\n);\n```\n\nWhen you query an existing key with `SharedMapCached`, it caches the value locally in the worker's isolate heap. If subsequent reads occur within the timeout window, they resolve instantly without cross-isolate latency.\n\n`SharedStore`\nIn complex applications, you rarely have just one cache. You might have:\n\n`SharedMap<String, Uint8List>`)` SharedMap<String, UserProfile>`)` SharedMap<String, int>`)\nInstead of passing dozens of individual references around, you pass a single **`SharedStoreReference`**:\n\n```\n// Main thread:\nfinal store = SharedStore('global_store');\nawait store.getSharedMap<String, String>('tokens');\nawait store.getSharedMap<String, int>('rate_limits');\n\nfinal storeRef = store.sharedReference();\n\n// Inside any background isolate:\nawait Isolate.run(() async {\n  final workerStore = SharedStore.fromSharedReference(storeRef);\n\n  // Dynamically resolve any map registered under this store:\n  final tokens = await workerStore.getSharedMap<String, String>('tokens');\n  final rateLimits = await workerStore.getSharedMap<String, int>('rate_limits');\n\n  // ...\n});\n```\n\nAs with any tool, understanding the architectural sweet spot is key.\n\n`Map<K, V>` or reactive Signal is all you need—there's no reason to pay the asynchronous abstraction cost.\nDart's isolate architecture keeps our code safe from concurrency bugs, but you shouldn't have to write hundreds of lines of port plumbing just to share an in-memory cache across worker tasks.\n\nBy bringing in **[`shared_map`](https://pub.dev/packages/shared_map)**:\n\nAdd `shared_map: ^1.1.9` to your `pubspec.yaml` and stop reinventing isolate messaging from scratch.\n\nHow do you currently coordinate data between background isolates in your Flutter apps? Have you been writing custom ports, or relying on `Isolate.run` return values? Let me know in the comments below!\n\n*Randal L. Schwartz is a Google Developer Expert (GDE) for Dart & Flutter and veteran software architect.*", "url": "https://wpnews.pro/news/share-state-across-dart-isolates-without-losing-your-mind-enter-shared-map", "canonical_source": "https://dev.to/gde/share-state-across-dart-isolates-without-losing-your-mind-enter-sharedmap-221b", "published_at": "2026-09-20 20:43:19+00:00", "updated_at": "2026-09-20 20:54:11.445404+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Dart", "Flutter", "shared_map", "Graciliano M. Passos", "Isolate.run", "SharedMap", "SharedStore", "pub.dev"], "alternates": {"html": "https://wpnews.pro/news/share-state-across-dart-isolates-without-losing-your-mind-enter-shared-map", "markdown": "https://wpnews.pro/news/share-state-across-dart-isolates-without-losing-your-mind-enter-shared-map.md", "text": "https://wpnews.pro/news/share-state-across-dart-isolates-without-losing-your-mind-enter-shared-map.txt", "jsonld": "https://wpnews.pro/news/share-state-across-dart-isolates-without-losing-your-mind-enter-shared-map.jsonld"}}