Zebra Printer

pub package Pub Version (including pre-releases) pub publisher Pub Likes Pub Popularity Pub Points License: MIT

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

  • πŸ” 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 PrinterInfo model 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?

  1. 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
  2. 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

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;
}

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

  1. Ensure Bluetooth is enabled on your device
  2. Check Location permission is granted (required for Bluetooth discovery)
  3. Make sure printer is powered on and in range
  4. Try power cycling the printer
  5. Check if printer is already paired with another device

Connection Issues

  1. Verify the MAC address is correct
  2. Unpair and re-pair the device if needed
  3. Check printer battery level
  4. Ensure printer supports Bluetooth connectivity
  5. Try restarting both devices
  1. Verify ZPL commands are correct
  2. Check printer paper and ribbon levels
  3. Adjust print darkness settings via ZPL
  4. 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

Libraries

main
zebra_printer