DEV Community

Hrishikesh Pathak
Hrishikesh Pathak

Posted on • Updated on

Flutter counter app, but using isolates

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.

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),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

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.

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

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

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.

static void remoteIsolate(SendPort sendPort) {
  sendPort.send("Hi i am from remote Isolate");
}
Enter fullscreen mode Exit fullscreen mode

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

_receivePort.listen(
  (message) {
    print(message);
  },
);
Enter fullscreen mode Exit fullscreen mode

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.

static void remoteIsolate(SendPort sendPort) {
  ReceivePort _isolateReceivePort = ReceivePort();
  sendPort.send(_isolateReceivePort.sendPort);
}
Enter fullscreen mode Exit fullscreen mode

Now we have to identify the sendPort from the rest of the message in our main-thread.

_receivePort.listen(
  (message) {
    if (message is SendPort) {
      message.send("SendPort from Isolate received");
    }
  },
);
Enter fullscreen mode Exit fullscreen mode

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.

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),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

If you have like this article, leave a comment. If you want to use a file picker in flutter, you can read this article. Follow me on Twitter to discuss cool new tech.

Discussion (0)