Today we are going to learn how to print via the thermal Bluetooth printer in Flutter. To accomplish this, we have to connect to a printer via Bluetooth and send printable data like text, image, or QR code. We will use these plugins:
flutter_blue: ^0.8.0
esc_pos_utils: ^1.1.0
We'll use this plugin to connect to the Bluetooth device and to send the data. It is not only for thermal printers but also can be used for any Bluetooth device. I chose this plugin because it supports iOS, Android, and macOS.
https://pub.dev/packages/flutter_blue
This plugin will help to generate ESC/POS commands to send it via flutter_blue. It supports images, barcodes, text styling, tables, paper cut, paper feed, and much more.
https://pub.dev/packages/esc_pos_utils
We may need permission setups, so go to the flutter_blue page and make setup according to instructions.
We can scan and get available devices like this:
FlutterBlue flutterBlue = FlutterBlue.instance;
// Start scanning
flutterBlue.startScan(timeout: Duration(seconds: 4));
// Listen to scan results
var subscription = flutterBlue.scanResults.listen((results) {
// do something with scan results
for (ScanResult r in results) {
print('${r.device.name} found! rssi: ${r.rssi}');
}
});
// Stop scanning
flutterBlue.stopScan();
But let's create a widget where
import 'package:blue_thermal_printing/blue_print.dart';
import 'package:esc_pos_utils/esc_pos_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_blue/flutter_blue.dart';
FlutterBlue flutterBlue = FlutterBlue.instance;
class PrintingWidget extends StatefulWidget {
const PrintingWidget({Key? key}) : super(key: key);
@override
_PrintingWidgetState createState() => _PrintingWidgetState();
}
class _PrintingWidgetState extends State<PrintingWidget> {
List<ScanResult>? scanResult;
@override
void initState() {
super.initState();
findDevices();
}
void findDevices() {
flutterBlue.startScan(timeout: const Duration(seconds: 4));
flutterBlue.scanResults.listen((results) {
setState(() {
scanResult = results;
});
});
flutterBlue.stopScan();
}
void printWithDevice(BluetoothDevice device) async {
await device.connect();
final gen = Generator(PaperSize.mm58, await CapabilityProfile.load());
final printer = BluePrint();
printer.add(gen.qrcode('https://altospos.com'));
printer.add(gen.text('Hello'));
printer.add(gen.text('World', styles: const PosStyles(bold: true)));
printer.add(gen.feed(1));
await printer.printData(device);
device.disconnect();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Bluetooth devices')),
body: ListView.separated(
itemBuilder: (context, index) {
return ListTile(
title: Text(scanResult![index].device.name),
subtitle: Text(scanResult![index].device.id.id),
onTap: () => printWithDevice(scanResult![index].device),
);
},
separatorBuilder: (context, index) => const Divider(),
itemCount: scanResult?.length ?? 0,
),
);
}
}
Let's look at the printWithDevice
method.
Generator
is a class of the esc_pos_utils
plugin. With the help of this class, we can create ESC/POS commands like this: generator.text('Hello')
.BluePrint
instance (we will create this class further).Now let's see the BluePrint class. In short, it collects ESC/POS commands and sends these data to a given printer.
void add(List<int> data)
- To add commands.List<List<int>> getChunks()
- If we need to print very large data like an image, we may encounter problems because some devices will fail because of the big package size. So we need to send the data in chunks.printData(BluetoothDevice device)
- Sends chunked data to the device via BluetoothCharacteristic.
import 'dart:math';
import 'package:flutter_blue/flutter_blue.dart';
class BluePrint {
BluePrint({this.chunkLen = 512});
final int chunkLen;
final _data = List<int>.empty(growable: true);
void add(List<int> data) {
_data.addAll(data);
}
List<List<int>> getChunks() {
final chunks = List<List<int>>.empty(growable: true);
for (var i = 0; i < _data.length; i += chunkLen) {
chunks.add(_data.sublist(i, min(i + chunkLen, _data.length)));
}
return chunks;
}
Future<void> printData(BluetoothDevice device) async {
final data = getChunks();
final characs = await _getCharacteristics(device);
for (var i = 0; i < characs.length; i++) {
if (await _tryPrint(characs[i], data)) {
break;
}
}
}
Future<bool> _tryPrint(
BluetoothCharacteristic charac,
List<List<int>> data,
) async {
for (var i = 0; i < data.length; i++) {
try {
await charac.write(data[i]);
} catch (e) {
return false;
}
}
return true;
}
Future<List<BluetoothCharacteristic>> _getCharacteristics(
BluetoothDevice device,
) async {
final services = await device.discoverServices();
final res = List<BluetoothCharacteristic>.empty(growable: true);
for (var i = 0; i < services.length; i++) {
res.addAll(services[i].characteristics);
}
return res;
}
}
I needed to add a Bluetooth thermal printer feature when I was working on Alto's POS project. It was my first Bluetooth experience with Flutter, and because of the little material about it, I had to try many things before making it work properly on every device. So I decided to write this article in hope that it will be useful for others.
The GitHub project is available here: https://github.com/usenbekov/blue-thermal-printing