Download the PHP package sghimire/mobile-scanner without Composer
On this page you can find all versions of the php package sghimire/mobile-scanner. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download sghimire/mobile-scanner
More information about sghimire/mobile-scanner
Files in sghimire/mobile-scanner
Package mobile-scanner
Short Description Self-contained native QR/barcode scanner (CameraX + ML Kit / AVFoundation) for NativePHP Mobile — own facade, fluent multi-format API, events, and JS bindings, with no dependency on the paid nativephp/mobile-scanner plugin.
License MIT
Informations about the package mobile-scanner
Mobile Scanner
Native QR code / barcode scanning for NativePHP Mobile apps, powered by CameraX + ML Kit on Android and AVFoundation on iOS.
This package is a free, self-contained alternative to the paid nativephp/mobile-scanner plugin. It ships its own Laravel facade, a fluent scan builder, Laravel events, JS/TypeScript bindings, and the native Kotlin/Swift implementation — with no dependency on the paid plugin.
Features
- Full-screen native camera scanner for QR codes and common barcode formats.
- Built-in gallery button on the scanner overlay — pick an existing photo instead of the live camera, decoded on-device (Android Photo Picker + ML Kit, iOS
PHPickerViewController+ Vision). No extra permissions or setup required. - Fluent, chainable scan builder in both PHP and JavaScript.
- Single-shot or continuous (multi-scan) sessions.
- Programmatically stop an open scan session.
CodeScannedandCancelledLaravel events, or bind straight to Livewire with#[OnNative].- Works from PHP (Blade/Livewire) and from JavaScript (Vue, React, Inertia, or plain JS).
Example App
Authenticator is a full example app built using this plugin
Requirements
- PHP ^8.2
nativephp/mobile^3.0livewire/livewire— only needed if you use the#[OnNative]attribute
Installation
Laravel's package auto-discovery registers ScannerServiceProvider for you. Then register the plugin with NativePHP:
This wires up the plugin's nativephp.json manifest (bridge functions, Android camera/vibrate permissions, iOS NSCameraUsageDescription) into your native build. Rebuild/reinstall the native shell afterwards (php artisan native:install or native:run) so the permissions and bridge functions are picked up.
How It Works (Under the Hood)
Same two-phase pattern as every async call in this plugin family — a synchronous "start" acknowledgement, then the real result delivered later through two parallel channels.
- Request out.
Scanner::scan()->scan()(PHP) andScanner.scan()(JS) both reach the same bridge — JS viafetch('/_native/api/call', { method: 'MobileScanner.Scan', params }), PHP vianativephp_call('MobileScanner.Scan', json_encode($params)). The bridge router matches"MobileScanner.Scan"toScannerFunctions.Scan, which opens the full-screen camera UI (CameraX + ML Kit on Android, AVFoundation on iOS). - Immediate ack. The call returns right away confirming the scanner UI is open — not that anything has been decoded yet.
- Result comes back later, twice, per scan. Each time the camera decodes a matching code, the native side injects one script into the webview that fires a
native-eventCustomEventondocument(what the JSOn()/Off()helpers listen for) and makes a second call back into Laravel that instantiates and dispatches the realCodeScannedevent (whatEvent::listen()/#[OnNative]pick up). Closing without a match deliversCancelledthe same way. - Continuous mode just means the scanner stays open and repeats step 3 for every new code, debounced natively so the same code isn't reported multiple times per second, until you call
Scanner::stop()/Scanner.stop()or the user closes it.
Because results are asynchronous and delivered to PHP and JS independently, always drive your UI from the CodeScanned / Cancelled events — never from the return value of scan().
Scanning from the photo gallery
Every scanner overlay opened by MobileScanner.Scan includes a gallery button (🖼) next to the close/torch buttons by default. No PHP/JS code or extra config is needed — it's part of the same overlay:
- Tapping it opens the OS photo picker (Android Photo Picker, iOS
PHPickerViewController) — neither requires a photo-library permission. - The chosen image is decoded on-device against the same
formats()you passed toscan()(Android: ML Kit; iOS: the Vision framework). - A match fires
CodeScannedexactly like a camera match, and closes the scanner — even incontinuous()mode, since picking a photo is a deliberate one-off action. - If no supported code is found in the picture, a short native message is shown and the scanner stays open so the user can try the camera or pick another photo — no event fires for that case.
Turn it off per scan with ->gallery(false) (PHP) / .gallery(false) (JS) — the button is simply omitted from the overlay, camera-only:
PHP Usage
The Scanner facade
scan() on the builder returns bool — true once the request reached the native bridge, false if it couldn't be started (e.g. running outside the native shell, or the builder was already started once).
If you never call ->scan() explicitly, it fires automatically when the builder object is destructed — so Scanner::scan()->prompt('Scan Code'); alone is enough to trigger it. Calling ->scan() yourself is recommended so you can check the return value.
Builder methods
| Method | Description |
|---|---|
prompt(string $text) |
Instruction text shown above the camera viewfinder. Defaults to "Scan Code". |
continuous(bool $continuous = true) |
Keep the scanner open and keep firing CodeScanned after each match instead of closing on the first one. |
gallery(bool $allow = true) |
Show/hide the gallery button on the overlay. Defaults to true; pass false to force camera-only scanning. |
formats(array $formats) |
Restrict detection to one or more formats (see table below). Throws InvalidArgumentException if empty or unknown. |
haptics(bool $enabled = true) |
Vibrate/impact-feedback on a successful scan. Defaults to true. |
zoom(float $ratio = 1.0) |
Initial camera zoom ratio, clamped to what the device supports. Defaults to 1.0. Throws InvalidArgumentException if not positive. |
maxZoom(float $ratio = 3.0) |
Upper bound of the on-screen zoom slider, clamped to what the device supports. Defaults to 3.0. Throws InvalidArgumentException if not positive. |
zoomControl(bool $enabled = true) |
Show/hide the on-screen zoom slider. Defaults to true. |
focusOnTap(bool $enabled = true) |
Let the user tap the preview to refocus the camera. Defaults to true. |
timeout(int $seconds = 0) |
Auto-cancel the scan after N seconds, firing Cancelled with reason timeout. Disabled (0) by default. Throws InvalidArgumentException if negative. |
id(string $id) |
Custom correlation ID for this scan session (not auto-generated — null unless set). |
getId() |
Get this scan's correlation ID, or null. |
scan() |
Send the scan request to the native bridge. Returns bool. |
Supported formats
qr, ean13, ean8, code128, code39, upca, upce, all (also available as PendingScan::FORMATS). Defaults to ['qr'] if formats() is never called.
Stopping a scan
Useful for dismissing a continuous() session programmatically (e.g. from a "Done" button elsewhere in the UI):
This fires Cancelled with reason: 'stopped_by_app'.
Listening for results
Livewire: #[OnNative]
JavaScript Usage
Importing
This package doesn't publish a #nativephp import alias (that's reserved for NativePHP's first-party plugins). Import the file directly — either from the vendor path, or copy it into your own resources/js/ and import it from there:
Full TypeScript types (including the BarcodeFormat union) are included in scanner.d.ts alongside it.
Basic scan
Scanner.scan() returns a thenable builder — await it directly, or chain builder methods first:
Stopping a scan
Listening for events
Vue 3 example
React example
JS API reference
| Export | Signature | Description |
|---|---|---|
Scanner.scan() |
() => PendingScan |
Start building a scan session. |
.prompt(text) |
(string) => this |
Instruction text above the viewfinder. |
.continuous(continuous?) |
(boolean = true) => this |
Keep scanning after each match. |
.gallery(allow?) |
(boolean = true) => this |
Show/hide the gallery button. Defaults to true; pass false for camera-only. |
.formats(formats) |
(BarcodeFormat[]) => this |
Restrict detection to given formats. Throws if empty/invalid. |
.haptics(enabled?) |
(boolean = true) => this |
Vibrate/impact-feedback on a successful scan. Defaults to true. |
.zoom(ratio?) |
(number = 1.0) => this |
Initial camera zoom ratio, clamped to what the device supports. Throws if not positive. |
.maxZoom(ratio?) |
(number = 3.0) => this |
Upper bound of the on-screen zoom slider, clamped to what the device supports. Throws if not positive. |
.zoomControl(enabled?) |
(boolean = true) => this |
Show/hide the on-screen zoom slider. Defaults to true. |
.focusOnTap(enabled?) |
(boolean = true) => this |
Let the user tap the preview to refocus the camera. Defaults to true. |
.timeout(seconds?) |
(number = 0) => this |
Auto-cancel the scan after N seconds, firing Cancelled with reason timeout. Disabled (0) by default. Throws if negative. |
.id(id) |
(string) => this |
Custom correlation ID. |
.getId() |
() => string \| null |
Read the current correlation ID. |
Scanner.stop(id?) |
(string?) => Promise<{ stopped: boolean }> |
Dismiss the open scanner. |
On(event, callback) |
(string, (payload, eventName) => void) => void |
Subscribe to a native event. |
Off(event, callback) |
(string, (payload, eventName) => void) => void |
Unsubscribe. |
Events.Scanner.CodeScanned |
string |
Event name constant. |
Events.Scanner.Cancelled |
string |
Event name constant. |
await-ing (or .then-ing) a PendingScan sends the request to the native bridge exactly once — awaiting it twice is a no-op the second time.
Events reference
CodeScanned
Dispatched every time the camera successfully decodes a matching code.
| Property | Type | Description |
|---|---|---|
data |
string / string |
The decoded value. |
format |
string / string |
Which format matched, e.g. "qr", "ean13". |
id |
?string / string \| null |
The correlation ID from .id(), if one was set. |
Cancelled
Dispatched when the scanner closes without (another) match — the user tapped close, or Scanner::stop() / Scanner.stop() was called.
| Property | Type | Description |
|---|---|---|
reason |
?string / string \| null |
"user_cancelled" when the user taps close, "stopped_by_app" when closed via stop(), "timeout" when .timeout() elapsed, "camera_error" if the camera failed to start, "permission_denied" / "permission_required" after a permission prompt resolves (retry .scan() on permission_required). Never null in practice. |
id |
?string / string \| null |
The correlation ID from .id(), if one was set. |
- PHP classes:
Sandip\Scanner\Native\Events\Scanner\CodeScanned,Sandip\Scanner\Native\Events\Scanner\Cancelled - JS event name constants:
Events.Scanner.CodeScanned,Events.Scanner.Cancelled
Implementation Guide: Building a Ticket Check-In Scanner
A typical staff-facing feature: keep the camera open, scan every ticket that passes by, validate each one against the backend, and show a running result list — without reopening the scanner between tickets.
1. Backend: validate a ticket code
2a. Livewire scanner
2b. Vue/React scanner (calling the API endpoint)
continuous() keeps the same scanner session open across many tickets — each decode fires its own CodeScanned, debounced natively so a ticket held in frame for a second isn't logged twice. Call Scanner::stop('check-in') / Scanner.stop('check-in') from a "Done" button to close the camera when the shift ends.
Platform notes
| Android | iOS | |
|---|---|---|
| Min OS version | API 23 | 15.0 |
| Permission | android.permission.CAMERA, android.permission.VIBRATE |
NSCameraUsageDescription in Info.plist (haptics need no entitlement) |
| Native implementation | resources/android/ScannerFunctions.kt (CameraX + ML Kit barcode scanning) |
resources/ios/ScannerFunctions.swift (AVFoundation) |
| Gallery picker | Android Photo Picker (ActivityResultContracts.PickVisualMedia) — no permission needed |
PHPickerViewController — no permission needed |
| Gallery decoding | ML Kit (InputImage.fromFilePath) |
Vision (VNDetectBarcodesRequest) |
Both are configured automatically by nativephp.json — you don't need to edit native project files by hand. Continuous mode uses a debounce window on both platforms so the same code isn't reported multiple times per second.
Testing
Outside of a compiled native shell, Scanner::scan()->scan() and Scanner::stop() return false (there's no bridge to call) — this is expected and is exactly what the test suite asserts.
License
MIT