Dollar StarXpand

Seamlessly Integrate Star Micronics Hardware with Dollar POS

Overview

Dollar StarXpand is a Flutter plugin designed to integrate Star Micronics printers and cash drawers into the Dollar POS system. This plugin provides platform-specific implementations for iOS and Android, enabling smooth communication with Star Micronics devices for printing receipts, opening cash drawers, and more.

Features

  • Discover and connect to Star Micronics printers.
  • Print receipts with customizable templates.
  • Open cash drawers programmatically.
  • Stream printer discovery events.

Installation

Add the plugin to your Flutter project by including it in your pubspec.yaml:

dependencies: 
	dollar_starxpand: latest

Run flutter pub get to fetch the package.

iOS Setup

To use the plugin on iOS, update your Info.plist file to include the required permissions:

<key>NSBluetoothPeripheralUsageDescription</key>
<string>Use Bluetooth for communication with the printer.</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Use Bluetooth for communication with the printer.</string>
<key>NSLocalNetworkUsageDescription</key>
<string>Use Local Network for communication with the printer or discovery the printers.</string>
<key>UISupportedExternalAccessoryProtocols</key>
<array>
  <string>jp.star-m.starpro</string>
</array>

Android Setup

Ensure the following permissions are added to your AndroidManifest.xml file for Bluetooth:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

Example Usage

Import the Plugin

import 'package:dollar_starxpand/dollar_starxpand.dart';

Discover Printers

final plugin = DollarStarxpand();

await plugin.startDiscovery();
plugin.onPrinterDiscovered((identifier, interfaceType, model) {
  print("Printer found: $model ($interfaceType)");
});

Connect to a Printer

await plugin.connectPrinter(identifier: "printer_identifier", interface: "bluetooth");
await plugin.printReceipt(
  identifier: "printer_identifier",
  interface: "bluetooth",
  printData: {
    "id": "12345",
    "date": "DEC 17, 2024 12:31 AM",
    "store": {"name": "Store Name", "address": "123 Main St", "phone": "123-456-7890"},
    // Additional receipt data here
  },
);

Open Cash Drawer

await plugin.openCashDrawer(identifier: "printer_identifier", interface: "bluetooth");

Method Channels

The plugin uses the following method channels for platform communication:

  • Method Channel: dollar_starxpand
    Handles printer connection, printing, and drawer operations.

  • Event Channel: printerDiscovered
    Streams discovered printer details to Flutter.

Example App

Here’s a complete example showcasing printer discovery, connection, and operations:

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

