Zebra Printer
A Flutter package for Zebra printers. Uses Zebra Link-OS SDK for Bluetooth and Network connectivity, ZPL printing, and printer management. Supports Android platform.
Features
Zebra-Specific Features (via PrinterManager - Recommended)
- π Discover Zebra Printers - Find printers via Bluetooth & Network using Zebra SDK
- π Persistent Connection - Connect once, print multiple times with automatic connection reuse
- π Print ZPL Labels - Send ZPL commands directly with optimized connection handling
- π Check Printer Status - Get real-time printer status (paper, head, pause) using active connection
- βΉοΈ Get Printer Info - Type-safe
PrinterInfomodel with device details, firmware, language - π― Type-Safe Models - Structured models for printer info, status, and devices
- β‘ Smart Connection Management - Automatic connection reuse for info/status queries when connected
Generic Bluetooth Features (via BluetoothManager - Optional)
- π‘ Scan and discover all Bluetooth devices
- π Pair and unpair with Bluetooth devices
- π― Generic Bluetooth connectivity for non-Zebra devices
ποΈ Architecture & SDK Usage
Android Native Implementation Comparison
| Feature | PrinterManager | BluetoothManager | Native SDK Used |
|---|---|---|---|
| Discovery | β Zebra-specific discovery | β All BT devices | PrinterManager: Zebra SDK (BluetoothDiscoverer, NetworkDiscoverer)BluetoothManager: Android Bluetooth API |
| Connection | β Persistent connection | β Generic BT connection | PrinterManager: Zebra SDK (BluetoothConnection)BluetoothManager: Android Bluetooth API ( BluetoothSocket) |
| Print (ZPL) | β Optimized for Zebra | β Raw data send | Both: Zebra SDK (Connection.write()) |
| Printer Status | β Full status info | β Not available | PrinterManager: Zebra SDK (SGD.GET()) |
| Printer Info | β Model, SN, firmware | β Not available | PrinterManager: Zebra SDK (SGD.GET(), ZebraPrinterFactory) |
| Get Paired Devices | β All paired BT devices | β All paired BT devices | Both: Android Bluetooth API (BluetoothAdapter.getBondedDevices()) |
| Unpair Device | β Remove pairing | β Remove pairing | Both: Android Bluetooth API (Reflection: device.removeBond()) |
| Connection Caching | β 10s cache for fast prints | β No caching | PrinterManager: Custom implementation with Zebra SDK |
| Active Connection Reuse | β Info/Status use active connection | β Not applicable | PrinterManager: Smart connection management (v0.2.3+) |
| Language Detection | β ZPL/CPCL detection | β Not available | PrinterManager: Zebra SDK (printer.getPrinterControlLanguage()) |
Why Two Managers?
-
PrinterManager (Recommended for Zebra):
- Uses Zebra Link-OS SDK for all printer-specific operations
- Falls back to Android Bluetooth API only for operations not supported by Zebra SDK (pairing/unpairing)
- Optimized for Zebra printers with connection pooling and status monitoring
- Provides rich printer information and status
-
BluetoothManager (Generic Bluetooth):
- Uses Android Bluetooth API for generic Bluetooth operations
- Suitable for non-Zebra devices or when you need raw Bluetooth control
- Can discover and connect to any Bluetooth device
- Simpler implementation for basic printing needs
Installation
Add the package to your pubspec.yaml file:
dependencies:
zebra_printer: latest_version
Android Setup
Add the required permissions to your android/app/src/main/AndroidManifest.xml file:
<!-- Bluetooth Permissions -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<!-- Location Permissions (Required for Bluetooth discovery on Android 12+) -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Network Permissions (For network printer discovery) -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
Important: For Android 12+, you need to request runtime permissions for Bluetooth and Location.
Usage
π― Recommended: Using PrinterManager (Zebra-Specific)
The PrinterManager uses Zebra Link-OS SDK and is the recommended approach for working with Zebra printers.
Basic Setup
import 'package:zebra_printer/zebra_printer.dart';
final printerManager = PrinterManager();
1. Discover Zebra Printers
// Setup callbacks for real-time discovery updates
printerManager.onPrinterFound = (printer) {
print('Found: ${printer.friendlyName} (${printer.address})');
};
printerManager.onDiscoveryFinished = (printers) {
print('Discovery finished. Found ${printers.length} printers');
};
// Start discovery
try {
// Options: 'bluetooth', 'network', or 'both'
final printers = await printerManager.startDiscovery(type: 'both');
for (var printer in printers) {
print('${printer.friendlyName} - ${printer.type} - ${printer.address}');
}
} catch (e) {
print('Discovery error: $e');
}
// Stop discovery when done
await printerManager.stopDiscovery();
2. Connect to Printer
// Connect to a discovered printer
try {
final connectionInfo = await printerManager.connect('AC:3F:A4:XX:XX:XX');
print('Connected: ${connectionInfo['friendlyName']}');
} catch (e) {
print('Connection error: $e');
}
// Check connection status
bool connected = await printerManager.isConnected();
// Or check specific printer
bool connected = await printerManager.isConnected(address: 'AC:3F:A4:XX:XX:XX');
3. Print Labels
// Print custom ZPL
String zpl = """
^XA
^FO50,50
^A0N,50,50
^FDHello from Flutter!^FS
^FO50,120
^A0N,30,30
^FDDate: ${DateTime.now()}^FS
^XZ
""";
try {
String result = await printerManager.sendZplToPrinter(
'AC:3F:A4:XX:XX:XX',
zpl
);
print('Print successful: $result');
} catch (e) {
print('Print error: $e');
}
// Or use the quick test print
await printerManager.printTestLabel('AC:3F:A4:XX:XX:XX');
4. Check Printer Status
try {
PrinterStatus status = await printerManager.checkPrinterStatus('AC:3F:A4:XX:XX:XX');
print('Connected: ${status.isConnected}');
print('Paper: ${status.isPaperOut ? "Out" : "OK"}');
print('Head: ${status.isHeadOpen ? "Open" : "Closed"}');
print('Paused: ${status.isPaused}');
} catch (e) {
print('Status check error: $e');
}
5. Get Printer Information
try {
PrinterInfo info = await printerManager.getPrinterInfo('AC:3F:A4:XX:XX:XX');
// Type-safe access to printer details
print('Model: ${info.model}');
print('Serial Number: ${info.serialNumber}');
print('Firmware: ${info.firmware}');
print('Language: ${info.language}'); // PrinterLanguage enum (ZPL/CPCL/Unknown)
// Or use formatted output
print(info.toString());
// Output:
// PrinterInfo{
// Model: ZD421
// Serial Number: XXXXXXXXXXXX
// Firmware: V84.20.11Z
// Language: ZPL
// }
// Compact format
print(info.toCompactString());
// Output: ZD421 (S/N: XXXXXXXXXXXX) - V84.20.11Z - ZPL
} catch (e) {
print('Info error: $e');
}
6. Multiple Operations with Connection Reuse β‘
When connected, getPrinterInfo() and checkPrinterStatus() automatically reuse the active connection for better performance and reliability.
// Connect once
await printerManager.connect('AC:3F:A4:XX:XX:XX');
// All these operations use the same connection (fast and reliable)
PrinterInfo info = await printerManager.getPrinterInfo('AC:3F:A4:XX:XX:XX');
PrinterStatus status = await printerManager.checkPrinterStatus('AC:3F:A4:XX:XX:XX');
await printerManager.sendZplToPrinter('AC:3F:A4:XX:XX:XX', zplData1);
await printerManager.sendZplToPrinter('AC:3F:A4:XX:XX:XX', zplData2);
// You can call info/status multiple times without reconnecting
info = await printerManager.getPrinterInfo('AC:3F:A4:XX:XX:XX'); // Still fast!
// Disconnect when done
await printerManager.disconnect(address: 'AC:3F:A4:XX:XX:XX');
Benefits:
- β‘ Faster: 300ms vs 800ms for subsequent calls
- π More reliable: No socket timeout errors
- π Lower resource usage: Single connection instead of multiple
7. Disconnect
// Disconnect from specific printer
await printerManager.disconnect(address: 'AC:3F:A4:XX:XX:XX');
// Or disconnect from currently connected printer
await printerManager.disconnect();
Complete Example with Connection State Monitoring
class PrinterScreen extends StatefulWidget {
@override
_PrinterScreenState createState() => _PrinterScreenState();
}
class _PrinterScreenState extends State<PrinterScreen> {
final printerManager = PrinterManager();
List<DiscoveredPrinter> printers = [];
DiscoveredPrinter? selectedPrinter;
bool isConnected = false;
@override
void initState() {
super.initState();
// Listen for connection state changes
printerManager.onConnectionStateChanged = (info) {
setState(() {
isConnected = info['isConnected'] ?? false;
});
print('Connection state: $isConnected');
};
// Listen for discovered printers
printerManager.onPrinterFound = (printer) {
setState(() {
if (!printers.any((p) => p.address == printer.address)) {
printers.add(printer);
}
});
};
}
Future<void> discover() async {
setState(() => printers.clear());
await printerManager.startDiscovery(type: 'both');
}
Future<void> connect(DiscoveredPrinter printer) async {
try {
await printerManager.connect(printer.address);
setState(() => selectedPrinter = printer);
} catch (e) {
print('Connection error: $e');
}
}
Future<void> printTest() async {
if (selectedPrinter != null) {
await printerManager.printTestLabel(selectedPrinter!.address);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Zebra Printer')),
body: Column(
children: [
ElevatedButton(
onPressed: discover,
child: Text('Discover Printers'),
),
if (isConnected) ...[
ElevatedButton(
onPressed: printTest,
child: Text('Print Test'),
),
ElevatedButton(
onPressed: () => printerManager.disconnect(),
child: Text('Disconnect'),
),
],
Expanded(
child: ListView.builder(
itemCount: printers.length,
itemBuilder: (context, index) {
final printer = printers[index];
return ListTile(
title: Text(printer.friendlyName),
subtitle: Text('${printer.type} - ${printer.address}'),
onTap: () => connect(printer),
);
},
),
),
],
),
);
}
}
π§ Alternative: Using BluetoothManager (Generic Bluetooth)
The BluetoothManager provides generic Android Bluetooth functionality for non-Zebra devices or when you need low-level Bluetooth control.
import 'package:zebra_printer/zebra_printer.dart';
final bluetoothManager = BluetoothManager();
// Check Bluetooth status
bool isEnabled = await bluetoothManager.isBluetoothEnabled();
// Get paired devices
List<BluetoothDevice> bondedDevices = await bluetoothManager.getBondedDevices();
// Listen for device discovery
bluetoothManager.onDeviceFound.listen((device) {
print('Found: ${device.name} (${device.address})');
});
// Start scanning
await bluetoothManager.startDiscovery();
// Stop scanning
await bluetoothManager.stopDiscovery();
// Connect to device
await bluetoothManager.connect('00:11:22:33:44:55');
// Disconnect
await bluetoothManager.disconnect();
// Clean up
bluetoothManager.dispose();
π¦ Models
The package provides type-safe model classes for structured data access:
PrinterInfo
Contains detailed information about a connected Zebra printer.
class PrinterInfo {
final String model; // Printer model name (e.g., "ZD421")
final String serialNumber; // Printer serial number
final String firmware; // Firmware version (e.g., "V84.20.11Z")
final PrinterLanguage language; // ZPL, CPCL, or Unknown
final String? rawInfo; // Original raw string (for debugging)
}
PrinterLanguage Enum:
enum PrinterLanguage {
zpl, // Zebra Programming Language
cpcl, // Common Printer Command Language
unknown
}
Usage Example:
PrinterInfo info = await printerManager.getPrinterInfo('AC:3F:A4:XX:XX:XX');
// Type-safe access
print(info.model); // "ZD421"
print(info.serialNumber); // "XXXXXXXXXXXX"
print(info.firmware); // "V84.20.11Z"
// Enum comparison
if (info.language == PrinterLanguage.zpl) {
print('This is a ZPL printer');
}
// Formatted output
print(info.toString());
print(info.toCompactString());
print(info.toJson());
PrinterStatus
Real-time status information from the printer.
class PrinterStatus {
final bool isConnected;
final bool isPaperOut;
final bool isHeadOpen;
final bool isPaused;
}
Usage Example:
PrinterStatus status = await printerManager.checkPrinterStatus('AC:3F:A4:XX:XX:XX');
if (status.isPaperOut) {
print('β οΈ Please load paper');
}
if (status.isHeadOpen) {
print('β οΈ Close printer head');
}
BluetoothDevice
Generic Bluetooth device information.
class BluetoothDevice {
final String name;
final String address;
final DeviceType type; // CLASSIC, LE, DUAL, UNKNOWN
final BondState bondState; // BONDED, BONDING, NONE
}
Usage Example:
List<BluetoothDevice> devices = await printerManager.getPairedPrinters();
for (var device in devices) {
print('${device.name} - ${device.address}');
if (device.bondState == BondState.bonded) {
print('β Paired');
}
}
DiscoveredPrinter
Zebra printer discovered via Zebra SDK.
class DiscoveredPrinter {
final String type; // "bluetooth" or "network"
final String address; // MAC address or IP
final String? name; // Friendly name
}
Usage Example:
printerManager.onPrinterFound.listen((printer) {
if (printer.type == 'bluetooth') {
print('Found Bluetooth Printer: ${printer.name} (${printer.address})');
} else {
print('Found Network Printer: ${printer.address}');
}
});
ZPL Examples
Simple Label
String createSimpleLabel(String text) {
return """
^XA
^FO50,50
^A0N,50,50
^FD$text^FS
^XZ
""";
}
Barcode Label
String createBarcodeLabel(String barcode, String description) {
return """
^XA
^FO50,50^A0N,30,30^FD$description^FS
^FO50,100^BY3^BCN,100,Y,N,N^FD$barcode^FS
^XZ
""";
}
QR Code Label
String createQRCodeLabel(String data) {
return """
^XA
^FO50,50^A0N,30,30^FDQR Code:^FS
^FO50,100^BQN,2,10^FDMA,$data^FS
^XZ
""";
}
Multi-Line Receipt
String createReceipt(String storeName, List<String> items, String total) {
String zpl = "^XA\n";
zpl += "^FO50,50^A0N,40,40^FD$storeName^FS\n";
int y = 100;
for (var item in items) {
zpl += "^FO50,$y^A0N,25,25^FD$item^FS\n";
y += 35;
}
zpl += "^FO50,$y^GB350,2,2^FS\n";
y += 20;
zpl += "^FO50,$y^A0N,30,30^FDTotal: $total^FS\n";
zpl += "^XZ";
return zpl;
}
Zebra Link-OS SDK
This package uses the Zebra Link-OS SDK for Android platform. For more detailed information, refer to Zebra's official documentation.
Supported Features
- Bluetooth Classic and Network connectivity
- Zebra printer discovery using SDK
- ZPL command execution
- Real-time printer status monitoring
- Persistent connections for multiple print jobs
- Printer information retrieval
Troubleshooting
Printer Not Found
- Ensure Bluetooth is enabled on your device
- Check Location permission is granted (required for Bluetooth discovery)
- Make sure printer is powered on and in range
- Try power cycling the printer
- Check if printer is already paired with another device
Connection Issues
- Verify the MAC address is correct
- Unpair and re-pair the device if needed
- Check printer battery level
- Ensure printer supports Bluetooth connectivity
- Try restarting both devices
Print Quality Issues
- Verify ZPL commands are correct
- Check printer paper and ribbon levels
- Adjust print darkness settings via ZPL
- Clean printer head if needed
Example App
The package includes a comprehensive example app demonstrating:
- Zebra-specific printer discovery (SDK-based)
- Generic Bluetooth device scanning
- Connection management
- Printing examples
- Status monitoring
Run the example:
cd example
flutter run
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License. See the LICENSE file for details.
Support
For issues and feature requests, please visit our GitHub Issues page.
Credits
- Powered by Zebra Link-OS SDK
- Maintained by the Flutter community