Overview
Barcode Scanner for Android opens Google's code scanner from Unity and returns the result to your C# code. Google Play services shows the camera screen, finds the barcode and parses its content, so your application needs no camera permission and no camera code. One call starts a scan, and C# events bring back the result on Unity's main thread.
It reads 13 barcode formats, from QR codes and Data Matrix to EAN-13 and Code 128, and turns structured QR content into typed objects: Wi-Fi networks, contacts, links, calendar events, email, SMS, phone numbers, locations and driver licenses.
One call to scan
BarcodeScanner.Scan() opens the scanner; events return the result, a cancel or an error.
13 formats
QR Code, Aztec, Data Matrix, PDF417, EAN, UPC, Code 128/39/93, Codabar and ITF.
Parsed content
Wi-Fi, contact, link, event, email, SMS, phone, location and driver license data as typed objects.
No camera permission
Google Play services owns the camera screen. Your manifest stays as it is.
Faster with filters
Limit detection to the formats you need, such as QR codes or product codes.
Safe by default
Main-thread events, one persistent service, no logged content, release-build rules included.
Requirements
| Item | Version | Notes |
|---|---|---|
| Unity | 6000.0+ | Android Build Support module |
| Android | API 21+ | Google's scanner library needs API 21; your Unity version may require a higher minimum |
| Google Play services | On the device | The scanner screen and decoder live in Play services. Devices without it report an error |
External Dependency Manager (com.unity.external-dependency-manager) | 2.0+ | Unity Registry package. Resolves Google's libraries from Editor/AndroidDependencies.xml |
play-services-code-scanner | 16.1.0 | Resolved by the External Dependency Manager |
play-services-base | 18.11.1 | Resolved by the External Dependency Manager; provides the module download API |
The plugin works on Android only. In the Editor and on other platforms every call reports
UnsupportedPlatformError instead of failing, so the same code compiles everywhere.
Quick start
- Import the package Everything lands in
Assets/hardartcore/BarcodeScanner. - Add the External Dependency Manager If your project doesn't have it yet, open , click +, choose Install package by name and enter
com.unity.external-dependency-manager. Switch the build target to Android and run . - Open the demo Open
Demo/Scenes/DemoScene, add it to the build, and press with an Android device connected. - Scan Tap Scan and point the camera at a barcode or QR code. The card shows the type, the format and every parsed field.
- Use it in your game Subscribe to the events and call
BarcodeScanner.Scan()(Scanning). A prefab is optional.
The first scan on a device may download Google's scanner module, which needs a connection. Call BarcodeScanner.Initialize() early, for example on your main menu, so the module is ready before the player taps Scan.
Keep one dependency manager. If the project also has Google's EDM4U, Unity asks which one to use: choose Use Unity's External Dependency Manager. The plugin's AndroidDependencies.xml works with both.
The demo scene
Demo/Scenes/DemoScene is a complete scanner screen built with uGUI. Its script,
Demo/Scripts/DemoScript.cs, shows every part of the API:
- Status chip
- Follows every event: preparing, ready, scanner downloaded, scanning, scanned, cancelled and errors with their code.
- Format chips
- All formats, QR codes and Product codes (EAN-13, EAN-8, UPC-A, UPC-E) call
SetFormats. - Result card
- The type with its icon, the format, and each parsed field as a row. Long results scroll. Copy raw value puts the raw value on the clipboard.
- Scan button
- Calls
Scan(), and is disabled whileIsScanningis true.
In the Editor the status chip shows Scan on an Android device: scanning needs Google Play services. The
scene uses the Standalone Input Module; in a project that uses only the Input System, select the
EventSystem and click Replace with InputSystemUIInputModule.
How it works
BarcodeScanner.Scan() C#, Unity's main thread
└─ com.hardartcore.scanner.BarcodeScanner Java source plugin, compiled by Unity
└─ Google code scanner Google Play services: camera screen, decoder, parser
└─ result, cancel or error
← BarcodeScanListener (AndroidJavaProxy) Android UI thread
← queued, then raised from Update Unity's main thread: your event handlers
The adapter ships as two Java files in Plugins/Android, which Unity compiles into the Android build;
no separate Android library is involved. Google's own libraries are resolved by Unity's External Dependency Manager package.
- No camera permission
- The camera screen belongs to Google Play services, so the plugin adds no
CAMERApermission to your manifest and needs no runtime permission prompt. - Scanner module
- The scanning code is a Play services module. If it isn't on the device yet, the plugin requests it and waits for the download, then raises
OnBarcodeModuleInstalledandOnModuleAvailable. A scan requested in the meantime starts when the module is ready. - Main thread
- Android calls back on its UI thread. The plugin queues every callback and raises the events from
Update, so handlers can change UI, scenes and game state directly. - One scan at a time
- A second
Scan()while a scan is open reportsScanInProgressErrorand leaves the open scan alone.
Scanning
Subscribe to the events, then call Scan(). All calls belong on Unity's main thread.
using HardArtcore;
using UnityEngine;
public class ScanExample : MonoBehaviour
{
private void OnEnable()
{
BarcodeScanner.OnBarcodeScanned += OnBarcodeScanned;
BarcodeScanner.OnScanCancelled += OnScanCancelled;
BarcodeScanner.OnErrorOccurred += OnErrorOccurred;
}
private void OnDisable()
{
BarcodeScanner.OnBarcodeScanned -= OnBarcodeScanned;
BarcodeScanner.OnScanCancelled -= OnScanCancelled;
BarcodeScanner.OnErrorOccurred -= OnErrorOccurred;
}
// Call from a button.
public void Scan() => BarcodeScanner.Scan();
private void OnBarcodeScanned(Barcode barcode)
{
Debug.Log($"{barcode.GetFormat()} {barcode.GetValueType()}: {barcode.GetDisplayValue()}");
}
private void OnScanCancelled()
{
// The player closed the scanner screen.
}
private void OnErrorOccurred(string message, int errorCode)
{
// Show the message, offer a retry. See Error codes.
}
}
Preparing the scanner
The service starts by itself on the first call. Initialize() starts it ahead of time and downloads
the scanner module if the device needs it; calling it again does nothing once the module is ready.
private void Start()
{
BarcodeScanner.OnModuleAvailable += () => scanButton.interactable = true;
BarcodeScanner.Initialize();
}
Choosing formats
By default the scanner detects every format. Limiting it to the formats you expect makes detection faster and
avoids reading the wrong code. Values are copied; null or an empty array resets to all formats. The
setting applies to the next scan.
// QR codes only.
BarcodeScanner.SetFormats(new[] { (int)Barcode.BarcodeFormat.QrCode });
// Retail product codes.
BarcodeScanner.SetFormats(new[]
{
(int)Barcode.BarcodeFormat.Ean13, (int)Barcode.BarcodeFormat.Ean8,
(int)Barcode.BarcodeFormat.UpcA, (int)Barcode.BarcodeFormat.UpcE,
});
// Everything again.
BarcodeScanner.SetFormats(null);
An unknown or negative value throws ArgumentOutOfRangeException before anything reaches Android.
BarcodeScanner reference
Everything is static on HardArtcore.BarcodeScanner.
Methods and properties
- Scan()
- Opens the scanner screen. Prepares the scanner module first if needed. Ends with exactly one of
OnBarcodeScanned,OnScanCancelledorOnErrorOccurred. - Initialize()
- Optional. Starts the service and prepares the scanner module ahead of the first scan.
- SetFormats(int[] formats)
- Formats to detect, as
Barcode.BarcodeFormatvalues.nullor empty: all formats. - IsSupported
trueon Android. It does not check whether Google Play services is installed.- IsReady
trueonce the scanner module is available.- IsScanning
truefromScan()until its result, including the time spent waiting for a module download.
Events
All events are raised on Unity's main thread.
- OnBarcodeScanned (Barcode)
- A barcode was read. See Reading results.
- OnScanCancelled
- The player closed the scanner screen without scanning.
- OnErrorOccurred (string message, int errorCode)
- Preparation or the scan failed. Both can be retried.
- OnModuleAvailable
- The scanner module is ready, whether it was already on the device or just downloaded.
- OnBarcodeModuleInstalled
- The module was just downloaded. Followed by
OnModuleAvailable.
Error codes
| Constant | Value | Meaning |
|---|---|---|
UnsupportedPlatformError | -1001 | Not running on Android (the Editor, iOS, desktop...) |
ScanInProgressError | -1002 | A scan is already open |
InitializationError | -1003 | The service, the module or the activity was not available |
ModuleInstallCancelledError | -1005 | The scanner module download was cancelled; scanning again retries |
InvalidFormatError | -1006 | The Android side received an unsupported format value |
| Other values | > 0 | Passed through from Google Play services (MlKitException and ApiException codes) |
Reading results
OnBarcodeScanned passes a Barcode. Every barcode has a format, a value type, a raw value
and a display value. Structured content has a typed getter that matches its value type; the others return
null.
private void OnBarcodeScanned(Barcode barcode)
{
switch (barcode.GetValueType())
{
case Barcode.BarcodeType.URL:
Application.OpenURL(barcode.GetUrl().GetUrl());
break;
case Barcode.BarcodeType.Wifi:
var wifi = barcode.GetWifi();
Connect(wifi.GetSsid(), wifi.GetPassword(), wifi.GetEncryptionType());
break;
case Barcode.BarcodeType.Product:
LookUpProduct(barcode.GetRawValue()); // e.g. 5901234123457
break;
default:
ShowText(barcode.GetDisplayValue());
break;
}
}
Barcode
- GetFormat()
- The symbology, a
Barcode.BarcodeFormat. - GetValueType()
- What the content is, a
Barcode.BarcodeType. - GetRawValue()
- The encoded text, unchanged. May be
nullfor binary content. - GetDisplayValue()
- A readable form of the value, for example the URL of a bookmark.
Value types
| BarcodeType | Getter | Fields |
|---|---|---|
URL | GetUrl() → UrlBookmark | GetTitle, GetUrl |
Wifi | GetWifi() → WiFi | GetSsid, GetPassword, GetEncryptionType (Open, Wpa, Wep) |
ContactInfo | GetContactInfo() → ContactInfo | GetName (PersonName), GetOrganization, GetTitle, GetPhones, GetEmails, GetAddresses, GetUrls |
Email | GetEmail() → Email | GetAddress, GetSubject, GetBody, GetEmailType |
Phone | GetPhone() → Phone | GetNumber, GetPhoneType (Work, Home, Fax, Mobile) |
Sms | GetSms() → Sms | GetPhoneNumber, GetMessage |
Geo | GetGeoPoint() → GeoPoint | GetLatitude, GetLongitude |
CalendarEvent | GetCalendarEvent() → CalendarEvent | GetSummary, GetStart/GetEnd (CalendarDateTime), GetLocation, GetOrganizer, GetDescription, GetStatus |
DriverLicense | GetDriverLicense() → DriverLicense | Names, GetLicenseNumber, birth, issue and expiry dates, issuing country, address, gender, document type (AAMVA) |
Isbn, Product, Text, Unknown | none | Read GetRawValue() or GetDisplayValue() |
PersonName has first, middle, last, prefix, suffix, pronunciation and formatted name.
Address has GetAddressType and GetAddressLines. CalendarDateTime
has year, month, day, hours, minutes, seconds and the raw value. Every class has a readable
ToString() for debugging.
Results read their values from the Android object on each call. Read what you need in the event handler, and keep plain strings or numbers rather than the Barcode itself.
Formats
| BarcodeFormat | Value | Kind |
|---|---|---|
Code128 | 1 | Linear |
Code39 | 2 | Linear |
Code93 | 4 | Linear |
Codabar | 8 | Linear |
DataMatrix | 16 | 2D |
Ean13 | 32 | Linear (product) |
Ean8 | 64 | Linear (product) |
Itf | 128 | Linear |
QrCode | 256 | 2D |
UpcA | 512 | Linear (product) |
UpcE | 1024 | Linear (product) |
Pdf417 | 2048 | 2D |
Aztec | 4096 | 2D |
AllFormats (0) and Unknown (-1) complete the enum. The values match Google's constants.
Google's scanner does not read 1D barcodes with one character, ITF codes shorter than six characters, codes using
FNC2, FNC3 or FNC4, or QR codes in ECI mode.
Lifecycle
- No prefab needed
- The first call creates a
BarcodeScannerGameObject. You can also placePrefabs/BarcodeScanner.prefabat the root of your first scene so it starts preparing at once. - One instance
- The service persists between scenes (
DontDestroyOnLoad). A secondBarcodeScannercomponent, for example from the prefab in another scene, removes itself. - Cleanup
- Destroying the component releases the Android service and its listeners. The scanner screen itself belongs to Google Play services and closes on its own.
- Domain reload
- Static events are cleared when Play mode starts, so it works with Enter Play Mode Options and domain reload turned off.
- Privacy
- The plugin never logs scanned content. Results go only to your event handlers.
Android build
- Resolve the dependencies The External Dependency Manager reads
Editor/AndroidDependencies.xml. With Patch mainTemplate.gradle turned on, Gradle downloads the libraries during the build; otherwise the resolver copies them intoAssets/Plugins/Android. Both work. - Build Unity compiles
BarcodeScanner.javaandBarcodeScanListener.javainto the Gradle project. Keep both enabled for Android only in the Plugin Inspector (they ship that way). - Release builds
Editor/BarcodeScannerAndroidBuild.csadds R8 keep rules for the adapter and for Google's result classes, which are read by name from C#, so minification keeps them. No setup needed.
Delete Plugins/Android/scanner-release.aar. Importing a package never removes old files, and the AAR defines the same Java classes as the new sources. The build stops with its path until it is gone.
The adapter's managed callbacks carry [Preserve], so they survive IL2CPP code stripping. Test a release
build on a device before you publish, as with any native plugin.
Folder layout
Assets/hardartcore/BarcodeScanner/
├── Documentation.html this page, offline
├── Scripts/
│ ├── BarcodeScanner.cs the API: Scan, Initialize, SetFormats, events
│ └── Data/ Barcode and its typed results (UrlBookmark, WiFi, ContactInfo...)
├── Plugins/Android/
│ ├── BarcodeScanner.java the Android adapter, compiled by Unity
│ └── BarcodeScanListener.java
├── Editor/
│ ├── AndroidDependencies.xml Google libraries for the External Dependency Manager
│ └── BarcodeScannerAndroidBuild.cs R8 rules, legacy AAR check
├── Prefabs/BarcodeScanner.prefab optional
└── Demo/
├── Scenes/DemoScene.unity
├── Scripts/DemoScript.cs
├── Sprites/ rounded shape, backdrop, Material Symbols icons
└── Fonts/ Nunito
Delete Demo if you don't need it; nothing else depends on it.
Troubleshooting
- Every call reports -1001
- You are in the Editor or on another platform. Build to an Android device.
- Gradle: duplicate class com.hardartcore.scanner...
- The 1.x
scanner-release.aaris still in the project. Delete it. - Gradle: package com.google.mlkit... does not exist
- The Google libraries were not resolved. Run .
- Unity asks which dependency manager to use
- The project also has Google's EDM4U, for example in
Assets/ExternalDependencyManager. Choose Use Unity's External Dependency Manager, and remove the old.unitypackageinstall unless another plugin strictly needs it. Keep only one dependency manager active; you can switch later in . - The first scan waits, or fails offline
- The scanner module is downloading. It needs a connection once; call
Initialize()early. - An error with a positive code
- It comes from Google Play services: often Play services is missing, outdated or disabled on the device.
- Buttons don't respond in the demo
- The project uses only the Input System: replace the EventSystem's module with
InputSystemUIInputModule. - NoSuchMethodError in a release build
- Another build script replaced
unityLibrary/build.gradle. Check that the line withhardartcore-barcode-scanner.prois present.
Third-party notices
The demo uses the Nunito font (SIL Open Font License 1.1) and Material Symbols icons (Apache License 2.0). Google's Play services libraries are not included in the package: the External Dependency Manager downloads them under Google's terms.
The full licence texts ship in the package's Documentation.html, and online: SIL Open Font License 1.1, Apache License 2.0.
Release notes
2.0.0
- The Android adapter ships as Java source compiled by Unity; the separate
scanner-release.aaris gone. Delete it when upgrading (the build tells you if it is still there). - Events are raised on Unity's main thread. Before, handlers ran on Android's UI thread.
- A newly downloaded scanner module now raises
OnBarcodeModuleInstalledwhen the download finishes, thenOnModuleAvailable. - Fixed format values:
Code128is 1 andCode39is 2, andCode93(4) was added. Update any stored numeric values. - New:
Initialize(),IsSupported,IsReady,IsScanningand error constants. A prefab is optional; one instance persists between scenes. SetFormatscopies its array, resets onnullor empty, and throws on unknown values.- A second scan while one is open reports
ScanInProgressError. Unsupported platforms reportUnsupportedPlatformError. - R8 keep rules for release builds,
[Preserve]on the callbacks, and no logging of scanned content. - Google libraries updated to
play-services-code-scanner16.1.0 andplay-services-base18.11.1. - Uses Unity's External Dependency Manager package (
com.unity.external-dependency-manager) from the Unity Registry. - A new demo screen: status, format filters, and a result card with every parsed field. It no longer needs TextMesh Pro.
- Requires Unity 6.
1.0.0
- First release.
Need help?
Questions, bug reports or feature requests: I usually reply within a day.
Last updated November 4, 2026.