# A receipt should be one print job

> How I handle receipt ordering, printer readiness, and failed submissions in my Flutter printer SDK.

Author: Nidal Bakir

Published: 2026-10-16T11:56:21.312Z

Canonical URL: https://portfolio.nidal-bakir.workers.dev/blog/printing-a-receipt-in-flutter/

I work on printer integration as part of IbtiPark, and I maintain a Flutter plugin for the built in printer on ZCS Z92 and Z91 terminals. There is a detail in that work that is easy to miss: a receipt needs to stay together from the first line to the final paper feed.

Text, images and QR codes all have their own printing methods. That makes the Dart API straightforward to call. It also means the app can accidentally treat one receipt as several unrelated jobs.

## Awaiting each call only solves part of it

Say a receipt contains a heading, some ticket details and a QR code. One part of the app sends those commands in order, awaiting every call. Meanwhile, another part of the app starts printing something else.

Each caller has kept its own order. The printer can still receive a command from the second caller between two commands from the first.

In the ZCS SDK, the printer and its append buffer are shared. I keep access to them on a single background queue in the Android bridge. That protects the shared resource and keeps the work off the Android UI thread. But if every line enters the queue separately, there is still room for another receipt to get between them.

The operation I actually want to queue is the whole receipt.

## Build the receipt before sending it

The plugin exposes `printBatch` for this. The app builds an ordered list of commands, then sends the list once. A small parking ticket could look like this:

```dart
import 'package:z92_pos_printer_flutter_sdk/z92_pos_printer_flutter_sdk.dart';

Future<bool> printTicket(String ticketId) async {
  final result = await PrinterPosSystem().printBatch(
    commands: [
      PrintCommand.text(
        text: 'PARKING TICKET',
        alignment: AlignmentPrint.center,
        isBold: true,
      ),
      PrintCommand.text(text: ticketId),
      PrintCommand.qrCode(text: ticketId, width: 200, height: 200),
      PrintCommand.space(space: 3),
    ],
  );
  return result == true;
}
```

This shows the submission path. The calling app still needs to handle platform exceptions and decide what feedback to show.

On the native side, the batch takes one place in the queue. It waits for readiness, appends the commands in order and calls the SDK's `setPrintStart()` once. Another queued job cannot insert its own commands halfway through that batch. The [Android bridge](https://github.com/Nidal-Bakir/z92-pos-printer-flutter-sdk/blob/6c50364625900665b768e2c3c1abac3743913c71/android/src/main/java/com/z92posprinter/Z92PosPrinter.java) contains that sequence.

Images need some preparation too. For a receipt with a logo and Arabic fields rendered as images, I want the PNG bytes ready before submitting the batch. Loading or rendering the next image should not be part of an already submitted receipt.

## A failed submission needs care

Waiting for a busy printer and repeating a failed receipt are different decisions.

The plugin waits for up to ten seconds while the SDK reports that the printer is already printing. Other error states, such as missing paper, fail immediately. That wait runs on the background queue.

Once a batch has been submitted, a failure does not tell me that nothing reached the paper. Some of the receipt may already exist. Automatically sending the whole thing again can produce a duplicate or a second partial receipt, so the plugin does not do that.

The boolean result also has a limit. `true` means the SDK reported success. It does not prove that the paper has finished moving or that the printed QR code can be scanned.

The SDK append buffer is cleared on every exit, including an append failure. Otherwise, leftover content could become part of the next receipt.

## The paper still needs checking

The native tests cover command order, a single submission, invalid arguments and buffer cleanup. Those checks are useful because they catch mistakes without needing a terminal connected for every run.

They cannot check the contrast of an image, the wrapping of a long value or a QR code printed on the actual paper. I keep those in the device acceptance checks. Before a release, the full receipt needs to be printed and scanned on the terminal it will run on.

The [SDK source and example app](https://github.com/Nidal-Bakir/z92-pos-printer-flutter-sdk) are public if you want to follow the complete path from a Dart command to the printer.
