
Interact with ESC/POS thermal printers via TCP/IP or USB, with reactive connection monitoring, off-screen UI capture into print-ready raster, barcode/QR support, and fluent ESC/POS command API.
Printer-KMP is a powerful, lightweight Kotlin Multiplatform library designed to make interacting with ESC/POS thermal receipt printers effortless. Built exclusively for Android and Desktop (JVM) targets, this library provides a fluent API for hardware control, reactive connection monitoring, and seamless Jetpack Compose UI capturing.
| 🛒 Compose UI Capture | 🚀 ESC/POS | 🎟️ ESC/POS |
|---|---|---|
CONNECTION_REFUSED, DEVICE_NOT_FOUND, and TIMEOUT.PERMISSION_DENIED and DEVICE_OFFLINE.Add the dependency to your shared KMP module's build.gradle.kts file:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.mamon-aburawi:printer-kmp:{last_version}")
}
}
}To allow the library to communicate with network and USB printers on Android, you must add the following permissions and features to your androidApp/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.USB_PERMISSION" />
<uses-feature android:name="android.hardware.usb.host" android:required="true" />
Printer-KMP supports both Network and USB printers with platform-specific optimizations under the hood.
Connecting via TCP/IP:
// Uses Ktor Network sockets internally for fast, reliable local communication
val tcpConnection = TcpConnection(ipAddress = "192.168.1.100", port = 9100, autoConnect = true)Connecting via USB:
The UsbConnection handles the heavy lifting of direct hardware communication.
val usbConnection = remember { UsbConnection(autoConnect = false) }
// On Android: Connect via raw Hardware IDs to bypass the OS spooler, you can use fun scanForAvailablePrinters to get you device data
usbConnection.connectViaUsb(
vendorId = "YOUR_VENDOR_ID_DEVICE",
productId = "YOUR_PRODUCT_ID_DEVICE",
onSuccess = { println("USB Connected!") },
onFailed = { error -> println("Failed: ${error.message}") }
)
// On Desktop (JVM): Connect via the OS-installed Printer Name
usbConnection.connectViaUsb(
targetPrinterName = "XP-80C",
onSuccess = { println("Desktop Printer Connected!") },
onFailed = { error -> println("Failed: ${error.message}") }
)
**Monitoring Connection Health:**
You can easily observe the hardware state reactively in your UI:
```kotlin
val printerErrors by usbConnection.errorStatus.collectAsState()
if (printerErrors == ConnectionError.DEVICE_OFFLINE) {
Text("Please check the USB cable!")
}Before connecting, you can easily scan for physically connected USB printers (Android) or installed system printers (Desktop).
```kotlin
val usbConnection = remember { UsbConnection(autoConnect = false) }
// 1. Trigger the background hardware scan
coroutineScope.launch {
usbConnection.scanForAvailablePrinters()
}
// 2. Observe the results reactively in your UI
val availablePrinters by usbConnection.availablePrinters.collectAsState()
LazyColumn {
items(availablePrinters) { printer ->
Text("Printer: ${printer.name}")
// On Android: Use printer.vendorId and printer.productId to connect
// On Desktop: Use printer.name to connect
}
}
Use `EscPosPrinter` and its fluent API to design your receipt. Commands are buffered and sent asynchronously when you call `.print()`.
```kotlin
val printer = EscPosPrinter(usbConnection)
coroutineScope.launch {
printer.print(
onSuccess = { println("Printed successfully!") },
onFailed = { error -> println("Print failed: $error") }
) {
text("MY AWESOME STORE", alignment = PosAlignment.CENTER, fontStyle = PosFontStyle.BOLD)
text("123 Main Street", alignment = PosAlignment.CENTER)
line(1)
text("1x Coffee $3.00")
text("1x Muffin $2.50")
text("--------------------------------")
text("TOTAL: $5.50", fontStyle = PosFontStyle.BOLD)
line(1)
// Highly customizable Barcodes & QR Codes
barcode("123456789", type = Barcode1D.CODE_128, alignment = PosAlignment.CENTER, showTextBelow = true)
qrCode("[https://github.com/mamon-aburawi](https://github.com/mamon-aburawi)", alignment = PosAlignment.CENTER)
cut() // Hardware auto-cut
beep() // Hardware buzzer
}
}
Design your receipt natively in **Jetpack Compose**, capture it entirely off-screen, and print it as a high-quality raster image. This handles unbounded vertical heights (for long receipts) and perfectly scales the UI to match your 80mm or 58mm printer hardware.
```kotlin
val captureController = rememberCaptureController()
// 1. Wrap your Compose design (Supports infinite vertical scrolling heights!)
Box(modifier = Modifier.capturable(captureController, allowOverflow = true)) {
MyBeautifulComposeReceipt()
}
// 2. Capture and print!
Button(onClick = {
coroutineScope.launch {
// Await the asynchronous UI capture
val bitmap = captureController.captureAsync().await()
// Convert ImageBitmap to ESC/POS monochrome bytes safely
val printerBytes = bitmap.toByteArrayPos(paperWidth = Paper.MM_80)
printer.print {
capture(printerBytes)
cut()
}
}
}) {
Text("Print Compose Receipt")
}
text(text, alignment, fontSize, fontStyle, fontType) - Print styled text.line(count) - Feed empty lines to clear the printhead.cut() - Trigger the hardware auto-cutter.beep() - Trigger the hardware buzzer.density(densityCode) - Adjust the thermal head heat/darkness (e.g., EscPosPrinter.MAX_DARKNESS_MODE).icon(bitmap, width, height, alignment) - Print scaled logos (ImageDimension.MM_5 to MM_45).capture(byteArray) - Inject fully rasterized ESC/POS image bands.Printer-KMP supports precise configuration of industry-standard codes:
UPC_A, EAN_13, EAN_8, CODE_39, ITF, CODABAR, CODE_93, CODE_128.scanForAvailablePrinters(): Triggers a hardware scan. Results are emitted to the availablePrinters: StateFlow<List<PosPrinter>>.disconnect(onSuccess, onFailed): Safely releases the USB interface or network socket.If this library saved you hours of reading ESC/POS manuals, fighting with byte arrays, and debugging USB interfaces across platforms, please support the project by giving it a Star ⭐️ on GitHub and sharing it with your developer friends!
Your support is the best motivation to keep me updating and maintaining this open-source library. 🚀
Developed with ❤️ by Mamon Aburawi
Printer-KMP is a powerful, lightweight Kotlin Multiplatform library designed to make interacting with ESC/POS thermal receipt printers effortless. Built exclusively for Android and Desktop (JVM) targets, this library provides a fluent API for hardware control, reactive connection monitoring, and seamless Jetpack Compose UI capturing.
| 🛒 Compose UI Capture | 🚀 ESC/POS | 🎟️ ESC/POS |
|---|---|---|
CONNECTION_REFUSED, DEVICE_NOT_FOUND, and TIMEOUT.PERMISSION_DENIED and DEVICE_OFFLINE.Add the dependency to your shared KMP module's build.gradle.kts file:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.mamon-aburawi:printer-kmp:{last_version}")
}
}
}To allow the library to communicate with network and USB printers on Android, you must add the following permissions and features to your androidApp/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.USB_PERMISSION" />
<uses-feature android:name="android.hardware.usb.host" android:required="true" />
Printer-KMP supports both Network and USB printers with platform-specific optimizations under the hood.
Connecting via TCP/IP:
// Uses Ktor Network sockets internally for fast, reliable local communication
val tcpConnection = TcpConnection(ipAddress = "192.168.1.100", port = 9100, autoConnect = true)Connecting via USB:
The UsbConnection handles the heavy lifting of direct hardware communication.
val usbConnection = remember { UsbConnection(autoConnect = false) }
// On Android: Connect via raw Hardware IDs to bypass the OS spooler, you can use fun scanForAvailablePrinters to get you device data
usbConnection.connectViaUsb(
vendorId = "YOUR_VENDOR_ID_DEVICE",
productId = "YOUR_PRODUCT_ID_DEVICE",
onSuccess = { println("USB Connected!") },
onFailed = { error -> println("Failed: ${error.message}") }
)
// On Desktop (JVM): Connect via the OS-installed Printer Name
usbConnection.connectViaUsb(
targetPrinterName = "XP-80C",
onSuccess = { println("Desktop Printer Connected!") },
onFailed = { error -> println("Failed: ${error.message}") }
)
**Monitoring Connection Health:**
You can easily observe the hardware state reactively in your UI:
```kotlin
val printerErrors by usbConnection.errorStatus.collectAsState()
if (printerErrors == ConnectionError.DEVICE_OFFLINE) {
Text("Please check the USB cable!")
}Before connecting, you can easily scan for physically connected USB printers (Android) or installed system printers (Desktop).
```kotlin
val usbConnection = remember { UsbConnection(autoConnect = false) }
// 1. Trigger the background hardware scan
coroutineScope.launch {
usbConnection.scanForAvailablePrinters()
}
// 2. Observe the results reactively in your UI
val availablePrinters by usbConnection.availablePrinters.collectAsState()
LazyColumn {
items(availablePrinters) { printer ->
Text("Printer: ${printer.name}")
// On Android: Use printer.vendorId and printer.productId to connect
// On Desktop: Use printer.name to connect
}
}
Use `EscPosPrinter` and its fluent API to design your receipt. Commands are buffered and sent asynchronously when you call `.print()`.
```kotlin
val printer = EscPosPrinter(usbConnection)
coroutineScope.launch {
printer.print(
onSuccess = { println("Printed successfully!") },
onFailed = { error -> println("Print failed: $error") }
) {
text("MY AWESOME STORE", alignment = PosAlignment.CENTER, fontStyle = PosFontStyle.BOLD)
text("123 Main Street", alignment = PosAlignment.CENTER)
line(1)
text("1x Coffee $3.00")
text("1x Muffin $2.50")
text("--------------------------------")
text("TOTAL: $5.50", fontStyle = PosFontStyle.BOLD)
line(1)
// Highly customizable Barcodes & QR Codes
barcode("123456789", type = Barcode1D.CODE_128, alignment = PosAlignment.CENTER, showTextBelow = true)
qrCode("[https://github.com/mamon-aburawi](https://github.com/mamon-aburawi)", alignment = PosAlignment.CENTER)
cut() // Hardware auto-cut
beep() // Hardware buzzer
}
}
Design your receipt natively in **Jetpack Compose**, capture it entirely off-screen, and print it as a high-quality raster image. This handles unbounded vertical heights (for long receipts) and perfectly scales the UI to match your 80mm or 58mm printer hardware.
```kotlin
val captureController = rememberCaptureController()
// 1. Wrap your Compose design (Supports infinite vertical scrolling heights!)
Box(modifier = Modifier.capturable(captureController, allowOverflow = true)) {
MyBeautifulComposeReceipt()
}
// 2. Capture and print!
Button(onClick = {
coroutineScope.launch {
// Await the asynchronous UI capture
val bitmap = captureController.captureAsync().await()
// Convert ImageBitmap to ESC/POS monochrome bytes safely
val printerBytes = bitmap.toByteArrayPos(paperWidth = Paper.MM_80)
printer.print {
capture(printerBytes)
cut()
}
}
}) {
Text("Print Compose Receipt")
}
text(text, alignment, fontSize, fontStyle, fontType) - Print styled text.line(count) - Feed empty lines to clear the printhead.cut() - Trigger the hardware auto-cutter.beep() - Trigger the hardware buzzer.density(densityCode) - Adjust the thermal head heat/darkness (e.g., EscPosPrinter.MAX_DARKNESS_MODE).icon(bitmap, width, height, alignment) - Print scaled logos (ImageDimension.MM_5 to MM_45).capture(byteArray) - Inject fully rasterized ESC/POS image bands.Printer-KMP supports precise configuration of industry-standard codes:
UPC_A, EAN_13, EAN_8, CODE_39, ITF, CODABAR, CODE_93, CODE_128.scanForAvailablePrinters(): Triggers a hardware scan. Results are emitted to the availablePrinters: StateFlow<List<PosPrinter>>.disconnect(onSuccess, onFailed): Safely releases the USB interface or network socket.If this library saved you hours of reading ESC/POS manuals, fighting with byte arrays, and debugging USB interfaces across platforms, please support the project by giving it a Star ⭐️ on GitHub and sharing it with your developer friends!
Your support is the best motivation to keep me updating and maintaining this open-source library. 🚀
Developed with ❤️ by Mamon Aburawi