@rejaul/react-native-printer-kit 0.3.4 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # react-native-printer-kit
2
2
 
3
- A professional-grade React Native module for printing directly to Bluetooth POS/thermal (ESC/POS) printers on **Android devices only** — no third-party printer app, no OS print dialog. Print raw text, images, PDFs, and full HTML/CSS content, including **Bangla and other non-Latin scripts** that ESC/POS printer fonts don't support natively.
3
+ A professional-grade React Native module for printing directly to Bluetooth, Wi-Fi/LAN and USB POS/thermal (ESC/POS) printers on **Android devices only** — no third-party printer app, no OS print dialog. Print raw text, images, PDFs, and full HTML/CSS content, including **Bangla and other non-Latin scripts** that ESC/POS printer fonts don't support natively.
4
4
 
5
- > **Important:** This library is **exclusively for Android**. Bluetooth Classic SPP + ESC/POS is an Android-specific integration; iOS does not expose the APIs this relies on.
5
+ > **Important:** This library is **exclusively for Android**. Bluetooth Classic SPP, USB host and ESC/POS are Android-specific integrations; iOS does not expose the APIs this relies on.
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/@rejaul/react-native-printer-kit.svg)](https://www.npmjs.com/package/@rejaul/react-native-printer-kit)
8
8
  [![license](https://img.shields.io/npm/l/@rejaul/react-native-printer-kit.svg)](LICENSE)
@@ -18,7 +18,11 @@ It's a thin React Native bridge over [`android-printer-kit`](https://github.com/
18
18
 
19
19
  ## Features
20
20
 
21
- - **Direct Bluetooth Classic (SPP) connection** to any paired ESC/POS printer.
21
+ - **Three connection types** behind one API: Bluetooth Classic (SPP), Wi-Fi/LAN (raw TCP) and USB (OTG). Every print function is the same for all three.
22
+ - **Printer-only device list**: `getBondedBluetoothPrinters({ filter: 'LIKELY_PRINTERS' })` hides headphones, watches and other non-printers.
23
+ - **Printer checks**: `isPrinterReady()`, `getPrinterStatus()` (online / paper out / cover / error) and `printTestPage()`.
24
+ - **Save files** (e.g. the PDF from `htmlToPdf`) to Downloads, Documents, app storage or a chosen folder with `saveFile()`.
25
+ - **Honest connection notification**: if the printer drops (switched off, out of range, Bluetooth off, USB unplugged) the ongoing "connected" notification is replaced by a dismissible "disconnected" one.
22
26
  - **HTML → PDF → print** pipeline, so you can print an actual styled receipt/report.
23
27
  - **PDF → image → print** pipeline via Android's built-in `PdfRenderer`.
24
28
  - **Non-Latin text support** (Bangla and others) via image-based rendering.
@@ -36,7 +40,15 @@ It's a thin React Native bridge over [`android-printer-kit`](https://github.com/
36
40
 
37
41
  ## Scope
38
42
 
39
- This library speaks **Bluetooth Classic SPP + ESC/POS** — the standard used by virtually all budget Bluetooth receipt/thermal printers. It does not support Bluetooth LE printers, WiFi/network printers, USB-connected printers, or non-ESC/POS protocols (PCL/PostScript inkjet & laser printers).
43
+ This library speaks **ESC/POS** — the standard used by virtually all receipt/thermal POS printers — over:
44
+
45
+ | Connection | Notes |
46
+ |---|---|
47
+ | Bluetooth Classic (SPP) | The printer must be paired in Android's Bluetooth settings first. |
48
+ | Wi-Fi / LAN | Raw TCP, usually port 9100. Phone and printer must be on the same network; there is no discovery, so you supply the IP. |
49
+ | USB | Needs a phone with USB host (OTG) and the user's per-device permission (`requestUsbPermission`). |
50
+
51
+ Not supported: Bluetooth LE-only printers, label printers that use TSPL/ZPL/CPCL (Zebra, TSC, ...), and non-ESC/POS protocols (PCL/PostScript inkjet & laser printers).
40
52
 
41
53
  ## Minimum Requirements
42
54
 
@@ -52,7 +64,7 @@ npm install @rejaul/react-native-printer-kit
52
64
  yarn add @rejaul/react-native-printer-kit
53
65
  ```
54
66
 
55
- Nothing to configure manually — all required Bluetooth and foreground-service permissions are declared by the underlying native library's manifest and merge in automatically. You still need to request the runtime `BLUETOOTH_CONNECT` (Android 12+) and `POST_NOTIFICATIONS` (Android 13+) permissions at runtime — `requestBluetoothPermission()` handles both in one call; see [Permissions](#permissions) below.
67
+ Nothing to configure manually — all required Bluetooth, internet, USB-host and foreground-service permissions are declared by the underlying native library's manifest and merge in automatically. You still need to request the runtime `BLUETOOTH_CONNECT` (Android 12+) and `POST_NOTIFICATIONS` (Android 13+) permissions at runtime — `requestBluetoothPermission()` handles both in one call; see [Permissions](#permissions) below.
56
68
 
57
69
  ## Quick Start
58
70
 
@@ -117,11 +129,39 @@ Every function that takes data (anything beyond a bare callback) takes a single
117
129
 
118
130
  ### Connection
119
131
 
120
- #### `getBondedBluetoothPrinters(): BluetoothPrinterDevice[]`
121
- Lists Bluetooth devices already paired with the phone via Android's own Bluetooth settings.
132
+ #### `getBondedBluetoothPrinters(params?: GetBondedPrintersParams): BluetoothPrinterDevice[]`
133
+ Lists Bluetooth devices already paired with the phone via Android's own Bluetooth settings, best printer matches first. Each device has a `confidence` (`CONFIDENT` / `LIKELY` / `UNKNOWN`). Pass `{ filter: 'LIKELY_PRINTERS' }` to hide devices that do not look like printers (`'STRICT'` keeps only certain ones), or `extraNameKeywords: ['part-of-its-name']` if a real printer has an unusual name and gets hidden. With no argument every paired device is returned, as before.
134
+
135
+ #### `getUsbPrinters(): UsbPrinterDevice[]`
136
+ USB printers currently plugged in (devices exposing the USB *printer* class). Needs a phone with USB host (OTG).
137
+
138
+ #### `hasUsbPermission(params: UsbDeviceParams): boolean`
139
+ Whether the user already granted this app access to that USB printer.
140
+
141
+ #### `requestUsbPermission(params: UsbDeviceParams): Promise<boolean>`
142
+ Shows the system USB permission dialog and resolves with the user's answer. Call it before connecting. The grant is lost when the app is reinstalled.
122
143
 
123
144
  #### `connectPrinter(params: ConnectPrinterParams): Promise<boolean>`
124
- Opens an RFCOMM/SPP connection to the paired printer. Closes any existing connection first, and remembers the address for auto-reconnect on the next app launch.
145
+ Connects over Bluetooth (default), Wi-Fi/LAN or USB depending on `params.type`. Closes any existing connection first, and remembers the printer for auto-reconnect on the next app launch. Resolves `false` (does not reject) if it could not connect; for USB that includes a missing permission.
146
+
147
+ ```typescript
148
+ // Bluetooth (default) - the printer must be paired first
149
+ await connectPrinter({ address: 'AA:BB:CC:DD:EE:FF' });
150
+
151
+ // Wi-Fi / LAN
152
+ await connectPrinter({ address: '192.168.1.50', type: 'NETWORK', port: 9100 });
153
+
154
+ // USB - ask for permission first (system dialog), then connect
155
+ const usb = getUsbPrinters()[0];
156
+ if (usb && (await requestUsbPermission(usb))) {
157
+ await connectPrinter({
158
+ address: '',
159
+ type: 'USB',
160
+ vendorId: usb.vendorId,
161
+ productId: usb.productId,
162
+ });
163
+ }
164
+ ```
125
165
 
126
166
  #### `disconnectPrinter(): Promise<void>`
127
167
  Closes the current connection and forgets it for auto-reconnect.
@@ -132,6 +172,91 @@ Whether a printer connection is currently open.
132
172
  #### `getConnectedPrinter(): Promise<BluetoothPrinterDevice | null>`
133
173
  The currently connected printer, if any.
134
174
 
175
+ ### Printer checks
176
+
177
+ #### `isPrinterReady(): Promise<boolean>`
178
+ Connected **and** the printer accepts data right now (it may be switched off or out of range). It does not check paper or the cover.
179
+
180
+ #### `getPrinterStatus(params?: PrinterStatusParams): Promise<PrinterStatus>`
181
+ Asks the printer for its state. Many printers ignore the request: then `responded` is `false` and every other field is `null` (unknown, **not** "ok"). Things to know:
182
+
183
+ - Right after connecting, the first request can time out (`responded: false`); asking again a moment later works.
184
+ - What a printer reports depends on its firmware. On the USB receipt printer used for testing, an open cover was reported as `online: false, paperOut: true` and never as `coverOpen: true`. So read `paperOut: true` as "no paper *or* cover open", and do not rely on `coverOpen` unless you verified it on your printer model.
185
+ - `online: false` is the most reliable "do not print now" signal.
186
+ - A print call that resolves only means the bytes were sent. An offline or out-of-paper printer may still accept them, so check the status first when it matters.
187
+
188
+ #### `printTestPage(): Promise<void>`
189
+ Prints a short ASCII test page (connection type, printer, address, time).
190
+
191
+ ### Saving and opening files
192
+
193
+ #### `saveFile(params: SaveFileParams): Promise<SaveFileResult>`
194
+ Saves a file to `CACHE`, `APP_FILES`, public `DOWNLOADS` / `DOCUMENTS`, the public `PICTURES` folder (shows in the Gallery; images only) or a `CUSTOM_FOLDER`. Either copy an existing file with `sourcePath` (for example the PDF from `htmlToPdf`), or **pass the content directly**: `text` (UTF-8 string) or `base64` (bytes, for images/PDFs; a `data:...;base64,` prefix is fine). Give exactly one of the three; with `text` / `base64` a `fileName` is required. Never rejects: check `result.success` and `result.error`.
195
+
196
+ ```typescript
197
+ await saveFile({ text: csv, fileName: 'report.csv', subFolder: 'MyApp' });
198
+ await saveFile({ base64: pngBase64, fileName: 'qr.png', location: FileLocation.DOCUMENTS });
199
+ ``` On Android 10+ Downloads/Documents use MediaStore (no permission needed); on Android 9 and below you need `WRITE_EXTERNAL_STORAGE`. `overwrite: true` can only replace files this app created.
200
+
201
+ #### `readFile(params: ReadFileParams): Promise<ReadFileResult>`
202
+ Reads **one** file and returns its content in the shape `format` asks for. `path` is an absolute path, or the `content://` Uri that `saveFile` returned.
203
+
204
+ | `format` | `result` holds |
205
+ |---|---|
206
+ | `'base64'` (default) | `content`: plain base64, for images, PDFs and any binary file |
207
+ | `'dataUri'` | `content`: `data:<type>;base64,...` (type from the file extension), ready for an `<Image>`/`<img>` or an upload |
208
+ | `'text'` | `content`: the file decoded as text (UTF-8, or `encoding: 'UTF-16'`, `'ISO-8859-1'`, ...). Only meaningful for text files |
209
+ | `'hex'` | `content`: lower-case hex, two digits per byte |
210
+ | `'bytes'` | `bytes`: array of numbers 0–255. Heavy, so **capped at 1 MB** |
211
+
212
+ Also returned: `sizeBytes` and `mimeType`. The whole file is loaded into memory, so a file over `maxBytes` (default **10 MB**) is refused with a clear message instead. Never rejects: check `result.success` and `result.error`. For JSON, read it as `'text'` and `JSON.parse` it.
213
+
214
+ ```typescript
215
+ const r = await readFile({ path: imagePath, format: 'base64' });
216
+ if (r.success) await upload(r.content!); // "iVBORw0KGgo..."
217
+
218
+ const pdf = await readFile({ path: pdfPath, format: 'dataUri' });
219
+ // pdf.content = "data:application/pdf;base64,JVBERi0x..."
220
+ ```
221
+
222
+ #### `htmlToImage(params: HtmlToImageParams): Promise<string | null>`
223
+ Renders HTML to **one** image file and returns its path, or `null` on failure. It is `htmlToPdf` plus one rendered page: `imageType` is `'PNG'` (default) or `'JPEG'`, `targetWidthPx` is the image width (default: twice the page width), `outputPath` chooses the full file path (folder must exist), and `pageWidthDp` / `heightDp` / `minPageHeightDp` work as in `htmlToPdf`. A **very tall page makes a very large bitmap and can run out of memory**, in which case you get `null`. For base64, pass the returned path to `readFile`.
224
+
225
+ ```typescript
226
+ const png = await htmlToImage({ html: receiptHtml, targetWidthPx: 800 });
227
+ if (png) {
228
+ const b64 = await readFile({ path: png, format: 'base64' }); // send to your server
229
+ await saveFile({ sourcePath: png, fileName: 'receipt.png', location: FileLocation.PICTURES }); // Gallery
230
+ }
231
+ ```
232
+
233
+ #### `readDir(params: ReadDirParams): Promise<ReadDirResult>`
234
+ Reads **one** folder: the files and folders directly inside it, **folders first, then A–Z**. `path` is an absolute folder path, or the `content://` Uri of a folder the user picked with the system folder picker. `result.names` is just the names; `result.entries` has `name`, `path`, `isDirectory`, `sizeBytes` (`null` for a folder) and `lastModified`. It does not go into sub-folders: call it again with an entry's `path` to go deeper, and an entry's `path` also works with `removeFile`. A path that is a file, is missing or is relative is refused. Android 10+ restricts reading public folders such as Downloads, so for those the result may say the folder cannot be read; the app's own folders and a folder picked with the system folder picker always work. Never rejects: check `result.success` and `result.error`.
235
+
236
+ ```typescript
237
+ const dir = await readDir({ path: '/data/user/0/<your.app>/files' });
238
+ if (dir.success) {
239
+ console.log(dir.names); // ['reports', 'logo.png', 'notes.txt']
240
+ const folders = dir.entries.filter((e) => e.isDirectory);
241
+ }
242
+ ```
243
+
244
+ #### `removeFile(params: RemoveFileParams): Promise<RemoveFileResult>`
245
+ Deletes **one** file: an absolute `path`, or the `content://` Uri that `saveFile` returned. It never deletes a folder, never expands a pattern, refuses a relative path, and **cannot be undone**. A missing file is reported as a failure (`"File not found"`), which you can ignore if you only wanted it gone. On Android 10+ a file in Downloads/Documents (a MediaStore Uri) can only be deleted if your app created it. Never rejects: check `result.success` and `result.error`.
246
+
247
+ ```typescript
248
+ const saved = await saveFile({ text: 'temp', fileName: 'tmp.txt' });
249
+ if (saved.success) await removeFile({ path: saved.path ?? saved.uri! });
250
+ ```
251
+
252
+ #### `openFile(params: OpenFileParams): boolean`
253
+ Opens a file with whatever installed app can handle its type (shows the "Open with" chooser if several match). Returns `false` if the file does not exist or no app can open it.
254
+
255
+ ### Bluetooth state
256
+
257
+ #### `isBluetoothEnabled(): boolean`
258
+ Whether the Bluetooth adapter itself is on. Needs no runtime permission, so check it before asking for the permission.
259
+
135
260
  ### Printing
136
261
 
137
262
  #### `printText(params: PrintTextParams): Promise<void>`
@@ -150,22 +275,171 @@ Renders one PDF page to an image file and returns its path.
150
275
  `pdfToImage` + print, in one call.
151
276
 
152
277
  #### `htmlToPdf(params: HtmlToPdfParams): Promise<string | null>`
153
- Renders HTML to a PDF file using an off-screen `WebView` and returns its path, or `null` on failure. `pageWidthDp` controls how large the content renders (like a CSS viewport width), independent of the final printed width. Leave `heightDp` unset to auto-measure the real content height (recommended).
278
+ Renders HTML to a PDF file using an off-screen `WebView` and returns its path, or `null` on failure. Pass `outputPath` (the full path including the file name, e.g. `/data/user/0/<your.app>/files/invoice.pdf`) to choose where the PDF goes; without it you get a generated name in the app's cache folder. `outputPath` must be in a folder that already exists and that your app can write to (its own files/cache folders), and an existing file is overwritten. To end up in a public folder such as Downloads, convert first and then call [`saveFile`](#savefileparams-savefileparams-promisesavefileresult), which accepts `fileName`, `location` and `subFolder`. `pageWidthDp` controls how large the content renders (like a CSS viewport width), independent of the final printed width. Leave `heightDp` unset to auto-measure the real content height (recommended).
154
279
 
155
280
  #### `printHtml(params: PrintHtmlParams): Promise<boolean>`
156
281
  Full pipeline: `htmlToPdf` → `printPdf`. This is how you print Bangla (or other non-Latin) text or any real HTML/CSS layout. Retries once (reconnect + resend) if the underlying write fails, e.g. from a "Broken pipe" when a cheap board drops the link mid-print.
157
282
 
283
+ ## Enums
284
+
285
+ The string options are also exported as constants, so you can write `PrinterFilter.LIKELY_PRINTERS` instead of a bare string (plain strings keep working too). Each name is both a runtime value and a type:
286
+
287
+ ```typescript
288
+ import {
289
+ PrinterFilter, // ALL | LIKELY_PRINTERS | STRICT
290
+ PrinterConfidence, // CONFIDENT | LIKELY | UNKNOWN
291
+ PrinterConnectionType, // BLUETOOTH | NETWORK | USB
292
+ PrinterImageType, // PNG | JPEG
293
+ FileLocation, // CACHE | APP_FILES | DOWNLOADS | DOCUMENTS | PICTURES | CUSTOM_FOLDER
294
+ ReadFileFormat, // base64 | dataUri | text | hex | bytes
295
+ } from '@rejaul/react-native-printer-kit';
296
+
297
+ getBondedBluetoothPrinters({ filter: PrinterFilter.LIKELY_PRINTERS });
298
+ connectPrinter({ address: '192.168.1.50', type: PrinterConnectionType.NETWORK });
299
+ saveFile({ sourcePath, location: FileLocation.DOWNLOADS });
300
+ ```
301
+
158
302
  ## Type Definitions
159
303
 
160
304
  ```typescript
305
+ type PrinterConfidence = 'CONFIDENT' | 'LIKELY' | 'UNKNOWN';
306
+ type PrinterFilter = 'ALL' | 'LIKELY_PRINTERS' | 'STRICT';
307
+ type PrinterConnectionType = 'BLUETOOTH' | 'NETWORK' | 'USB';
308
+
161
309
  type BluetoothPrinterDevice = {
310
+ name: string | null; // null for a Wi-Fi/LAN printer
311
+ address: string; // MAC, host, or "vendorId:productId" for USB
312
+ confidence: PrinterConfidence;
313
+ reason: string | null;
314
+ };
315
+
316
+ type GetBondedPrintersParams = {
317
+ filter?: PrinterFilter; // default 'ALL'
318
+ extraNameKeywords?: string[];
319
+ };
320
+
321
+ type UsbPrinterDevice = {
162
322
  name: string | null;
163
- address: string;
323
+ vendorId: number;
324
+ productId: number;
325
+ deviceName: string;
164
326
  };
165
327
 
328
+ type UsbDeviceParams = { vendorId: number; productId: number };
329
+
166
330
  type PrinterImageType = 'PNG' | 'JPEG';
167
331
 
168
- type ConnectPrinterParams = { address: string };
332
+ type ConnectPrinterParams = {
333
+ address: string; // Bluetooth MAC, or IP/host for NETWORK; '' for USB
334
+ type?: PrinterConnectionType; // default 'BLUETOOTH'
335
+ port?: number; // NETWORK only, default 9100
336
+ timeoutMs?: number; // NETWORK connect timeout, default 5000
337
+ vendorId?: number; // USB only
338
+ productId?: number; // USB only
339
+ notificationTitle?: string; // default 'Printer connected'
340
+ disconnectedNotificationTitle?: string; // default 'Printer disconnected'
341
+ };
342
+
343
+ type PrinterStatusParams = { timeoutMs?: number }; // default 800
344
+
345
+ // null = unknown (the printer did not report it), NOT "ok"
346
+ type PrinterStatus = {
347
+ connected: boolean;
348
+ responded: boolean;
349
+ online: boolean | null;
350
+ paperOut: boolean | null;
351
+ paperNearEnd: boolean | null;
352
+ coverOpen: boolean | null;
353
+ hasError: boolean | null;
354
+ };
355
+
356
+ type FileLocation =
357
+ | 'CACHE'
358
+ | 'APP_FILES'
359
+ | 'DOWNLOADS'
360
+ | 'DOCUMENTS'
361
+ | 'PICTURES' // public Pictures folder: shows in the Gallery. Images only.
362
+ | 'CUSTOM_FOLDER';
363
+
364
+ type SaveFileParams = {
365
+ sourcePath?: string; // an existing file ... OR text / base64 below (exactly one)
366
+ text?: string; // content as UTF-8 text; needs fileName
367
+ base64?: string; // content as base64 bytes; needs fileName
368
+ fileName?: string; // defaults to the source file's name; required with text/base64
369
+ location?: FileLocation; // default 'DOWNLOADS'
370
+ customFolder?: string; // required for CUSTOM_FOLDER
371
+ subFolder?: string;
372
+ mimeType?: string; // guessed from the extension
373
+ overwrite?: boolean;
374
+ deleteSource?: boolean;
375
+ };
376
+
377
+ type SaveFileResult = {
378
+ success: boolean;
379
+ path: string | null;
380
+ uri: string | null;
381
+ fileName: string | null;
382
+ error: string | null;
383
+ };
384
+
385
+ type ReadFileFormat = 'base64' | 'dataUri' | 'text' | 'hex' | 'bytes';
386
+
387
+ type ReadFileParams = {
388
+ path: string; // absolute path, or a content:// Uri
389
+ format?: ReadFileFormat; // default 'base64'
390
+ encoding?: string; // 'text' only; default 'UTF-8'
391
+ maxBytes?: number; // default 10 MB; 'bytes' is always capped at 1 MB
392
+ };
393
+
394
+ type ReadFileResult = {
395
+ success: boolean;
396
+ format: ReadFileFormat | null;
397
+ content: string | null; // base64 / dataUri / text / hex
398
+ bytes: number[] | null; // bytes
399
+ sizeBytes: number | null;
400
+ mimeType: string | null;
401
+ error: string | null;
402
+ };
403
+
404
+ type HtmlToImageParams = {
405
+ html: string;
406
+ outputPath?: string; // full path incl. file name; default: generated name in the cache folder
407
+ imageType?: PrinterImageType; // default 'PNG'
408
+ targetWidthPx?: number; // default: twice the page width
409
+ pageWidthDp?: number;
410
+ heightDp?: number;
411
+ minPageHeightDp?: number;
412
+ };
413
+
414
+ type ReadDirParams = { path: string }; // absolute folder path, or a folder content:// Uri
415
+
416
+ type DirEntry = {
417
+ name: string;
418
+ path: string; // works with removeFile (a file) or readDir (a folder)
419
+ isDirectory: boolean;
420
+ sizeBytes: number | null; // null for a folder
421
+ lastModified: number | null; // ms since 1970
422
+ };
423
+
424
+ type ReadDirResult = {
425
+ success: boolean;
426
+ path: string | null;
427
+ names: string[];
428
+ entries: DirEntry[];
429
+ error: string | null;
430
+ };
431
+
432
+ type RemoveFileParams = { path: string }; // absolute path, or a content:// Uri from saveFile
433
+
434
+ type RemoveFileResult = {
435
+ success: boolean;
436
+ path: string | null;
437
+ uri: string | null;
438
+ fileName: string | null;
439
+ error: string | null;
440
+ };
441
+
442
+ type OpenFileParams = { path: string; mimeType?: string };
169
443
 
170
444
  type PrintTextParams = { text: string; feedLines?: number };
171
445
 
@@ -203,6 +477,7 @@ type PrintPdfParams = {
203
477
 
204
478
  type HtmlToPdfParams = {
205
479
  html: string;
480
+ outputPath?: string; // full path incl. file name; default: generated name in the cache folder
206
481
  pageWidthDp?: number;
207
482
  heightDp?: number;
208
483
  minPageHeightDp?: number;
@@ -221,9 +496,13 @@ type PrintHtmlParams = {
221
496
 
222
497
  ## Persistent connection & auto-reconnect
223
498
 
224
- Connecting through this library keeps the printer connected even if the app is swiped away from Recents — the native side runs a foreground service that outlives the Activity/JS runtime. Every successful `connectPrinter()` call also remembers the device, so the next time the app is opened (even after being fully killed) it reconnects automatically without the user having to pick the printer again.
499
+ Connecting through this library keeps the printer connected even if the app is swiped away from Recents — the native side runs a foreground service that outlives the Activity/JS runtime. Every successful `connectPrinter()` call also remembers the printer (connection type, address/IP, port and USB ids), so the next time the app is opened (even after being fully killed) it reconnects automatically without the user having to pick the printer again.
500
+
501
+ `connectPrinter()` falls back to a direct RFCOMM channel if the standard SDP-based socket fails (common on printers with a broken SDP record). `isConnectedPrinter()` reflects a real disconnect for Bluetooth (Android's ACL-disconnect and Bluetooth-off events) and USB (unplugging) instead of a stale flag; a dropped Wi-Fi/LAN link is only noticed on the next write, which reconnects once automatically.
502
+
503
+ ## Connection lost notification
225
504
 
226
- `connectPrinter()` falls back to a direct RFCOMM channel if the standard SDP-based socket fails (common on printers with a broken SDP record), and `isConnectedPrinter()` reflects a real physical disconnect immediately (via Android's ACL-disconnect broadcast) instead of a stale flag that stays `true` after the printer silently drops the link.
505
+ When a connected printer drops on its own, the ongoing **"Printer connected"** notification is removed and a normal, dismissible **"Printer disconnected — \<printer name\>"** notification is posted. Reconnecting removes it again. Calling `disconnectPrinter()` yourself shows no notification. Change the wording with `notificationTitle` / `disconnectedNotificationTitle` in `connectPrinter()`.
227
506
 
228
507
  ## Printing Bangla / other non-Latin text
229
508
 
@@ -231,7 +510,7 @@ ESC/POS printer firmware fonts only cover ASCII, so `printText()` can't render B
231
510
 
232
511
  ## Example Application
233
512
 
234
- See the [`example`](./example) directory for a complete React Native application demonstrating permission handling, printer pairing/connection, and every print function above.
513
+ See the [`example`](./example) directory for a complete React Native application demonstrating permission handling, Bluetooth and USB connection, printer status, the test page, and the print functions above.
235
514
 
236
515
  ```bash
237
516
  cd example
@@ -61,5 +61,5 @@ repositories {
61
61
 
62
62
  dependencies {
63
63
  implementation "com.facebook.react:react-android"
64
- implementation("com.github.DeveloperRejaul:android-printer-kit:v0.0.14")
64
+ implementation("com.github.DeveloperRejaul:android-printer-kit:v0.1.1")
65
65
  }