Flutter: ESC/POS Receipt Printing with USB (React JS design)

Flutter: ESC/POS Receipt Printing with USB (React JS design)

Table of contents

No heading

No headings in the article.

Hi Flutter Devs! In this blog, I am going to share the experience of ESC/POS printer integration into our flutter app. ESC/POS, short for Epson Standard Code for Printers and sometimes styled Escape/P, is a printer control language developed by Epson to control computer printers. It was mainly used in dot matrix printers and some inkjet printers, and is still widely used in many receipt thermal printers.

Recently, we have worked with ESC/POS printer in one of our projects to print the order receipt of a restaurant. We have developed our app with flutter. It is mainly a webview app. React JS is used to develop the web front end. We have to communicate with Javascript through flutter webview to design the receipt. I will describe the process step by step.

Plugin

There are several libraries for flutter which are used to connect and print receipt through ESC command. We have chosen flutter_usb_printer for this purpose. The plugin performance is good and has less bugs.

USB Devices

We have implemented the receipt printing feature for USB connection only.

Future _getDeviceList(BuildContext context) async {
    List<Map<String, dynamic>> results = [];
    Timer.periodic(Duration(seconds: 5), (timer) async{
      results = await FlutterUsbPrinter.getUSBDeviceList();
      print(" length: ${results.length}");
      for (int i=0; i<results.length; i++) {
          Map<String, dynamic> device = results[i];
          await UsbUtils()
                  .connect(
                  int.parse(device["vendorId"]), int.parse(device["productId"]))
                  .then((value) {
              });
        }
    });
  }

Here, we get the connected usb devices in the list. We are continuously listening the device connection at 5 seconds interval to detect if any new device is connected or any device is removed. Then we have checked the device vendorID and productID was previously connected with our app or not by reading from our local database. If it matches with our saved data, we connect the device otherwise we show a popup to the user with showing the list of found devices along with a connect button.

Screenshot_20220725-122747_klikit Manager (1).jpg

Connection

After showing the list of usb connected printers we will wait for user action to establish connection with our app.

static FlutterUsbPrinter flutterUsbPrinter = FlutterUsbPrinter();
  Future<bool> connect(int vendorId, int productId) async {
    bool returned = false;
    try {
      returned = await flutterUsbPrinter.connect(vendorId, productId).catchError((e) async{
        print(e);
        await FileManager().writeFile(Constants.printErrorLog, "printer connection error: $e");
      });
      Constants.printerConnected = returned;
    } on PlatformException {
      //response = 'Failed to get platform version.';
    }
    if(returned) {
      Map<String, dynamic> printerInfo = {
        "vendorId" : vendorId,
        "productId" : productId,
      };
      await LocalDb().storeUsbPrinter(printerInfo);
    }
    return returned;
  }

Then we have connected the printer according to user’s choice and saved locally the printer information (vendorID, productID) with shared_preferences for future use. We have written a file if printer connection failed with an exception to trace the error.

Receipt Design and Javascript Bridging

There are two ways in our hands to design the receipt.

  1. Designing the app with the plugin.

  2. Designing the receipt from the web front end and get the byte array of the receipt command.

We have chosen the 2nd option for our business logic and keeping more control in our hands. So we have maintained communication with Javascript channel to get the byte array, process it and send it for printing.

WebView(
   initialUrl: Constants.webUrl,
   javascriptMode: JavascriptMode.unrestricted,
   initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
   javascriptChannels: Set.from([
      JavascriptChannel(
         name: 'orderPrint',
         onMessageReceived: (JavascriptMessage message) {
            print(message.message);
            List<int> list = message.message.replaceAll('[', ',').replaceAll(']', ',')
                             .split(',').map<int>((e) {return int.tryParse(e);}).toList();
                             list.removeLast();
                             list.removeAt(0);
                             List<int> bytes = list;
            if(Constants.printerConnected) {
               UsbUtils().usbPrint(bytes);
            } else {
               ScaffoldMessenger.of(context).showSnackBar(
               SnackBar(content: Text("Printer not connected!"),backgroundColor: Colors.red,));
            }
      }),
   ]),
   onPageFinished: (finish) {
      setState(() {
         isLoading = false;
      });
   },
   onWebViewCreated: (WebViewController webViewController) {
      _controller.complete(webViewController);
   },
)

Print Receipt

We receive the byte array as a string from the front end through the javascript channel. Then we convert it to array of integers and these are passed to the library command for printing. The array of integers need to be converted into Uint8List. Finally it is sent the the plugin’s write function and the plugin processes the print command to the printer.

Output

Finally, we can print the receipt like this.

IMG_20220725_172224.jpg

For the React JS design part you may follow this article.