Flutter
TensorFlow
Object Detection
Isolates
Machine Learning

Flutter how to perform object-detection in an isolate using TensorFlow?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Running object detection in a Flutter isolate is a good way to keep the UI responsive when inference and image preprocessing are expensive. The main rule is that isolates do not share memory, so you should send plain transferable data such as image bytes or file paths, create the TensorFlow Lite interpreter inside the worker isolate, and send lightweight detection results back to the UI isolate.

Why Use An Isolate At All

Camera frames, image resizing, and TensorFlow Lite inference can easily block the main isolate if everything runs on the UI thread. That shows up as dropped frames, delayed gestures, and stuttering previews.

An isolate helps because:

  • preprocessing can run off the UI isolate
  • inference can run off the UI isolate
  • only the final result comes back to the screen layer

This does not make the model itself magically faster, but it prevents heavy work from freezing the interface.

Keep The Data Transfer Simple

Do not try to send widget references, controller objects, or complex plugin state across isolates. Send only serializable or transferable data such as:

  • a model path
  • image bytes
  • width and height
  • plain maps or lists of results

A simple message type:

dart
1class DetectionRequest {
2  final Uint8List imageBytes;
3  final int width;
4  final int height;
5
6  DetectionRequest(this.imageBytes, this.width, this.height);
7}

That kind of object is easy to reason about and does not depend on UI-layer objects.

Spawn A Worker Isolate

A basic isolate setup looks like this:

dart
1import 'dart:isolate';
2import 'dart:typed_data';
3
4Future<SendPort> startWorker() async {
5  final receivePort = ReceivePort();
6  await Isolate.spawn(detectionWorker, receivePort.sendPort);
7  return await receivePort.first as SendPort;
8}
9
10void detectionWorker(SendPort mainSendPort) async {
11  final workerReceivePort = ReceivePort();
12  mainSendPort.send(workerReceivePort.sendPort);
13
14  await for (final message in workerReceivePort) {
15    // handle requests here
16  }
17}

This creates a long-lived worker isolate. That is usually better than spawning a fresh isolate for every camera frame.

Create The Interpreter Inside The Worker

The interpreter should be initialized inside the isolate that uses it.

dart
1import 'dart:isolate';
2import 'dart:typed_data';
3import 'package:tflite_flutter/tflite_flutter.dart';
4
5void detectionWorker(SendPort mainSendPort) async {
6  final workerReceivePort = ReceivePort();
7  mainSendPort.send(workerReceivePort.sendPort);
8
9  final interpreter = await Interpreter.fromAsset('detect.tflite');
10
11  await for (final message in workerReceivePort) {
12    final request = message as DetectionRequest;
13
14    final input = preprocessImage(request.imageBytes, request.width, request.height);
15    final output = List.generate(1, (_) => List.filled(10, 0.0));
16
17    interpreter.run(input, output);
18    mainSendPort.send(output);
19  }
20}

This matters because many problems come from trying to share interpreter state or plugin objects across isolates. Keep the worker self-contained.

Preprocess Before Inference

Object detection models usually expect resized and normalized tensors, not raw camera bytes.

A placeholder preprocessing function might look like this:

dart
1List<List<List<List<double>>>> preprocessImage(Uint8List bytes, int width, int height) {
2  return List.generate(
3    1,
4    (_) => List.generate(
5      300,
6      (_) => List.generate(
7        300,
8        (_) => [0.0, 0.0, 0.0],
9      ),
10    ),
11  );
12}

This example only shows structure, but in a real app you would:

  • decode the image bytes
  • resize to the model input size
  • convert color channels as needed
  • normalize pixel values

The more camera frames you process, the more valuable it becomes to keep this work in the isolate too.

Return Lightweight Detection Results

Do not send giant intermediate tensors back to the UI isolate if the screen only needs boxes and labels.

A simple result type:

dart
1class DetectionResult {
2  final String label;
3  final double score;
4  final double left;
5  final double top;
6  final double right;
7  final double bottom;
8
9  DetectionResult(this.label, this.score, this.left, this.top, this.right, this.bottom);
10}

Then convert raw model output into a list of DetectionResult objects inside the worker and send only that list back.

This keeps the message traffic smaller and the UI logic simpler.

For Repeated Camera Frames, Reuse The Worker

If the app performs live object detection, do not repeatedly spawn and tear down isolates for each frame. Start one worker, initialize the interpreter once, and send requests through a port.

That avoids repeated model loading, which can be more expensive than the inference itself.

It also makes backpressure easier to manage. If frames arrive too quickly, you can decide to:

  • drop old frames
  • keep only the most recent frame
  • throttle detection frequency

That is usually necessary for a stable real-time UX.

Common Pitfalls

The biggest mistake is trying to share interpreter state or UI objects between isolates. Isolates do not share memory, so each worker should own its own runtime objects.

Another mistake is sending heavy image objects or plugin-specific state across isolate boundaries instead of plain bytes and metadata.

People also forget that preprocessing is often as expensive as inference. Moving only the model call to an isolate may still leave the UI thread overloaded.

Finally, spawning a fresh isolate per frame is usually too expensive for live camera scenarios. Reuse one worker isolate and one interpreter.

Summary

  • Use an isolate to keep TensorFlow Lite preprocessing and inference off the Flutter UI isolate.
  • Send plain transferable data such as image bytes, dimensions, and simple result objects.
  • Create the interpreter inside the worker isolate instead of trying to share it.
  • Reuse a long-lived worker for repeated detection tasks such as camera frames.
  • Keep the response payload small by returning only detection boxes, labels, and scores.

Course illustration
Course illustration

All Rights Reserved.