DEV Community

Cover image for Use Isolates to prevent UI Jank | Flutter multiprocessing
Ramiro - Ramgen
Ramiro - Ramgen

Posted on • Updated on

Use Isolates to prevent UI Jank | Flutter multiprocessing

As you might know flutter runs asynchronously on a single thread often called the main thread and also where the UI is running, now when you need to run heavy computational function you find that you app freezes and janks because is busy with that function.

Here is where Isolates come into action, this is a way to spawn threads in dart/flutter and prevent the main thread to halt.

Isolates are a little finicky but they are useful when the situation comes and this was the perfect situation, I needed to load an image and send it to an API for processing, this was janking the main thread but it can be done on an isolate and prevent that.

Let's see one of the easiest ways of using isolates with the compute function

Function to run

First we need a function to execute, this function loads the image and sends it with http to the API.

Future<Uint8List?> requestImg(RequestInputs reqInputs) async {
  File file = File(reqInputs.filePath!);

  final http.MultipartRequest request = http.MultipartRequest(
    'POST',
    Uri.parse('server:uri'),
  );
  final Map<String, String> headers = <String, String>{'Content-type': 'multipart/form-data'};

  final img_proc.Image tmp = img_proc.decodeImage(await file.readAsBytes())!;
  final List<int> tosend = img_proc.encodeJpg(tmp);

  request.files.add(http.MultipartFile.fromBytes(
    'file',
    tosend,
    filename: 'aism_source',
    contentType: MediaType('image', 'jpeg'),
  ));

  request.headers.addAll(headers);
  request.fields.addAll(reqInputs.settings);
  final http.StreamedResponse res = await request.send();
  debugPrint('Request settings sent');

  Uint8List responseImage = await res.stream.toBytes();
  return responseImage;
}
Enter fullscreen mode Exit fullscreen mode

Now my function needed more than one argument but the compute function only accepts functions with one, to solve this I use a custom class to pass all of the data.

class RequestInputs {
  final String? filePath;
  final Map<String, String> settings;

  RequestInputs(this.filePath, this.externalPath, this.settings);
}
Enter fullscreen mode Exit fullscreen mode

Compute function

Great we have the function and the custom class let's see how to use compute to run it on an isolate.

// #1
compute<StickerRequestInputs, Uint8List?>(
  requestImg,
  StickerRequestInputs(
    imgPath,
    storage,
    settings,
  ),
// #2
).then((value) => setState(() {
      loading = false;
      if (value != null) {
        imgBytes = value;
        saveImage(value);
      } else {
        debugPrint('Error!');
      }
    }));
Enter fullscreen mode Exit fullscreen mode

We first need to declare the function input and output types in the compute statement #1 compute<Q,R> Q is the input type and R is the return type.

Then we pass the function and the input to run, in this case we instantiate the custom class that we declared.

The compute is a future of the return type R #2, we can use either async or .then to do something after it finishes, in this case we update the state with the new data or we print an error if it fails.

Watch the video for a walkthrough of all the code, subscribe to see the progress of the app :D

Alright and that's it, I will continue working with isolates and see what we can do with them, but as i said the are a little tricky to work with regardless they are a good option when you need them.

Top comments (0)