mf_bluetooth_printer 1.0.5
mf_bluetooth_printer: ^1.0.5 copied to clipboard
A Flutter package for MF Bluetooth Printer — print receipts to Bluetooth thermal printers via ESC/POS.
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart' as fbp;
import 'package:mf_bluetooth_printer/mf_bluetooth_printer.dart';
import 'package:permission_handler/permission_handler.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Bluetooth Thermal Printer Example',
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF1565C0),
brightness: Brightness.light,
),
cardTheme: CardThemeData(
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
margin: EdgeInsets.zero,
clipBehavior: Clip.antiAlias,
),
),
home: const PrinterExamplePage(),
);
}
}
class PrinterExamplePage extends StatefulWidget {
const PrinterExamplePage({super.key});
@override
State<PrinterExamplePage> createState() => _PrinterExamplePageState();
}
class _PrinterExamplePageState extends State<PrinterExamplePage> {
final BluetoothPrinterService _printer = BluetoothPrinterService();
@override
void dispose() {
_printer.dispose();
super.dispose();
}
Future<void> _scan() async {
try {
await _printer.startScan();
} catch (e) {
if (mounted) {
final msg = e.toString().replaceFirst('Exception: ', '');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(msg),
behavior: SnackBarBehavior.floating,
action: SnackBarAction(
label: 'Settings',
onPressed: () => openAppSettings(),
),
),
);
}
}
}
Future<void> _connect(fbp.BluetoothDevice device) async {
try {
await _printer.connectToDevice(device);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Connected to printer'),
behavior: SnackBarBehavior.floating,
backgroundColor: Colors.green.shade700,
),
);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Connect error: $e'),
behavior: SnackBarBehavior.floating,
backgroundColor: Colors.red.shade700,
),
);
}
}
}
Future<void> _printTest() async {
final receipt = ReceiptData(
companyInfo: const CompanyInfo(
name: 'Example Co',
address1: '123 Street',
phone: '1234567890',
taxNo: 'TRN123',
email: 'sales@example.com',
),
salesmanName: 'John',
routeName: 'Route A',
invoiceNumber: 'INV-001',
dateTime: DateTime.now(),
customerPhone: '9876543210',
paymentMethod: 'cash',
customerName: 'Customer Name',
customerAddress: 'Address',
customerCode: 'C001',
customerTRN: 'TRN456',
items: [
ReceiptItem(
productCode: 'P1',
name: 'Product One',
quantity: 2,
price: 10.0,
netTotal: 20.0,
vatAmount: 1.0,
total: 21.0,
),
],
totalBeforeTax: 20.0,
taxAmount: 1.0,
roundOff: 0.0,
totalAmount: 21.0,
);
try {
await _printer.printReceipt(
receipt,
printerSize: PrinterSize.threeInch,
design: const PrinterDesignSettings(),
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Receipt sent to printer'),
behavior: SnackBarBehavior.floating,
backgroundColor: Colors.green.shade700,
),
);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Print error: $e'),
behavior: SnackBarBehavior.floating,
backgroundColor: Colors.red.shade700,
),
);
}
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('Bluetooth Thermal Printer'),
centerTitle: true,
elevation: 0,
scrolledUnderElevation: 2,
),
body: ListView(
padding: const EdgeInsets.all(20),
children: [
ValueListenableBuilder<bool>(
valueListenable: _printer.isConnected,
builder: (_, connected, __) => Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: connected
? Colors.green.withOpacity(0.15)
: theme.colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12),
),
child: Icon(
connected ? Icons.print : Icons.print_disabled,
size: 28,
color: connected
? Colors.green.shade700
: theme.colorScheme.onSurfaceVariant,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
connected ? 'Printer connected' : 'No printer',
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 2),
Text(
connected
? 'Ready to print receipts'
: 'Scan and tap a device to connect',
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
),
),
],
),
const SizedBox(height: 20),
FilledButton.icon(
onPressed: _printer.isScanning.value ? null : _scan,
icon: _printer.isScanning.value
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: theme.colorScheme.onPrimary,
),
)
: const Icon(Icons.search, size: 20),
label: Text(
_printer.isScanning.value ? 'Scanning...' : 'Scan for printers',
),
style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
),
),
if (connected) ...[
const SizedBox(height: 12),
FilledButton.tonalIcon(
onPressed: _printTest,
icon: const Icon(Icons.receipt_long, size: 20),
label: const Text('Print test receipt'),
style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
),
),
],
],
),
),
),
),
const SizedBox(height: 28),
ValueListenableBuilder<List<fbp.ScanResult>>(
valueListenable: _printer.scanResults,
builder: (_, results, __) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 4, bottom: 8),
child: Text(
'Available devices',
style: theme.textTheme.titleSmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w600,
),
),
),
if (results.isEmpty)
Card(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 24,
horizontal: 20,
),
child: Row(
children: [
Icon(
Icons.bluetooth_searching,
size: 40,
color: theme.colorScheme.outline,
),
const SizedBox(width: 16),
Expanded(
child: Text(
'No devices found. Tap "Scan for printers" above.',
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
),
],
),
),
)
else
...results.map((r) {
final name = r.device.platformName.isNotEmpty
? r.device.platformName
: 'Unknown device';
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 4,
),
leading: CircleAvatar(
backgroundColor: theme.colorScheme.primaryContainer,
child: Icon(
Icons.print,
color: theme.colorScheme.onPrimaryContainer,
),
),
title: Text(
name,
style: const TextStyle(fontWeight: FontWeight.w500),
),
subtitle: Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
'${r.device.remoteId.str} · RSSI ${r.rssi}',
style: theme.textTheme.bodySmall,
),
),
trailing: const Icon(Icons.chevron_right),
onTap: () => _connect(r.device),
),
);
}),
],
);
},
),
],
),
);
}
}