---
title: Capacitor Barcode Scanning Without Google Play Services
description: Build a Capacitor barcode scanner without Google Play services for Huawei, Fire OS and AOSP devices with the bundled ML Kit model on Android.
date:
  created: 2026-10-02
  updated: 2026-10-02
authors:
  - robingenz
categories:
  - Capacitor
  - Guides
  - ML Kit
  - SDKs
links:
  - Capacitor Barcode Scanner: sdks/capacitor/barcode-scanner.md
  - Capacitor ML Kit Barcode Scanning: sdks/capacitor/mlkit/barcode-scanning.md
  - Capacitor Google Play Services: sdks/capacitor/google-play-services.md
faq: true
---

# Capacitor Barcode Scanning Without Google Play Services

Barcode scanning works on Android devices without Google Play services, as long as the ML Kit model ships inside your app instead of being downloaded through Play services. That makes a Capacitor barcode scanner without Google Play services possible on Huawei phones released after May 2019, Amazon Fire tablets, and AOSP-based kiosk and rugged devices. This guide explains the bundled and unbundled ML Kit models and which methods of the free, open-source [Capacitor ML Kit Barcode Scanning plugin](../../sdks/capacitor/mlkit/barcode-scanning.md) rely on Play services. It also shows how the [Capacitor Barcode Scanner plugin](../../sdks/capacitor/barcode-scanner.md) avoids Play services on every path. The ML Kit plugin is an unofficial plugin maintained by Capawesome and is not affiliated with Google. The Barcode Scanner plugin is paid and part of the Capawesome [Insiders](../../insiders/index.md) subscription.

<!-- more -->

<div class="capawesome-z29o10a">
  <a href="https://capawesome.io/" target="_blank">
    <img alt="Try Capawesome Cloud free for 14 days — iOS builds without a Mac" src="https://capawesome.io/assets/banners/cloud-free-trial-capacitor-apps.png" />
  </a>
</div>

**Key takeaways:**

- ML Kit barcode scanning comes as a bundled model linked into the app (about 2.4 MB) or an unbundled one downloaded via Google Play services (about 200 KB).
- In the free Capacitor ML Kit Barcode Scanning plugin, `startScan()` and `readBarcodesFromImage()` use the bundled model, while `scan()` runs the Google code scanner and needs Google Play services.
- The Capacitor Barcode Scanner plugin, part of the paid Capawesome Insiders subscription, uses the bundled model for every Android method and AVFoundation plus Vision on iOS.
- The Capacitor Google Play Services plugin's `getStatus()` resolves to `SERVICE_MISSING` on devices without Play services, so an app can fall back from `scan()` to `startScan()` at runtime.
- Only Android is affected, since iOS has no Google Play services and the ML Kit plugin's iOS model ships with the `GoogleMLKit/BarcodeScanning` pod.

## Devices without Play services

Google describes Google Play services as "a core set of on-device background services that run on all Google-certified Android devices" in its Play services overview. Several groups of Android devices ship without it:

