Exploring Flutter Magic: How Isolates and Compute Work Behind the Scenes!
Table of contents
As we have learned about async/await, futures, and streams in our previous articles, you might remember that futures are things that happen sometime in the future. Let’s revisit this with an example:
Imagine you ask your friend to bring water while you continue doing your task. When your friend brings the water, you stop briefly to drink it before resuming your work. You didn't stop your task entirely; you just paused briefly.
Even though this feels like things are happening in the background, they are actually occurring in the same space (in programming terms, the main thread). While it seems like multitasking, futures only operate on the main thread.
The Need for True Background Processing
If you give your friend a bigger task—like building a mini cart for your house—this would take more time. During this time, even though the task is being done in the "background," it would affect your ability to do other work.
In programming, this is similar to how heavy tasks can block the UI thread. Your UI runs at 60 frames per second, and if a task causes delays, your app might drop to 30 frames or less, resulting in laggy performance.
To fix this, we need true background processing, which requires creating a separate thread. This is where isolates come in.
What Are Isolates?
Isolates are Flutter's way of achieving multithreading without the complexity of managing threads manually. Isolates help you offload heavy tasks to run in the background, without blocking the main thread or affecting the UI.
Let’s Understand Isolates with an Analogy
Imagine you need to build a mini cart for a project. Doing this in your house would create noise and distractions, making it hard for you to study for an exam.
Now, suppose you have a magical gadget that can create a portal to another house. Here’s what you do:
Create the portal using the gadget.
Send your friend through the portal, along with the tools and instructions to build the mini cart.
Your friend works in their house (background thread).
Once done, your friend uses the gadget to send the completed mini cart back to you through the portal.
This way, your house (main thread) remains unaffected while the work is completed in the background.
How Isolates Work in Flutter
Breaking it down:
Creating the portal: Use
Isolate.spawn
.Sending data: Provide the task (function) and a way to communicate (ports).
Receiving results: Use a
ReceivePort
to listen for the completed task.
Example: Implementing Isolates in Flutter
// Task: Creating a mini cart in the background
Future<void> createMiniCart() async {
// Step 1: Create a ReceivePort (portal)
final receivePort = ReceivePort();
// Step 2: Spawn the isolate (send task and port)
await Isolate.spawn(miniCartTask, receivePort.sendPort);
// Step 3: Listen to results from the isolate
receivePort.listen((data) {
print('Mini cart received: $data');
receivePort.close();
});
}
// Function to run in the isolate
void miniCartTask(SendPort sendPort) {
// Perform the heavy task here
final miniCart = "Mini cart created!";
// Send the result back through the portal
sendPort.send(miniCart);
}
This is how isolates work: you create a portal, send data through it, and listen for results.
Simplifying Isolates with Compute
While isolates provide flexibility, they can be overwhelming to set up for simple tasks. Flutter’s compute
function is an abstraction over isolates, making it easier to handle background tasks.
Compute Function Analogy
Instead of creating a portal and managing everything yourself, imagine you have a magical genie. You simply tell the genie:
What task needs to be done (function).
The tools or ingredients required (parameters).
The genie does the task in the background and gives you the result without requiring you to manage any complexities.
Example: Using Compute in Flutter
import 'dart:async';
import 'package:flutter/foundation.dart';
// Task to be performed in the background
String miniCartTask(String tools) {
return "Mini cart created using $tools!";
}
// Using compute to simplify
Future<void> createMiniCart() async {
final result = await compute(miniCartTask, "wood and nails");
print(result); // Output: Mini cart created using wood and nails!
}
With compute
, you simply pass the task (function) and the required parameters. Everything happens in the background, and you get the result without worrying about creating or managing threads.
When to Use Isolates vs Compute
Use
compute
for simple tasks that take an input and return an output.Use isolates for more complex scenarios where you need granular control over background processing, such as maintaining long-running tasks or managing multiple streams of data.
Conclusion
Next time you encounter a heavy task in your app:
For simple, long-running functions, use
compute
.For complex tasks requiring more control, use isolates with
Isolate.spawn
.
This ensures your app stays responsive, with a smooth UI, while handling background tasks efficiently.