Skip to content

Manual Implementation

d-markey edited this page Jan 22, 2026 · 3 revisions

While squadron_builder is excellent for rapid development, understanding how Squadron works "under the hood" is valuable for advanced scenarios, debugging, or when you need behavior not yet supported by the generator (like passing workers to workers).

Examples in the Squadron repository itself use this manual approach.


Anatomy of a Squadron Service

A complete Squadron implementation consists of 5 main parts:

  1. Service Interface: The defining contract.
  2. Service Implementation: The actual business logic (runs on the worker thread).
  3. Service Client: The proxy (runs on the main thread) that sends commands to the worker.
  4. Worker Entry Points: The thread function that initializes the worker channel and service instance.
  5. Worker / WorkerPool: The class you instantiate to manage the thread.

1. The Service Interface

This is just a Dart class or abstract class. It defines the operations and Command IDs.

abstract class MyService {
  Future<int> add(int a, int b);
  
  // Command IDs are used to identify which method to call
  static const addCommand = 1;
}

2. The Service Implementation

This class runs inside the worker. It implements WorkerService and maps Command IDs to method calls.

class MyServiceImpl implements MyService, WorkerService {
  
  @override
  Future<int> add(int a, int b) async => a + b;

  // The Operations Map routes incoming requests (Command ID) to methods
  @override
  late final OperationsMap operations = OperationsMap({
    MyService.addCommand: (WorkerRequest r) => add(r.args[0], r.args[1]),
  });
}

3. The Service Client (The Proxy)

This class runs on the main thread. It implements WorkerService but forwards everything to the Worker via the Channel.

// 'WorkerClient' handles the low-level channel communication
class MyServiceClient extends WorkerClient implements MyService {
  MyServiceClient(Channel channel) : super(channel);

  @override
  final operations = WorkerService.noOperations; // Clients don' handle operations

  @override
  Future<int> add(int a, int b) => send(
    MyService.addCommand, 
    args: [a, b]
  );
}

Note: This WorkerClient is what allows you to treat a Remote Worker exactly like a Local Service.

4. The Worker Entry Point

This is the bridge between the OS thread and your code.

Native (VM) Entry Point:

void start(List args) => Squadron.run((startRequest) => MyServiceImpl(), args);

Web (JS/WASM) Entry Point:

void main() => Squadron.run((startRequest) => MyServiceImpl());

5. The Worker Class

Finally, the class the user interacts with. It typically extends Worker and implements MyService.

class MyWorker extends Worker implements MyService {
  MyWorker(entryPoint) : super(entryPoint);

  @override
  Future<int> add(int a, int b) => send(MyService.addCommand, args: [a, b]);
  
  // ...
}

Why do this manually?

  1. Custom Construction: Pass start arguments (like other Channels, database paths, or configuration maps) to your service implementation.
  2. Stateful Workers: Manage complex initialization logic before the service starts handling requests.
  3. Optimization: Fine-tune marshaling/unmarshaling without relying on generic code.

Clone this wiki locally