Implementing Bluetooth Printer Connection in Mobile Applications
Bluetooth printing seems simple: find device, connect, send bytes. Practice — scanner won't find printer paired with another phone; connection breaks mid-print without notice; Android 12+ Bluetooth permissions changed, old code won't compile.
Android: Classic Bluetooth vs BLE
Thermal printers (Zebra ZQ300, Bixolon SPP-R310, iDPRT SP450) use Bluetooth Classic (SPP — Serial Port Profile). Not BLE. Important: APIs different.
Classic Bluetooth on Android — BluetoothAdapter, BluetoothDevice, BluetoothSocket. With Android 12 (targetSdk 31+) need new permissions:
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
Without BLUETOOTH_SCAN with neverForLocation flag, Google Play requires justification — why scan for location. Flag explicitly says not for this.
Connect via SPP UUID:
val device: BluetoothDevice = bluetoothAdapter.bondedDevices
.firstOrNull { it.name.contains("Zebra") } ?: return
val socket = device.createRfcommSocketToServiceRecord(
UUID.fromString("00001101-0000-1000-8000-00805F9B34FB") // SPP UUID
)
withContext(Dispatchers.IO) {
socket.connect()
val outputStream = socket.outputStream
outputStream.write(zplData)
outputStream.flush()
}
createRfcommSocketToServiceRecord may throw IOException if device busy with another connection. Zebra printers support single active connection — if printer already connected to another phone, connection fails. Show user clear message, not standard crash.
Device discovery. BluetoothAdapter.startDiscovery() — asynchronous, takes up to 12 seconds, drains battery. Better show list of paired devices (bondedDevices) — user pairs printer once in phone settings. New search — only on explicit request.
Zebra Link-OS SDK: When Plain Bluetooth Insufficient
For Zebra printers — official SDK — ZSDK_ANDROID_API_x.x.aar. Abstracts transport (Bluetooth / TCP) and adds useful:
- Printer status check before print (
PrinterStatus) - Get configuration (
SettingsGenerator.getConfigLabel()) - Media calibration
- Get fonts and formats on printer
val connection = BluetoothConnection(macAddress)
connection.open()
val printer = ZebraPrinterFactory.getInstance(connection)
val status = printer.currentStatus
if (status.isReadyToPrint) {
printer.sendCommand(zplTemplate)
} else {
// status.isPaused, status.isHeadOpen, status.isPaperOut — specific reason
showError(getPrinterStatusMessage(status))
}
connection.close()
Without SDK — no way know paper out before print attempt. With SDK — status.isPaperOut gives specific reason.
iOS: ExternalAccessory Framework
On iOS thermal printers with Bluetooth Classic work via ExternalAccessory framework — MFi (Made for iPhone) protocol. Printer must have MFi certification. Zebra, Star Micronics, Bixolon — certified.
import ExternalAccessory
let session = EASession(accessory: accessory, forProtocol: "com.zebra.rawport")
session?.outputStream?.schedule(in: .main, forMode: .default)
session?.outputStream?.open()
let data = zplString.data(using: .utf8)!
data.withUnsafeBytes { session?.outputStream?.write($0, maxLength: data.count) }
Protocol string (com.zebra.rawport) — vendor specific, listed in Info.plist under UISupportedExternalAccessoryProtocols. Without this, iOS won't open session.
BLE printers on iOS — no MFi restrictions, via standard CoreBluetooth. Star Micronics mPOP, some Bixolon models — support BLE.
Connection Stability and Reconnection
Bluetooth breaks. Printer turned off and on. Phone moved out of range. Implementation without auto-reconnect — source of support complaints.
Pattern: on IOException in outputStream.write() — close socket, wait 1–2 seconds, reconnect (max 3 attempts). If fail — save job in local queue, notify user. WorkManager with BackoffPolicy.LINEAR for retries when connection available.
Bluetooth printing implementation supporting Zebra/Star/Bixolon: 1–2 weeks. Cost calculated individually.







