Skip to main content

posprinter_usb_android 1.0.0 copy "posprinter_usb_android: ^1.0.0" to clipboard
posprinter_usb_android: ^1.0.0 copied to clipboard

PlatformAndroid

Android USB host transport for ESC/POS thermal receipt printers: device discovery, the USB permission handshake and raw bulk-transfer printing.

example/lib/main.dart

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:posprinter_usb_android/posprinter_usb_android.dart';

void main() => runApp(const ExampleApp());

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'USB printer example',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF2563EB)),
      ),
      home: const UsbPrinterPage(),
    );
  }
}

class UsbPrinterPage extends StatefulWidget {
  const UsbPrinterPage({super.key});

  @override
  State<UsbPrinterPage> createState() => _UsbPrinterPageState();
}

class _UsbPrinterPageState extends State<UsbPrinterPage> {
  final PrinterManager _manager = PrinterManager.instance;
  final List<PrinterDevice> _printers = <PrinterDevice>[];

  StreamSubscription<PrinterDevice>? _discoverySubscription;
  StreamSubscription<USBStatus>? _statusSubscription;

  PrinterDevice? _connectedPrinter;
  USBStatus _status = USBStatus.none;
  bool _busy = false;

  @override
  void initState() {
    super.initState();
    _statusSubscription = _manager.stateUSB.listen((USBStatus status) {
      if (!mounted) return;
      setState(() {
        _status = status;
        if (status == USBStatus.none) _connectedPrinter = null;
      });
    });
    // Deferred to the first frame: the plugin's discovery stream never
    // completes, and `_scan` toggles state as soon as it starts.
    WidgetsBinding.instance.addPostFrameCallback((Duration _) {
      if (mounted) unawaited(_scan());
    });
  }

  @override
  void dispose() {
    _discoverySubscription?.cancel();
    _statusSubscription?.cancel();
    super.dispose();
  }

  Future<void> _scan() async {
    if (_busy) return;
    setState(() {
      _busy = true;
      _printers.clear();
    });

    await _discoverySubscription?.cancel();
    _discoverySubscription = _manager
        .discovery(type: PrinterType.usb)
        .listen((PrinterDevice device) {
      if (!mounted) return;
      setState(() => _printers.add(device));
    });

    // The platform discovery stream has no completion event: give it a moment,
    // then re-enable the UI.
    await Future<void>.delayed(const Duration(seconds: 3));
    if (mounted) setState(() => _busy = false);
  }

  Future<void> _connect(PrinterDevice device) async {
    if (_busy) return;
    setState(() => _busy = true);

    final bool connected = await _manager.connect(
      type: PrinterType.usb,
      model: UsbPrinterInput(
        name: device.name,
        vendorId: device.vendorId,
        productId: device.productId,
      ),
    );

    if (!mounted) return;
    setState(() {
      _busy = false;
      if (connected) _connectedPrinter = device;
    });
    _showMessage(
      connected
          ? 'Connected to ${device.name}.'
          : 'Could not connect to ${device.name}. Replug the cable and retry.',
    );
  }

  Future<void> _printTest() async {
    if (_busy) return;
    setState(() => _busy = true);

    final bool sent = await _manager.send(
      type: PrinterType.usb,
      bytes: _testReceipt(),
    );

    if (!mounted) return;
    setState(() => _busy = false);
    _showMessage(sent ? 'Test receipt sent.' : 'Print failed.');
  }

  Future<void> _disconnect() async {
    if (_busy) return;
    setState(() => _busy = true);

    await _manager.disconnect(type: PrinterType.usb);
    if (!mounted) return;
    setState(() {
      _busy = false;
      _connectedPrinter = null;
    });
  }

  void _showMessage(String message) {
    if (!mounted) return;
    ScaffoldMessenger.of(context)
      ..hideCurrentSnackBar()
      ..showSnackBar(SnackBar(content: Text(message)));
  }