- **Huawei phones.** Models released after May 2019 launched without Google Play services and run Huawei's HMS Core instead. Huawei's own [support article](https://consumer.huawei.com/en/support/content/en-us15841718/){:target="_blank"} states that "certain Android apps require support from GMS Core and may not be able to run on HMS Core".
- **Amazon Fire tablets and Fire TV.** Amazon's [porting guide](https://developer.amazon.com/docs/app-porting/port-existing-app.html){:target="_blank"} warns that an existing Android app "might use Google Play services, which isn't supported on Fire OS".
- **Enterprise, kiosk and rugged devices.** Some rugged and kiosk devices ship AOSP builds without GMS, and Microsoft Intune offers an AOSP management solution for corporate-owned devices that are "Not integrated with Google Mobile Services". On devices that do have Play services, Zebra's GMS Restricted mode can switch it off.
- **Meta Quest.** Meta's [Horizon OS documentation](https://developers.meta.com/horizon/documentation/android-apps/unsupported-dependencies/){:target="_blank"} states that "Horizon OS does not include Google Mobile Services, so calls into GMS APIs fail".
- **Mainland China.** Google's Wear OS guide for China tells developers to "account for handsets without Google Play services pre-installed".
- **De-Googled systems.** On LineageOS and GrapheneOS, Google Play is an optional, separate install.

A scanner that depends on Play services breaks on these devices. One example from outside Capacitor is [expo/expo#39449](https://github.com/expo/expo/issues/39449){:target="_blank"}, where expo-camera, a React Native library, crashed with an `ActivityNotFoundException` on Honor and Huawei devices because it tried to launch Google ML Kit's barcode scanning activity, which those devices don't have. For an enterprise app rolled out to a fleet of AOSP handhelds, these devices can be every install. If you distribute to Huawei users through their own store, [Publish a Capacitor App on Huawei AppGallery](./how-to-publish-a-capacitor-app-on-huawei-appgallery.md) covers the release side.

## Bundled vs. unbundled model

Capacitor barcode scanning works without Google Play services when the Android side uses the bundled ML Kit model, because that model is statically linked into the app and nothing has to be downloaded through Play services. Google's [Barcode Scanning guide for Android](https://developers.google.com/ml-kit/vision/barcode-scanning/android){:target="_blank"} offers the model in two variants, and the last three rows below quote its comparison table:

| Feature | Unbundled | Bundled |
| --- | --- | --- |
| Gradle artifact | `com.google.android.gms:play-services-mlkit-barcode-scanning` | `com.google.mlkit:barcode-scanning` |
| Implementation | Model is dynamically downloaded via Google Play Services. | Model is statically linked to your app at build time. |
| App size | About 200 KB size increase. | About 2.4 MB size increase. |
| Initialization time | Might have to wait for model to download before first use. | Model is available immediately. |

The unbundled model lives in Play services and is shared between apps. Google states that if the app doesn't request the download ahead of time, "the model is downloaded the first time you run the scanner. Requests you make before the download has completed produce no results." On a device without Play services, there is nothing to download it from. Google's advice on [choosing between the two](https://developers.google.com/ml-kit/tips/installation-paths){:target="_blank"} lists full functionality right after installation and offline use as reasons for bundled, and a smaller app plus automatic model updates through Play services as reasons for unbundled.

The Google code scanner is a third option, a complete scanning screen rather than a model. According to Google's [code scanner guide](https://developers.google.com/ml-kit/vision/barcode-scanning/code-scanner){:target="_blank"}, its "implementation resides entirely within Google Play services", and the installation paths page lists it as unbundled only. Google's document scanner is unbundled only as well, which is why the [Capacitor Document Scanner plugin](../../sdks/capacitor/document-scanner.md) requires Play services on Android.

The `com.google.mlkit.vision.DEPENDENCIES` meta-data in `AndroidManifest.xml` only applies to modules that Play services delivers. It tells Play services to download them while the app is installed from the Play Store, so it has no effect on a bundled model and does nothing on devices without the Play Store.

To see which variant a plugin uses, read the artifact its `android/build.gradle` declares directly. `com.google.mlkit:barcode-scanning` is the bundled model, and `com.google.android.gms:play-services-mlkit-barcode-scanning` is the unbundled one. The resolved dependency tree won't tell you, because the bundled artifact's own POM depends on `play-services-mlkit-barcode-scanning`, so that client library shows up in bundled setups too. A `com.google.android.gms` library inside your APK is also not the same thing as the Google Play services app on the device.

## The ML Kit plugin

The Capacitor ML Kit Barcode Scanning plugin declares two Google SDKs in its [Android `build.gradle`](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/barcode-scanning/android/build.gradle){:target="_blank"}, and each method uses one of them. The first, `com.google.mlkit:barcode-scanning:17.3.0`, is the bundled model, which the plugin has used since its first release in March 2023. The second, `com.google.android.gms:play-services-code-scanner:16.1.0`, is the Google code scanner.

[`startScan(...)`](../../sdks/capacitor/mlkit/barcode-scanning.md#startscan) runs on the bundled model, so it works without Play services. [`readBarcodesFromImage(...)`](../../sdks/capacitor/mlkit/barcode-scanning.md#readbarcodesfromimage) uses the same model. [`scan(...)`](../../sdks/capacitor/mlkit/barcode-scanning.md#scan) opens the Google code scanner instead, which the plugin's API reference describes as "only available on devices with Google Play Services installed".

Our answers on GitHub confirm the split. Asked in [issue #174](https://github.com/capawesome-team/capacitor-mlkit/issues/174){:target="_blank"} whether the plugin supports Huawei devices without Google Play services, we replied that "Huawei devices are supported but you have to use the `startScan(...)` method and build the UI yourself". In [issue #219](https://github.com/capawesome-team/capacitor-mlkit/issues/219){:target="_blank"}, a user's scanner module download failed in production with status `FAILED = 5`, and we named both artifacts: "`com.google.mlkit:barcode-scanning` is used by the `startScan(...)` method and is bundled. `com.google.android.gms:play-services-code-scanner` is used by the `scan(...)` method and cannot be bundled as far as I know."

A `startScan()` session needs the camera permission, a listener for the `barcodesScanned` event, and a CSS class that hides the web view elements covering the camera:

```typescript
import { BarcodeScanner } from '@capacitor-mlkit/barcode-scanning';

const startScan = async () => {
  const { camera } = await BarcodeScanner.requestPermissions();
  if (camera !== 'granted') {
    return;
  }
  document.body.classList.add('barcode-scanner-active');
  await BarcodeScanner.addListener('barcodesScanned', (event) => {
    console.log(event.barcodes);
  });
  await BarcodeScanner.startScan();
};

const stopScan = async () => {
  document.body.classList.remove('barcode-scanner-active');
  await BarcodeScanner.removeAllListeners();
  await BarcodeScanner.stopScan();
};
```

The `barcode-scanner-active` class is yours to define. `startScan()` renders the camera behind the web view, so the class has to make every element between the camera and the user transparent. Version 7 of the plugin replaced the older single-barcode event with `barcodesScanned`, which delivers an array in `event.barcodes`.

The trade-off is the UI and the permission. With `startScan()`, you build the scanning screen and declare the `CAMERA` permission yourself. With `scan()`, Google provides the screen and your app needs no camera permission on Android, but only on devices with Play services. The ML Kit plugin also parses structured payloads. Each `Barcode` carries a `valueType` and, depending on the content, fields such as `wifi`, `contactInfo`, `driverLicense` or `calendarEvent`, which the Insiders plugin does not offer yet.

## Detect Play services first

With the [Capacitor Google Play Services plugin](../../sdks/capacitor/google-play-services.md), an app on the free ML Kit plugin can use `scan()` where Play services exists and fall back to `startScan()` everywhere else. The plugin is free, Android-only and depends only on the `play-services-base` library, whose availability check "works on any Android device, including devices without Google Play Services". [`getStatus()`](../../sdks/capacitor/google-play-services.md#getstatus) resolves to `SERVICE_MISSING` on devices where Play services is not installed at all, which the plugin documentation names for Huawei devices with Huawei Mobile Services, Amazon Fire devices and custom Android builds. The following function picks the scan method from that status:

```typescript
import { BarcodeScanner } from '@capacitor-mlkit/barcode-scanning';
import { GooglePlayServices, Status } from '@capawesome/capacitor-google-play-services';

const scanBarcodeOnAndroid = async () => {
  const { status } = await GooglePlayServices.getStatus();
  if (status !== Status.Success) {
    await startScan();
    return;
  }
  const { barcodes } = await BarcodeScanner.scan();
  console.log(barcodes);
};
```

`startScan` is the function from the previous section. Run this branch on Android only, since the Google Play Services plugin has no iOS or web implementation. Because `scan()` requires Google Play services, make the check before the call rather than reacting to a failed scan. The function falls back for every status other than `SUCCESS`. `SERVICE_MISSING` is the Huawei and Fire OS case, while `SERVICE_DISABLED`, `SERVICE_VERSION_UPDATE_REQUIRED` and `SERVICE_UPDATING` mean Play services is installed but not usable yet, and [`makeAvailable()`](../../sdks/capacitor/google-play-services.md#makeavailable) prompts the user to fix that before you offer `scan()`.

A `SUCCESS` status does not guarantee that the scanner module is on the device yet. The ML Kit plugin's documentation recommends [`isGoogleBarcodeScannerModuleAvailable()`](../../sdks/capacitor/mlkit/barcode-scanning.md#isgooglebarcodescannermoduleavailable) as the plugin-level check before `scan()` on Android, and `installGoogleBarcodeScannerModule()` starts the download when the module is missing.

## The Insiders plugin

The Capacitor Barcode Scanner plugin uses the bundled ML Kit model for every Android method, including its ready-made scanner UI. Its documentation lists this as a feature: "Uses the bundled ML Kit model, so scanning works offline and without Google Play services." The Android notes add the cost, saying scanning "does not require Google Play services, but the model increases your app size by a few megabytes."

The fullscreen [`scan(...)`](../../sdks/capacitor/barcode-scanner.md#scan), the embedded `startScan(...)` and `readBarcodesFromImage(...)` all run on that model, so there is no Play services branch to write. The scanner UI ships inside the plugin rather than as a Play services module, which is why the plugin's migration table marks `isGoogleBarcodeScannerModuleAvailable()` as "Not needed". [`isAvailable()`](../../sdks/capacitor/barcode-scanner.md#isavailable) only checks whether the device has a camera. The plugin declares the camera permission in its own manifest, so neither the `DEPENDENCIES` meta-data nor any other manifest change is required. On iOS, the plugin is built on AVFoundation and Vision only, with no third-party pods.

A fullscreen scan with a themed UI takes one call:

```typescript
import { BarcodeFormat, BarcodeScanner } from '@capawesome-team/capacitor-barcode-scanner';

const scanParcelLabel = async () => {
  const { barcodes } = await BarcodeScanner.scan({
    formats: [BarcodeFormat.Code128, BarcodeFormat.DataMatrix],
    ui: {
      accentColor: '#ff9500',
      beep: true,
      instructions: 'Hold the label inside the frame.',
      title: 'Scan Parcel',
    },
  });
  return barcodes[0].rawValue;
};
```

Nothing in this call goes through Play services, so the code path is the same on a Pixel, a Huawei phone and an AOSP handheld. For scanning inside your own layout, [Embedded Barcode Scanner with Camera Preview in Capacitor](./capacitor-embedded-barcode-scanner.md) covers the embedded `startScan()` mode with a native camera preview. The plugin is part of the paid Capawesome Insiders subscription, and the [Installation](../../sdks/capacitor/barcode-scanner.md#installation) section of the plugin documentation covers the setup.

## Which one to pick

Choosing between the two plugins depends on whether any device that runs your app lacks Play services, who builds the scanner UI, and whether a subscription fits your project. The three paths compare as follows:

| Path | Needs Google Play services | UI | Camera permission | Price |
| --- | --- | --- | --- | --- |
| ML Kit plugin, `startScan()` | No | Your own, behind the web view | Yes | Free |
| ML Kit plugin, `scan()` | Yes, on Android | Google code scanner | Not on Android | Free |
| Barcode Scanner plugin, all methods | No | Themeable fullscreen UI or embedded native preview | Yes, declared by the plugin | Insiders subscription |

Both bundled paths make the app larger by the amount in Google's table above. The `scan()` path keeps the app smaller, but it depends on Play services and on the module download before its first use. On devices that have Play services, it is also the shortest path, since there is no UI to build. Apps that read Wi-Fi codes, contact cards or driver licenses should factor in the ML Kit plugin's structured payload parsing.

## FAQ

### Does Capacitor barcode scanning work without Google Play services?

Yes, when the Android side uses the bundled ML Kit model, which is linked into the app at build time. The free Capacitor ML Kit Barcode Scanning plugin uses it for `startScan()` and `readBarcodesFromImage()`, and the Capacitor Barcode Scanner plugin uses it for every method. Only the ML Kit plugin's `scan()` needs Google Play services, because it runs the Google code scanner.

### Does the ML Kit plugin work on Huawei devices?

Yes, with `startScan()`. Our answer on GitHub is that "Huawei devices are supported but you have to use the `startScan(...)` method and build the UI yourself." The ready-made `scan()` screen comes from Google Play services, which Huawei phones released after May 2019 don't have.

### How much does the bundled model add to the app size?

Google puts the bundled barcode model at "About 2.4 MB size increase", compared to "About 200 KB size increase" for the unbundled variant. The Capacitor Barcode Scanner plugin's documentation describes the same cost as "a few megabytes".

### Do I still need the `DEPENDENCIES` meta-data?

Only if you use the ML Kit plugin's `scan()`. The `barcode_ui` value from the plugin's setup asks Google Play services to download the Google code scanner module while the app is installed from the Play Store; without it, Play services downloads the module the first time the scanner runs. The meta-data has no effect on `startScan()` and does nothing on devices without the Play Store. The Capacitor Barcode Scanner plugin needs no manifest changes at all.

### Does barcode scanning work offline?

Yes, once the model is on the device. Google's [ML Kit overview](https://developers.google.com/ml-kit){:target="_blank"} states that ML Kit processing happens on-device and "also works while offline". A bundled model is available from installation, while the unbundled model and the Google code scanner module have to be downloaded through Play services before their first use. On iOS, both plugins work offline, since the ML Kit iOS SDK comes as a pod with no Play services variant and the Capacitor Barcode Scanner plugin uses AVFoundation and Vision.

### Is the Capacitor Barcode Scanner plugin free?

No. It's part of the paid Capawesome [Insiders](../../insiders/index.md) subscription, which covers all Insiders plugins and priority support. The Capacitor ML Kit Barcode Scanning plugin and the Capacitor Google Play Services plugin are free.

## Related posts

- [Embedded Barcode Scanner with Camera Preview in Capacitor](./capacitor-embedded-barcode-scanner.md)
- [Publish a Capacitor App on Huawei AppGallery](./how-to-publish-a-capacitor-app-on-huawei-appgallery.md)
- [Announcing the Capacitor Document Scanner Plugin](./announcing-the-capacitor-document-scanner-plugin.md)
- [Announcing the Capacitor Background Geolocation Plugin](./announcing-the-capacitor-background-geolocation-plugin.md)

## Try Capawesome Cloud

One Android build can go to both Google Play and Huawei AppGallery through Capawesome Cloud destinations. The [Huawei AppGallery destination](../../cloud/app-store-publishing/destinations/huawei-appgallery.md) uploads the build to AppGallery Connect and, depending on the release status, submits it for review.

[Try Capawesome Cloud Free](https://capawesome.io){ .md-button .md-button--primary }

## Conclusion

Use the bundled model as soon as one device that runs your app ships without Google Play services. On the free plugin, that means `startScan()` with your own UI and a `getStatus()` check deciding where `scan()` is safe. If you'd rather not build and maintain a scanner screen, the [Capacitor Barcode Scanner plugin](../../sdks/capacitor/barcode-scanner.md) gives you the fullscreen UI and the embedded view on the bundled model. Keep `scan()` alone only when every device is Google-certified.

If you have any questions, join us on the [Capawesome Discord server](https://discord.gg/VCXxSVjefW){:target="_blank"}. To stay updated on the latest news, subscribe to the [Capawesome newsletter](https://capawesome.io/newsletter/){:target="_blank"}.
