Dart was originally a single-threaded programming language. However, many have heard of Isolate. They are often asked about in Flutter developer interviews. For this reason alone, it is worth studying this topic in a little more detail.
Today we will figure out what they are and how they can help us create responsive interfaces that work with 60 fps.

What is Isolate?

Any Dart code is executed in Isolate. Isolate is an isolated environment, inside which there is memory allocated to it and its EventLoop. Each Isolate is single-threaded and can only manage the memory and EventLoop allocated for it. You cannot control the memory of another Isolate.

How do different Isolates communicate?

Communication between Isolates occurs through the transfer of messages between ports.

How do I start using Isolates in my applications?

So, to use this powerful tool, we need to include the Dart library: isolates
If we look at the documentation, we will see a rather small API that gives us the following main classes to work with:

When should I use Isolate?

So there are two surefire cases when you should resort to Isolate.
  1. Suppose you need to complete a sizeable one-time task. For example, de- / serialization of data, receiving a large response from the server, some complex mathematical calculations (Fibonacci number), etc.
  2. You can isolate someone else's poorly optimized code so that you do not rewrite it. Thus, you will protect yourself from a drop in your application's performance due to the fault of any massive libraries or non-optimal code of another person.

Basic example

import 'dart:isolate';


import 'package:flutter/material.dart';


void main() {

 runApp(MyApp());

}


class MyApp extends StatelessWidget {

 @override

 Widget build(BuildContext context) {

   return MaterialApp(

     home: MyHomePage(),

   );

 }

}


class MyHomePage extends StatefulWidget {

 @override

 _MyHomePageState createState() => _MyHomePageState();

}


class _MyHomePageState extends State<MyHomePage> {

 Isolate isolate;

 ReceivePort receivePort;


 @override

 void initState() {

   super.initState();

   spawnNewIsolate();

 }


 void spawnNewIsolate() async {

   receivePort = ReceivePort();

   try {

     isolate = await Isolate.spawn(sayHello, receivePort.sendPort);

     print("Isolate: $isolate");

     receivePort.listen((dynamic message) {

       print('New message from Isolate: $message');

     });

   } catch (e) {

     print("Error: $e");

   }

 }


 //spawn accepts only static methods or top-level functions

 static void sayHello(SendPort sendPort) {

   sendPort.send("Hello from Isolate");

 }


 @override

 void dispose() {

   super.dispose();

   receivePort.close();

   isolate.kill();

 }


 @override

 Widget build(BuildContext context) {

   return Scaffold(

     appBar: AppBar(

       title: Text("Isolate Demo"),

     ),

     body: Center(),

   );

 }

}

Compute

So we figured out how to spawn a new isolate. Now let's look at more convenient ways of working with isolates, and the first of them will be the built-in wrapper over Isolate Api in Flutter - Compute.
Compute slightly expands the capabilities of working with isolates in Flutter and takes on the following responsibilities:
Thus, all you need to use the compute function is to pass the first argument to the function that you want to execute in the isolate, and the second argument to pass those arguments that should go to the executable function.

Basic example

import 'package:flutter/foundation.dart';

import 'package:flutter/material.dart';


void main() {

 runApp(MyApp());

}


class MyApp extends StatelessWidget {

 @override

 Widget build(BuildContext context) {

   return MaterialApp(

     home: MyHomePage(),

   );

 }

}


class MyHomePage extends StatefulWidget {

 @override

 _MyHomePageState createState() => _MyHomePageState();

}


class _MyHomePageState extends State<MyHomePage> {

 @override

 void initState() {

   super.initState();

   createComputeFunction();

 }


 void createComputeFunction() async {

   String answer;

   answer = await compute(sayHelloFromCompute, 'Hello');

   print("Answer from compute: $answer");

 }


 static String sayHelloFromCompute(String string) {

   return string;

 }


 @override

 Widget build(BuildContext context) {

   return Scaffold(

     appBar: AppBar(

       title: Text("Isolate Demo"),

     ),

     body: Center(),

   );

 }

}

Third-party solutions

We have already mastered the skills of working with isolates and know how to use the compute function.
Today there are two third-party libraries for working with isolates.
You can already familiarize yourself with them in more detail and see examples of work in the documentation.

Representative example

However, we have not yet looked at the most illustrative example showing the importance of performing complex operations in separate isolates.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
 runApp(MyApp());
}
class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     home: MyHomePage(),
   );
 }
}
class MyHomePage extends StatefulWidget {
 @override
 _MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
 AnimationController rotationController;
 Animation animation;
 List<int> results = [];
 @override
 void initState() {
   super.initState();
   rotationController = AnimationController(
     duration: const Duration(seconds: 5),
     vsync: this,
   )..addListener(() => setState(() {}));
   animation = Tween(begin: 0.0, end: 1.0).animate(rotationController);
   rotationController.forward(from: 0.0);
   //loop the animation for clarity
   animation.addStatusListener((status) {
     if (status == AnimationStatus.completed) {
       rotationController.repeat();
     }
   });
 }
 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: Text("Isolate Demo"),
     ),
     body: Column(
       children: [
         SizedBox(
           height: 100,
         ),
         Center(
           child: RotationTransition(
             turns: animation,
             child: Container(
               width: 200,
               height: 200,
               color: Colors.orange,
             ),
           ),
         ),
         SizedBox(
           height: 100,
         ),
         RaisedButton(
           onPressed: () {
             setState(() {
               final result = fib(40);
               print(result);
               results.add(result);
             });
           },
           child: Text("fib(40) in main thread"),
         ),
         RaisedButton(
           onPressed: () async {
             final result = await compute(fib, 40);
             setState(() {
               results.add(result);
             });
           },
           child: Text("fib(40) in isolate"),
         ),
         Text("Number of results: ${results.length.toString()}")
       ],
     ),
   );
 }
}
int fib(int n) {
 if (n < 2) {
   return n;
 }
 return fib(n - 2) + fib(n - 1);
}

Additional resources

About the Author

Sergey Vedmediev
Flutter Developer at GBKSOFT
Having started my career as a front-end developer; at some point, I got to start learning Flutter. Despite my moderate experience with it, I was able to switch to building beautiful cross-platform applications quickly. I think this technology, and in particular, the Dart language is much more promising and exciting.

FAQ:

What is Isolate?
Isolate - an isolated environment within which Dart code is executed with its own allocated memory (heap) and its own (separate) EventLoop.
What are they needed for?
They are needed to perform complex operations in separate threads and not affect performance in the main thread (main/ui-thread).
How do I use them?
The dart:isolate library in Dart or the compute function in Flutter or third-party Flutter libraries.