  /// Minimal ESC/POS test page: reset, centre, text, feed, cut.
  List<int> _testReceipt() {
    final List<int> bytes = <int>[
      0x1B, 0x40, // reset
      0x1B, 0x61, 0x01, // centre
    ];
    bytes.addAll('USB PRINT TEST\n'.codeUnits);
    bytes.addAll(<int>[0x1B, 0x61, 0x00]); // left
    bytes.addAll('Printer: ${_connectedPrinter?.name ?? 'unknown'}\n'.codeUnits);
    bytes.addAll('Printed: ${DateTime.now()}\n'.codeUnits);
    bytes.addAll(<int>[0x0A, 0x0A, 0x0A]);
    bytes.addAll(<int>[0x1D, 0x56, 0x00]); // cut
    return bytes;
  }

  bool _isConnected(PrinterDevice device) {
    final PrinterDevice? current = _connectedPrinter;
    if (current == null) return false;
    if (current.vendorId != null && device.vendorId != null) {
      return current.vendorId == device.vendorId &&
          current.productId == device.productId;
    }
    return current.name == device.name;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('USB thermal printer')),
      body: SafeArea(
        child: Column(
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(12),
              child: Row(
                children: <Widget>[
                  Expanded(
                    child: FilledButton.icon(
                      onPressed: _busy ? null : _scan,
                      icon: const Icon(Icons.usb_rounded),
                      label: Text(_busy ? 'Working…' : 'Scan for printers'),
                    ),
                  ),
                ],
              ),
            ),
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 12),
              child: Align(
                alignment: Alignment.centerLeft,
                child: Text(
                  'USB state: ${_status.name}',
                  style: Theme.of(context).textTheme.bodySmall,
                ),
              ),
            ),
            Expanded(
              child: _printers.isEmpty
                  ? const Center(
                      child: Padding(
                        padding: EdgeInsets.all(24),
                        child: Text(
                          'Attach a USB receipt printer, then tap Scan. '
                          'Android will ask for permission the first time you '
                          'connect.',
                          textAlign: TextAlign.center,
                        ),
                      ),
                    )
                  : ListView.builder(
                      itemCount: _printers.length,
                      itemBuilder: (BuildContext context, int index) {
                        final PrinterDevice device = _printers[index];
                        final bool connected = _isConnected(device);
                        return ListTile(
                          leading: Icon(
                            connected
                                ? Icons.usb_rounded
                                : Icons.print_rounded,
                          ),
                          title: Text(device.name),
                          subtitle: Text(
                            device.vendorId == null
                                ? 'USB printer'
                                : 'VID ${device.vendorId} • '
                                      'PID ${device.productId}',
                          ),
                          trailing: connected
                              ? const Text('Connected')
                              : const Icon(Icons.chevron_right_rounded),
                          onTap: _busy ? null : () => _connect(device),
                        );
                      },
                    ),
            ),
            if (_connectedPrinter != null)
              Padding(
                padding: const EdgeInsets.all(12),
                child: Row(
                  children: <Widget>[
                    Expanded(
                      child: FilledButton.icon(
                        onPressed: _busy ? null : _printTest,
                        icon: const Icon(Icons.print_rounded),
                        label: const Text('Test print'),
                      ),
                    ),
                    const SizedBox(width: 8),
                    Expanded(
                      child: OutlinedButton.icon(
                        onPressed: _busy ? null : _disconnect,
                        icon: const Icon(Icons.link_off_rounded),
                        label: const Text('Disconnect'),
                      ),
                    ),
                  ],
                ),
              ),
          ],
        ),
      ),
    );
  }
}
0
likes
160
points
45
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Android USB host transport for ESC/POS thermal receipt printers: device discovery, the USB permission handshake and raw bulk-transfer printing.

Repository (GitHub)
View/report issues

Topics

#printer #thermal-printer #usb #esc-pos

License

MIT (license)

Dependencies

flutter

More

Packages that depend on posprinter_usb_android

Packages that implement posprinter_usb_android