printing_ffi 0.0.1 copy "printing_ffi: ^0.0.1" to clipboard
printing_ffi: ^0.0.1 copied to clipboard

A Flutter plugin for direct printer communication using native FFI bindings for macOS, Windows, and Linux.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:printing_ffi/printing_ffi.dart';
import 'dart:typed_data';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Printing Plugin Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
        cardTheme: CardThemeData(
          elevation: 2,
          margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
        ),
        elevatedButtonTheme: ElevatedButtonThemeData(
          style: ElevatedButton.styleFrom(
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(8),
            ),
          ),
        ),
      ),
      home: const PrinterScreen(),
    );
  }
}

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

  @override
  State<PrinterScreen> createState() => _PrinterScreenState();
}

class _PrinterScreenState extends State<PrinterScreen> {
  Printer? selectedPrinter;
  List<PrintJob> jobs = [];
  List<Printer> printers = [];
  bool isLoading = false;

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

  Future<void> _loadPrinters() async {
    setState(() => isLoading = true);
    try {
      final printerList = listPrinters();
      setState(() {
        printers = printerList;
        selectedPrinter = printers.isNotEmpty ? printers.first : null;
        isLoading = false;
        if (selectedPrinter != null) {
          _loadJobs();
        } else {
          jobs = [];
        }
      });
    } catch (e) {
      setState(() => isLoading = false);
      _showSnackBar('Error loading printers: $e', isError: true);
    }
  }

  Future<void> _loadJobs() async {
    if (selectedPrinter == null) return;
    setState(() => isLoading = true);
    try {
      final jobList = await listPrintJobs(selectedPrinter!.name);
      setState(() {
        jobs = jobList;
        isLoading = false;
      });
    } catch (e) {
      setState(() => isLoading = false);
      _showSnackBar('Error loading print jobs: $e', isError: true);
    }
  }

  Future<void> _printTest() async {
    if (selectedPrinter == null) {
      _showSnackBar('No printer selected', isError: true);
      return;
    }
    if (!selectedPrinter!.isAvailable) {
      _showSnackBar('Cannot print: Printer is offline', isError: true);
      return;
    }
    setState(() => isLoading = true);
    try {
      final rawData = Uint8List.fromList([
        0x1B,
        0x40,
        0x48,
        0x65,
        0x6C,
        0x6C,
        0x6F,
        0x0A,
      ]); // "Hello\n" in ASCII
      final success = await rawDataToPrinter(selectedPrinter!.name, rawData);
      setState(() => isLoading = false);
      _showSnackBar(
        success ? 'Print job sent successfully' : 'Failed to send print job',
        isError: !success,
      );
      await _loadJobs();
    } catch (e) {
      setState(() => isLoading = false);
      _showSnackBar('Error printing: $e', isError: true);
    }
  }

  Future<void> _pausePrintJob(int jobId) async {
    if (selectedPrinter == null) return;
    setState(() => isLoading = true);
    try {
      final success = await pausePrintJob(selectedPrinter!.name, jobId);
      setState(() => isLoading = false);
      _showSnackBar(
        success ? 'Job paused successfully' : 'Failed to pause job',
        isError: !success,
      );
      await _loadJobs();
    } catch (e) {
      setState(() => isLoading = false);
      _showSnackBar('Error pausing job: $e', isError: true);
    }
  }

  Future<void> _resumePrintJob(int jobId) async {
    if (selectedPrinter == null) return;
    setState(() => isLoading = true);
    try {
      final success = await resumePrintJob(selectedPrinter!.name, jobId);
      setState(() => isLoading = false);
      _showSnackBar(
        success ? 'Job resumed successfully' : 'Failed to resume job',
        isError: !success,
      );
      await _loadJobs();
    } catch (e) {
      setState(() => isLoading = false);
      _showSnackBar('Error resuming job: $e', isError: true);
    }
  }

  Future<void> _cancelPrintJob(int jobId) async {
    if (selectedPrinter == null) return;
    setState(() => isLoading = true);
    try {
      final success = await cancelPrintJob(selectedPrinter!.name, jobId);
      setState(() => isLoading = false);
      _showSnackBar(
        success ? 'Job canceled successfully' : 'Failed to cancel job',
        isError: !success,
      );
      await _loadJobs();
    } catch (e) {
      setState(() => isLoading = false);
      _showSnackBar('Error canceling job: $e', isError: true);
    }
  }

