Flutter counter app, but using isolates
Tags: flutter, dart, android, ios
Published: 2021-08-02 17:28:00

Asynchronous and parallelism are two different things. Let's take an example. Our brain can do only one thing at a time. It can't handle two different things at the same instant. If you want to talk about multitasking here, it is actually asynchronous in nature. In multitasking, if a job is taking more time, then we move to another work and when the first job is done, we come back to process the output from the first job.

It is simple, right. Till now, it looks like asynchronous processing is the bast way to boost our productivity, right ? Yeah, but not 100% right.

Nowadays, multicore CPU are default in our machines, it can do a lot of different things parallelly. So to use this architecture, we have to make our software in a way to use multicore or multiple thread. 

But the thing is that flutter use only one thread, and it does all of its work on a single thread. In this thread, it has to pump 60 Frames Per Second to give a fluid experience to the end user. If we do some heavy work or long-running task in this thread, Then flutter thread will be busy in the heavy work and as a result frame rates of our app will drop and the app looks sluggish, or stutters.

So what to do ? How to do heavy lifting work in out flutter app ? There are many ways to this in flutter. Like writing asynchronous code or using compute function.

But the best way I have found that to use an isolated. If you haven't heard this name before, don't worry. I am here to help.

## What is an Isolate in flutter

Isolate is a container which  is completely separate from flutter thread and don't share any memory with the app. Isolate means a CPU thread which run in its own sandbox. We can create an isolate from a flutter app and communicate with an isolate by passing messages to and fro with the isolate.

So enough of introduction, let's see how to create and use isolates in flutter.

## What we are going to make

Here we will use the basic flutter counter app and implement  isolate to generate random numbers. Just like the vanilla flutter counter app, but with isolate. Excited ? Let's dive in.

## How to create an Isolate in flutter

To create Isolate in flutter, we need to import `dart:isolate` package. We can start an isolate from our main thread with `Isolate.spawn` method. This is the entry point to our isolate, and we have to pass a `ReceivePort` in `Isolate.spawn` method to receive message from the isolate and listen the messages as a stream in the main thread.

Let us create a stateful widget named `MyApp` in flutter.

```dart
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Counter"),
      ),
      body: Center(child: Text("1")),
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        child: Icon(Icons.add),
      ),
    );
  }
}

```
Now, I define the `Isolate _isolate` variable in the top of the stateful widget. As we need to listen, the incoming values from the isolate inside the `build` function as a stream.

Then in the `initState` function in our widget, we start our isolate and pass a `ReceivePort`.

```dart
@override
void initState() {
  spawnIsolate();
  super.initState();
}

Future spawnIsolate() async {
  ReceivePort _receivePort = ReceivePort();
  _isolate = await Isolate.spawn(remoteIsolate, _receivePort.sendPort,
      debugName: "remoteIsolate");
}
```
In the above code, `remoteIsolate` is the entry point of the isolate. 

We have defined a `ReceivePort _receivePort` and send the `_receivePort.sendPort` to the created isolate. This `sendPort` is the address of the main thread of the application. We have given our isolate a name called `remoteIsolate`. This will be helpful to debug our code later.

This `remoteIsolate` can use `sendPort` to send data to the main thread.

Now inside the `remoteIsolate` function, which is the entry point of our isolate, we define the working or functionality of our isolate.

```dart
static void remoteIsolate(SendPort sendPort) {
  sendPort.send("Hi i am from remote Isolate");
}
```

We can listen to this message in our main thread using the `ReceivePort`.

```dart
_receivePort.listen(
  (message) {
    print(message);
  },
);
```
Till now, I think you have understood the basic working of an `Isolate` and how to use `ReceivePort` and `SendPort` to communicate between isolate and the main thread.

## Bidirectional communication between isolate and the main-thread

Bidirectional communication between isolate and the main thread is very useful if we have to use the same isolate repeatedly.

To send a message from the main-thread to our isolate, we need the `sendPort` of the isolate. To get that `sendPort`, we the isolate is initialize for the first time, we create a `ReceivePort` inside the isolate and send the `sendPort` to the main thread as a message. 

Then we can use this `sendPort` of the isolate to send a message from our main thread.

Let's modify our `remoteIsolate` function from above.

```dart
static void remoteIsolate(SendPort sendPort) {
  ReceivePort _isolateReceivePort = ReceivePort();
  sendPort.send(_isolateReceivePort.sendPort);
}
```
Now we have to identify the `sendPort` from the rest of the message in our main-thread. 

```dart
_receivePort.listen(
  (message) {
    if (message is SendPort) {
      message.send("SendPort from Isolate received");
    }
  },
);
```
Now we can use this `sendPort` to send any message to the isolate. So we can now use bidirectional messaging in flutter isolate and can reuse the isolate over and over again.

## Flutter counter app using Isolate

As I have promised before in this article, This is the complete source-code of isolate implementation in the flutter counter app.

```dart
import 'dart:isolate';
import 'dart:math';
import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: MyApp(),
    ),
  );
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  ReceivePort? _receivePort;
  Isolate? _isolate;
  SendPort? _isolateSendPort;

  static void remoteIsolate(SendPort sendPort) {
    ReceivePort _isolateReceivePort = ReceivePort();
    sendPort.send(_isolateReceivePort.sendPort);
    _isolateReceivePort.listen((message) {
      if (message == "+") {
        sendPort.send(Random().nextInt(100));
      }
    });
  }

  Future spawnIsolate() async {
    _receivePort = ReceivePort();
    _isolate = await Isolate.spawn(remoteIsolate, _receivePort!.sendPort,
        debugName: "remoteIsolate");
  }

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

  @override
  void dispose() {
    if (_isolate != null) {
      _isolate!.kill();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Counter"),
      ),
      body: Center(
        child: StreamBuilder(
          stream: _receivePort,
          initialData: "NoData",
          builder: (BuildContext context, AsyncSnapshot snapshot) {
            if (snapshot.data is SendPort) {
              _isolateSendPort = snapshot.data;
            }
            return Container(
              child: Text(
                snapshot.data.toString(),
              ),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          _isolateSendPort!.send("+");
        },
        child: Icon(Icons.add),
      ),
    );
  }
}
```
If you have like this article, leave a comment. If you want to use a file picker in flutter, you can read [this article](https://www.devquark.com/blog/file-picker-flutter). Follow me on [Twitter](https://twitter.com/hrishikshpathak) to discuss cool new tech.