nitro_printing 0.0.1
nitro_printing: ^0.0.1 copied to clipboard
A high-performance Flutter printing plugin built on NitroModules (JSI/FFI). Supports text, image, PDF, raw/ESC-POS/ZPL printing, print-job management, mDNS printer discovery, IPP status queries, and a [...]
nitro_printing #
A high-performance Flutter printing plugin built on top of the Nitrogen SDK — a Flutter port of Nitro Modules for React Native that calls native code directly via Dart FFI, completely bypassing Flutter method channels.
What is Nitro / Nitrogen? #
Nitro Modules is a React Native library by Marc Rousavy (Margelo)
that replaces the JS bridge with zero-overhead JSI bindings. The Nitrogen SDK
(nitro · nitro_generator · nitrogen_cli)
is a Flutter port of that same concept — it replaces method channels with pure
Dart FFI (C ABI) calls, using dart:ffi and a code-generation pipeline
(build_runner + nitro_generator) that turns a single annotated Dart file into
type-safe Dart ↔ Kotlin / Swift / C++ bridge code.
nitro ecosystem (Flutter port of React Native Nitro Modules)
├── nitro ← runtime: HybridObject, annotations, NitroRuntime
├── nitro_annotations ← @NitroModule, @HybridStruct, @HybridEnum, @nitroAsync, @NitroStream
├── nitro_generator ← build_runner builder → generates *.g.dart + native stubs
└── nitrogen_cli ← CLI: scaffold plugins, run generator, doctor
Repository: https://github.com/Shreemanarjun/nitro_ecosystem
Why nitro_printing vs. existing packages? #
| Feature | printing (pub.dev) |
nitro_printing |
|---|---|---|
| Bridge | Method channels (async serialize/deserialize) | Nitrogen JSI/FFI — zero serialization overhead |
| Sync printer queries | ❌ always async | ✅ getPrintersCount(), getPrinterAt(), getDefaultPrinter() are synchronous |
| Raw TCP / ESC-POS / ZPL | ❌ not supported | ✅ printRaw, printEscPos, printZpl |
| mDNS/Bonjour discovery | ❌ | ✅ startPrinterDiscovery + onPrinterDiscovered stream |
| IPP detailed status | ❌ | ✅ getPrinterStatusDetail (ink, paper jam, toner, …) |
| Real-time job streams | ❌ polling only | ✅ onPrintJobChanged, onPrinterStatusChanged (zero-copy streams) |
| Print-to-file (virtual) | Limited | ✅ printToFile, renderPreview |
| Built-in settings UI | ❌ | ✅ NitroPrintSettingsPage — Material 3 full-screen editor |
| Batch printing | ❌ | ✅ printBatch extension |
| Platforms | Android, iOS, macOS, Web | Android, iOS, macOS, Windows, Linux |
Installation #
dependencies:
nitro_printing: ^0.0.1
flutter pub get
Required Platform Setup #
Before running your application, ensure you have configured the required permissions and system packages for your target platforms:
🤖 Android #
The plugin requires the INTERNET permission to connect to network printers (IPP, TCP raw sockets) and to run mDNS discovery.
In your app's android/app/src/main/AndroidManifest.xml, ensure the following is present:
<uses-permission android:name="android.permission.INTERNET"/>
(Note: The plugin's own manifest includes this permission, so Gradle will merge it automatically, but it is recommended to declare it in the main app.)
🍎 iOS #
The plugin uses Apple's Bonjour/mDNS framework (NetServiceBrowser) to search for network-enabled IPP printers. On iOS 14+, you must declare local network permissions and the specific Bonjour services the app will query in ios/Runner/Info.plist:
- Local Network Usage Description: A description explaining why local network access is needed.
- Bonjour Services: The list of service types (
_ipp._tcpand_ipps._tcp).
Add the following keys to your Info.plist:
<key>NSLocalNetworkUsageDescription</key>
<string>This app requires access to the local network to discover and connect to network printers.</string>
<key>NSBonjourServices</key>
<array>
<string>_ipp._tcp</string>
<string>_ipps._tcp</string>
</array>
💻 macOS #
1. Sandbox Entitlements
If your macOS app has App Sandbox enabled (default for Flutter templates), you must add the printing and network client entitlements in both macos/Runner/DebugProfile.entitlements and macos/Runner/Release.entitlements:
<!-- Allow printing operations -->
<key>com.apple.security.print</key>
<true/>
<!-- Allow outgoing network connections to TCP/IPP/mDNS printers -->
<key>com.apple.security.network.client</key>
<true/>
2. Local Network & Bonjour (macOS 11+)
If sandbox is enabled and you are performing printer discovery, declare the Bonjour service details in macos/Runner/Info.plist (similar to iOS):
<key>NSLocalNetworkUsageDescription</key>
<string>This app requires access to the local network to discover and connect to network printers.</string>
<key>NSBonjourServices</key>
<array>
<string>_ipp._tcp</string>
<string>_ipps._tcp</string>
</array>
🐧 Linux #
1. Compilation Dependencies
The Linux native implementation links against CUPS (Common UNIX Printing System). You must install the CUPS development headers and pkg-config on your build machine to compile the application:
- Ubuntu/Debian:
sudo apt-get install -y libcups2-dev pkg-config - Fedora/RHEL:
sudo dnf install cups-devel pkgconfig - Arch Linux:
sudo pacman -S cups pkgconf
2. Runtime Requirements
A running CUPS daemon (cupsd) is required on the target machine for local printer queries and job spooling. This is pre-installed and running on standard desktop Linux distributions, but may need to be installed manually on minimal or headless systems.
🪟 Windows #
No special permissions, configuration, or external dependencies are required. The plugin uses standard Win32 print spooler APIs (like WinSpool) which are built into all Windows environments.
Quick Start #
import 'package:nitro_printing/nitro_printing.dart';
final printing = NitroPrinting.instance;
// Synchronous — no await, no Isolate
if (!printing.isPrintingSupported()) return;
// Print a PDF
final pdfBytes = await loadPdfAsset();
final result = await printing.printPdf(
pdfBytes,
settings: PrintSettings(
printerId: 'My Printer',
paperSize: PaperSize.a4,
quality: PrintQuality.high,
duplex: true,
),
);
if (result.success) {
print('Job ID: ${result.jobId}');
}
API Reference #
NitroPrinting.instance #
The singleton entry point. Defined as a @NitroModule extending HybridObject —
the Nitrogen runtime creates the FFI bridge automatically via code generation.
Synchronous Queries (no await, no Isolate overhead) #
These are direct Dart FFI calls that execute in < 1 µs — safe to call on the UI thread.
bool isPrintingSupported();
int getPrintersCount();
PrinterInfo getPrinterAt(int index);
PrinterInfo getDefaultPrinter();
String getPrinterDriverVersion(String printerId);
PrinterCapabilities getPrinterCapabilities(String printerId);
Example — populate a printer list:
final count = printing.getPrintersCount();
final printers = [for (int i = 0; i < count; i++) printing.getPrinterAt(i)];
Print Operations (@nitroAsync) #
Marked @nitroAsync — dispatched on a background isolate, returns to the main isolate.
Future<PrintResult> printText(String text, {PrintSettings? settings});
Future<PrintResult> printImage(Uint8List imageData, {PrintSettings? settings});
Future<PrintResult> printPdf(Uint8List pdfData, {PrintSettings? settings});
Future<PrintResult> printDocument(PrintDocument document, {PrintSettings? settings});
Future<bool> printFile(String filePath, {PrintSettings? settings});
PrintResult contains success, jobId, errorMessage, errorCode.
Export / Virtual Print #
/// Render a document to PDF bytes without sending to a printer (for previews).
Future<PreviewResult> renderPreview(PrintDocument document, {PrintSettings? settings});
/// Count how many pages a document will produce.
Future<int> getPageCount(PrintDocument document);
/// Write a rendered PDF to disk (virtual / file print).
Future<bool> printToFile(PrintDocument document, String outputPath, {PrintSettings? settings});
Raw Protocol Printing #
Direct TCP socket output — no OS print dialog, no driver needed.
/// Send raw bytes to a printer on port 9100 or via IPP.
Future<PrintResult> printRaw(Uint8List data, {PrintSettings? settings});
/// ESC/POS thermal receipt printers (socket://host:port).
Future<PrintResult> printEscPos(Uint8List escPosData, {PrintSettings? settings});
/// ZPL label printers (Zebra, socket://host:9100).
Future<PrintResult> printZpl(String zpl, {PrintSettings? settings});
/// Cancel any in-progress raw/ESC-POS/ZPL network job.
Future<bool> cancelRawPrint();
Set PrintSettings.printerId to an IP address or URI (socket://192.168.1.5:9100, ipp://...).
Print Job Management #
Future<bool> cancelPrintJob(String jobId);
Future<bool> pausePrintJob(String jobId);
Future<bool> resumePrintJob(String jobId);
Future<bool> clearPrintQueue();
Future<int> getPrintJobsCount();
Future<PrintJob> getPrintJobAt(int index);
Future<PrintJob> getPrintJobStatus(String jobId);
Printer Discovery (mDNS/Bonjour) #
Future<bool> startPrinterDiscovery();
Future<bool> stopPrinterDiscovery();
// @NitroStream — zero-copy reactive stream of discovered IPP printers
Stream<DiscoveredPrinter> onPrinterDiscovered();
Example:
final sub = printing.onPrinterDiscovered().listen((p) {
print('Found: ${p.name} at ${p.uri}');
});
await printing.startPrinterDiscovery();
// ... later:
await printing.stopPrinterDiscovery();
await sub.cancel();
Connection & Administration #
/// TCP probe — check if a printer is reachable.
Future<bool> testPrinterConnection(String printerId, {int? timeoutSeconds});
/// Set the system-default printer (macOS / Windows only).
Future<bool> setDefaultPrinter(String printerId);
Platform UX #
/// Open the OS print queue window for a printer (empty string = all printers).
Future<bool> openSystemPrintQueue(String printerId);
/// Open OS printer-properties dialog (macOS / Windows only).
Future<bool> openPrinterProperties(String printerId);
Detailed IPP Status #
/// Query live printer status via IPP Get-Printer-Attributes.
Future<PrinterStatusDetail> getPrinterStatusDetail(
String printerId, {
int? timeoutSeconds,
});
PrinterStatusDetail exposes: isOnline, isReady, hasPaperJam, isOutOfPaper,
isOutOfInk, per-channel ink levels (inkLevelBlack/Cyan/Magenta/Yellow), tonerLevel,
paperLevel, jobsInQueue, printerState, stateReasons, and more.
Real-Time Streams (@NitroStream) #
All streams are annotated with @NitroStream(backpressure: Backpressure.dropLatest) —
the Nitrogen runtime uses Dart_PostCObject to push events from the native thread directly
to Dart with no method-channel round-trip.
Stream<PrintJobUpdate> onPrintJobChanged(); // job state + progress
Stream<PrinterStatus> onPrinterStatusChanged(); // online/offline, ink, queue depth
Stream<DiscoveredPrinter> onPrinterDiscovered(); // mDNS discovery events
Batch Printing (Dart-side orchestration) #
final results = await printing.printBatch(
[doc1, doc2, doc3],
stopOnError: true,
settings: PrintSettings(copies: 2),
);
Built-in Print Settings UI #
A Material 3 full-screen settings editor — no extra dependency needed.
final settings = await NitroPrintSettingsPage.show(context);
if (settings != null) {
await printing.printPdf(pdfBytes, settings: settings);
}
The page exposes: printer picker, show-dialog toggle, job name, paper size (including custom pt dimensions), orientation, copies stepper, pages-per-sheet, page range, fit-to-page, quality, media type, color/duplex/collate toggles, header/footer text, and input tray.
Data Models #
PrintSettings #
| Field | Type | Default | Description |
|---|---|---|---|
printerId |
String |
'' |
Printer ID / IP / URI |
paperSize |
PaperSize |
.a4 |
a4, a5, letter, legal, custom |
orientationDegrees |
double |
0.0 |
0=portrait, 90=landscape, 180/270=reverse |
quality |
PrintQuality |
.normal |
draft, normal, high, best |
copies |
int |
1 |
Number of copies |
collate |
bool |
false |
Collate multi-copy jobs |
duplex |
bool |
false |
Double-sided |
color |
bool |
true |
Color vs grayscale |
marginTop/Bottom/Left/Right |
double |
0 |
Margins in PostScript points |
jobName |
String |
'' |
Spooler job name |
pagesPerSheet |
int |
1 |
1, 2, 4, 6, 8, 16 |
showPrintDialog |
bool |
true |
false = silent direct print |
pageRangeFrom/To |
int |
0 |
1-based; 0 = start/end |
customPaperWidth/Height |
double |
0 |
Points, when paperSize == .custom |
fitToPage |
bool |
false |
Scale to printable area |
mediaType |
MediaType |
.plain |
plain, glossy, matte, photo, label, envelope |
headerText / footerText |
String |
'' |
Per-page header/footer |
inputTray |
String |
'' |
Tray name, e.g. "Tray 1" |
networkTimeoutSeconds |
int |
30 |
TCP/IPP timeout |
Other Models #
| Model | Key Fields |
|---|---|
PrinterInfo |
id, name, address, isDefault, isAvailable |
PrinterCapabilities |
color, duplex, copy count, paper sizes, quality levels, max DPI, borderless, input trays |
PrintJob |
id, printerId, documentTitle, state (PrintState), progress (0–100), createdAt, completedAt, errorMessage, pagesPrinted |
PrintDocument |
id, title, type (DocumentType: plainText/html/pdf/image), data (Uint8List) |
DiscoveredPrinter |
id, name, host, port, serviceType (e.g. _ipp._tcp), uri, isAvailable |
PrinterStatusDetail |
isOnline, isReady, hasPaperJam, isOutOfPaper, ink levels per channel, tonerLevel, paperLevel, stateReasons |
Platform Support #
| Platform | Discovery | Raw/ESC-POS/ZPL | System Dialog | |
|---|---|---|---|---|
| Android | ✅ | ✅ | ✅ | ✅ (Print Services) |
| iOS | ✅ | ✅ | ✅ | ✅ (AirPrint) |
| macOS | ✅ | ✅ | ✅ | ✅ |
| Windows | ✅ | ✅ | ✅ | ✅ |
| Linux | ✅ | ✅ | ✅ | ✅ (CUPS) |
| Web | ❌ | ❌ | ❌ | ❌ |
How It's Built — Nitrogen SDK #
nitro_printing is powered by the Nitrogen SDK, a Flutter port of
React Native Nitro Modules originally by Marc Rousavy (Margelo).
Architecture #
nitro_printing (this plugin)
└── depends on: nitro ^0.4.3
└── nitro_annotations ^0.4.3
dev: nitro_generator ^0.4.3 (build_runner builder)
nitro_ecosystem/packages/ ← local source of the SDK
├── nitro/ ← runtime: HybridObject, NitroRuntime, NitroConfig
├── nitro_annotations/ ← @NitroModule, @HybridStruct, @HybridEnum, @nitroAsync, @NitroStream, @ZeroCopy
├── nitro_generator/ ← build_runner builder → *.g.dart + native stubs
└── nitrogen_cli/ ← CLI (nitrogen generate / init / doctor)
Key differences vs. method channels #
| Concern | Method channel | Nitrogen |
|---|---|---|
| Per-call overhead | ~0.3 ms (serialize → platform thread → deserialize) | ~0 µs (direct C ABI via dart:ffi) |
| Sync calls | ❌ impossible | ✅ any non-@nitroAsync method |
| Streams | EventChannel + serialized JSON | @NitroStream → Dart_PostCObject direct push |
| Large buffers | Copied twice (platform → Dart) | @ZeroCopy → pointer handoff, zero copies |
| Code to write | Dart + platform channel + boilerplate | 1 .native.dart file + generated everything |
How code generation works #
- You write
lib/src/nitro_printing.native.dart— a single abstract Dart class annotated with@NitroModule. - Running
dart run build_runner buildinvokesnitro_generator, which outputs:lib/src/nitro_printing.g.dart— the Dart FFI bridge (_NitroPrintingImpl)- Native stub headers for Kotlin (Android), Swift (iOS/macOS), C++ (Windows/Linux)
- Platform implementations (
NitroPrintingPlugin.kt,SwiftNitroPrintingPlugin.swift, etc.) fill in the business logic against the generated spec.
Development #
Regenerate the Nitrogen glue code after modifying the Dart API spec:
dart run build_runner build --delete-conflicting-outputs
Run the example app:
cd example
flutter run
Related #
- nitro — Nitrogen runtime (Flutter port of RN Nitro Modules)
- nitro_generator —
build_runnercode generator - nitrogen_cli — CLI scaffold & doctor tool
- nitro_ecosystem repository — source of the SDK
- Nitro Modules (React Native) — the original RN library this is ported from
License #
MIT — see LICENSE.