  void _showSnackBar(String message, {bool isError = false}) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: isError ? Colors.red : Colors.green,
        duration: const Duration(seconds: 3),
      ),
    );
  }

  void _selectDefaultPrinter() {
    final defaultPrinter = getDefaultPrinter();
    if (defaultPrinter != null) {
      // Find the corresponding printer object in our state list to ensure
      // we're using the same instance for the DropdownButton.
      final printerInList = printers.cast<Printer?>().firstWhere(
        (p) => p!.name == defaultPrinter.name,
        orElse: () => null,
      );

      if (printerInList != null) {
        setState(() {
          selectedPrinter = printerInList;
          _loadJobs();
          _showSnackBar('Selected default printer: ${printerInList.name}');
        });
      } else {
        _showSnackBar(
          'Default printer "${defaultPrinter.name}" not in list. Try refreshing.',
          isError: true,
        );
      }
    } else {
      _showSnackBar('No default printer found.', isError: true);
    }
  }

  Widget _buildDetailRow(String label, String? value) {
    if (value == null || value.isEmpty) {
      return const SizedBox.shrink();
    }
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4.0),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text('$label: ', style: const TextStyle(fontWeight: FontWeight.bold)),
          Expanded(child: Text(value)),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                const Text(
                  'Select Printer: ',
                  style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                ),
                const SizedBox(width: 8),
                DropdownButton<Printer>(
                  value: selectedPrinter,
                  hint: const Text('No printers found'),
                  items: printers.map((printer) {
                    return DropdownMenuItem<Printer>(
                      value: printer,
                      child: Text(
                        '${printer.name} ${!printer.isAvailable ? '(Offline)' : ''}',
                      ),
                    );
                  }).toList(),
                  onChanged: (value) {
                    setState(() {
                      selectedPrinter = value;
                      if (value != null) {
                        _loadJobs();
                      } else {
                        jobs = [];
                      }
                    });
                  },
                ),
                const Spacer(),
                IconButton(
                  icon: const Icon(Icons.refresh),
                  tooltip: 'Refresh Printers',
                  onPressed: _loadPrinters,
                ),
              ],
            ),
            if (selectedPrinter != null)
              Card(
                margin: const EdgeInsets.only(top: 16),
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'Printer Details',
                        style: Theme.of(context).textTheme.titleMedium,
                      ),
                      const Divider(height: 16),
                      _buildDetailRow('Model', selectedPrinter!.model),
                      _buildDetailRow('Location', selectedPrinter!.location),
                      _buildDetailRow('Comment', selectedPrinter!.comment),
                      _buildDetailRow('URL', selectedPrinter!.url),
                      _buildDetailRow(
                        'Default',
                        selectedPrinter!.isDefault.toString(),
                      ),
                    ],
                  ),
                ),
              ),
            const SizedBox(height: 16),
            Wrap(
              spacing: 8,
              children: [
                ElevatedButton.icon(
                  icon: const Icon(Icons.print),
                  label: const Text('Print Test'),
                  onPressed: isLoading || selectedPrinter == null
                      ? null
                      : _printTest,
                ),
                ElevatedButton.icon(
                  icon: const Icon(Icons.refresh),
                  label: const Text('Refresh Jobs'),
                  onPressed: isLoading || selectedPrinter == null
                      ? null
                      : _loadJobs,
                ),
                ElevatedButton.icon(
                  icon: const Icon(Icons.star_border),
                  label: const Text('Select Default'),
                  onPressed: isLoading ? null : _selectDefaultPrinter,
                ),
              ],
            ),
            const SizedBox(height: 16),
            if (isLoading)
              const Center(child: CircularProgressIndicator())
            else if (printers.isEmpty)
              const Expanded(
                child: Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Icon(Icons.print_disabled, size: 64, color: Colors.grey),
                      SizedBox(height: 16),
                      Text(
                        'No printers found',
                        style: TextStyle(fontSize: 18, color: Colors.grey),
                      ),
                      Text(
                        'Please connect a printer and refresh',
                        style: TextStyle(color: Colors.grey),
                      ),
                    ],
                  ),
                ),
              )
            else if (jobs.isEmpty)
              const Expanded(
                child: Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Icon(Icons.description, size: 64, color: Colors.grey),
                      SizedBox(height: 16),
                      Text(
                        'No print jobs found',
                        style: TextStyle(fontSize: 18, color: Colors.grey),
                      ),
                      Text(
                        'Try printing a test document',
                        style: TextStyle(color: Colors.grey),
                      ),
                    ],
                  ),
                ),
              )
            else
              Expanded(
                child: ListView.builder(
                  itemCount: jobs.length,
                  itemBuilder: (context, index) {
                    final job = jobs[index];
                    return Card(
                      child: ListTile(
                        title: Text(
                          job.title,
                          style: const TextStyle(fontWeight: FontWeight.bold),
                        ),
                        subtitle: Text(
                          'ID: ${job.id}, Status: ${job.statusDescription}',
                        ),
                        trailing: Row(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            IconButton(
                              icon: const Icon(Icons.pause, color: Colors.blue),
                              tooltip: 'Pause Job',
                              onPressed: isLoading
                                  ? null
                                  : () => _pausePrintJob(job.id),
                            ),
                            IconButton(
                              icon: const Icon(
                                Icons.play_arrow,
                                color: Colors.green,
                              ),
                              tooltip: 'Resume Job',
                              onPressed: isLoading
                                  ? null
                                  : () => _resumePrintJob(job.id),
                            ),
                            IconButton(
                              icon: const Icon(Icons.cancel, color: Colors.red),
                              tooltip: 'Cancel Job',
                              onPressed: isLoading
                                  ? null
                                  : () => _cancelPrintJob(job.id),
                            ),
                          ],
                        ),
                      ),
                    );
                  },
                ),
              ),
          ],
        ),
      ),
    );
  }
}
11
likes
0
points
721
downloads

Publisher

verified publishershreeman.dev

Weekly Downloads

A Flutter plugin for direct printer communication using native FFI bindings for macOS, Windows, and Linux.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

ffi, flutter, plugin_platform_interface

More

Packages that depend on printing_ffi

Packages that implement printing_ffi