Flutter Daily: Isolates for Background Processing

Isolates for Background Processing

What You’ll Learn

Today we’ll explore Dart isolates—Dart’s approach to true parallel execution. Unlike async/await which runs code concurrently on a single thread, isolates let you run computationally expensive tasks on separate threads without blocking your UI.

Why Isolates Matter

Flutter apps run on a single thread by default. Heavy operations like JSON parsing from large files, image processing, or complex calculations can freeze your UI. Isolates solve this by running code in complete isolation from the main thread.

Example: Background JSON Processing

Here’s a practical example of parsing large JSON data without blocking the UI:

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

// This function runs in a separate isolate
Future<List<User>> parseUsers(String jsonString) async {
  final parsed = json.decode(jsonString) as List;
  return parsed.map((json) => User.fromJson(json)).toList();
}

// In your widget or service class:
class UserService {
  Future<List<User>> loadUsersInBackground(String jsonString) async {
    // Spawn an isolate to parse JSON
    final result = await Isolate.run(() => parseUsers(jsonString));
    return result;
  }
}

// Using it in a widget:
class UserListScreen extends StatefulWidget {
  @override
  State<UserListScreen> createState() => _UserListScreenState();
}

class _UserListScreenState extends State<UserListScreen> {
  List<User> users = [];
  bool isLoading = true;

  @override
  void initState() {
    super.initState();
    _loadUsers();
  }

  Future<void> _loadUsers() async {
    final jsonString = await loadLargeJsonFile(); // Your loading logic
    
    // This runs on a separate thread - UI stays responsive!
    final parsedUsers = await UserService().loadUsersInBackground(jsonString);
    
    setState(() {
      users = parsedUsers;
      isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    if (isLoading) return CircularProgressIndicator();
    return ListView.builder(
      itemCount: users.length,
      itemBuilder: (context, index) => UserTile(users[index]),
    );
  }
}

How It Works

  1. Isolate.run() is the modern way (Flutter 3.7+) to spawn isolates for one-time computations
  2. The function you pass runs completely separately from your main thread
  3. Data is copied between isolates (not shared) for safety
  4. The result is automatically sent back when the computation completes

When to Use Isolates

Use isolates for:

Don’t use isolates for:

Try It Yourself

Create a simple app that compresses an image in the background:

Future<Uint8List> compressImage(Uint8List imageBytes) async {
  return await Isolate.run(() {
    // Simulate heavy processing
    final compressed = /* your image compression logic */;
    return compressed;
  });
}

Challenge: Add a progress indicator while the isolate is working, and display the compressed image when done. Notice how smooth your UI remains during compression!

Tip of the Day

For long-running background tasks that need bidirectional communication (like progress updates), use Isolate.spawn() with SendPort and ReceivePort instead of Isolate.run(). This gives you a persistent isolate that can communicate back and forth with your main thread—perfect for download managers or real-time data processing.