void main() {
  runApp(const MaterialApp(
    home: MyApp(),
  ));
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // Instance of the plugin  
  final _dollarStarxpandPlugin = DollarStarxpand();

  // State variables  
  List<Map<String, dynamic>> discoveredPrinters = [];
  bool isDiscovering = false;
  String statusMessage = 'Press "Discover Printers" to search.';

  @override
  void initState() {
    super.initState();
    setupPrinterDiscoveryListener();
  }

  // Start discovery of printers  
  Future<void> startDiscovery() async {
    setState(() {
      isDiscovering = true;
      discoveredPrinters.clear();
      statusMessage = "Starting discovery...";
    });

    try {
      await _dollarStarxpandPlugin.startDiscovery();
    } catch (error) {
      if (!mounted) return;
      setState(() {
        statusMessage = "Discovery failed: $error";
      });
      _showSnackBar('Discovery failed: $error');
    } finally {
      setState(() {
        isDiscovering = false;
      });
    }
  }

  // Set up listener for printer discovery events  
  void setupPrinterDiscoveryListener() {
    _dollarStarxpandPlugin.onPrinterDiscovered(
          (identifier, interfaceType, model) {
        if (!mounted) return;
        setState(() {
          discoveredPrinters.add({
            "identifier": identifier,
            "interface": interfaceType,
            "model": model,
          });
          statusMessage = "Printer found: $identifier $interfaceType $model";
        });
      },
    );
  }

  // Connect to a printer  
  Future<void> connectToPrinter(String identifier, String interface) async {
    setState(() {
      statusMessage = "Connecting to printer: $identifier...";
    });

    try {
      await _dollarStarxpandPlugin.connectPrinter(identifier, interface);
      if (!mounted) return;
      setState(() {
        statusMessage = "Connected to printer: $identifier";
      });
      _showSnackBar('Connected to printer: $identifier');
    } catch (error) {
      if (!mounted) return;
      setState(() {
        statusMessage = "Failed to connect: $error";
      });
      _showSnackBar('Failed to connect: $error');
    }
  }

  // Helper method to show a SnackBar  
  void _showSnackBar(String message) {
    if (!mounted) return;
    ScaffoldMessenger.of(context)
        .showSnackBar(SnackBar(content: Text(message)));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Printer Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // Status message  
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: Text(
                statusMessage,
                textAlign: TextAlign.center,
                style: const TextStyle(fontSize: 16.0),
              ),
            ),
            // Discover Printers button  
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: ElevatedButton(
                onPressed: isDiscovering ? null : startDiscovery,
                child: isDiscovering
                    ? const CircularProgressIndicator(
                  valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
                )
                    : const Text('Discover Printers'),
              ),
            ),
            // List of discovered printers  
            Expanded(
              child: discoveredPrinters.isEmpty
                  ? const Center(child: Text('No printers found.'))
                  : ListView.builder(
                itemCount: discoveredPrinters.length,
                itemBuilder: (context, index) {
                  final printer = discoveredPrinters[index];
                  return PrinterTile(
                    printer: printer,
                    onConnect: () =>
                        connectToPrinter(
                            printer["identifier"], printer["interface"]),
                    onPrint: () {
                      _dollarStarxpandPlugin.printReceipt(
                        printer["identifier"],
                        printer["interface"],
                        printData,
                      );
                    },
                    onOpenDrawer: () =>
                        _dollarStarxpandPlugin.openCashDrawer(
                          printer["identifier"],
                          printer["interface"],
                        ),
                    onDisconnect:
                    _dollarStarxpandPlugin.disconnectPrinter,
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class PrinterTile extends StatelessWidget {
  final Map<String, dynamic> printer;
  final VoidCallback onConnect;
  final VoidCallback onPrint;
  final VoidCallback onOpenDrawer;
  final VoidCallback onDisconnect;

  const PrinterTile({
    super.key,
    required this.printer,
    required this.onConnect,
    required this.onPrint,
    required this.onOpenDrawer,
    required this.onDisconnect,
  });

  @override
  Widget build(BuildContext context) {
    return ListTile(
      leading: const Icon(Icons.print),
      title: Text('${printer["model"]}: ${printer["interface"]}'),
      subtitle: Text(printer["identifier"]),
      trailing: SizedBox(
        width: 180,
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            IconButton(
                icon: const Icon(Icons.print, size: 40.0), onPressed: onPrint),
            IconButton(
                icon: const Icon(Icons.dashboard, size: 40.0),
                onPressed: onOpenDrawer),
            IconButton(
                icon: const Icon(Icons.close, size: 40.0),
                onPressed: onDisconnect),
          ],
        ),
      ),
      onTap: onConnect,
    );
  }
}

final printData = {
  "id": "12345",
  "status": "completed",
  "date": "DEC 17, 2024 12:31 AM",
  "registerNo": "1",
  "employee": {"id": 1, "firstName": "John", "lastName": "Doe"},
  "subTotal": 100.0,
  "tax": 10.0,
  "taxBreakdown": [
    {"name": "State Tax", "value": 6.0},
    {"name": "County Tax", "value": 4.0}
  ],
  "surcharge": 2.0,
  "discount": 5.0,
  "total": 107.0,
  "changeDue": 0.0,
  "tenders": [
    {
      "method": "Card",
      "amount": 107.0,
      "details": {
        "authCode": "123456",
        "referenceId": "ABC123",
        "amount": 107.0,
        "cardData": {
          "cardType": "VISA",
          "entryType": "Contactless",
          "last4": "5678",
          "expirationDate": "12/24",
          "name": "John Doe"
        }
      },
    }
  ],
  "store": {
    "name": "Store Name",
    "address": "123 Main St",
    "phone": "123-456-7890"
  },
  "items": [
    {
      "item": {
        "name": "Product 1",
        "upc": "123456789",
        "pack": "Box",
        "size": "500g",
        "price": 10.0
      },
      "qty": "1",
      "price": 10.0,
      "taxable": true
    },
    {
      "item": {
        "name": "Product 2",
        "upc": "987654321",
        "pack": "Pack",
        "size": "1kg",
        "price": 20.0
      },
      "qty": "2",
      "price": 40.0,
      "taxable": false
    }
  ]
};

Troubleshooting

Common Issues

  1. Printer Not Found: Ensure the printer is powered on and discoverable.
  2. Connection Failed: Verify the identifier and interface match the discovered printer details.

License

This plugin is licensed under the MIT License.