Building a Flutter Barcode Scanner app

Building a Flutter πŸ’™ Barcode Scanner app in 2026 β€” the packages worth using today, what to migrate away from, and how to get scanning fast.

The use case is straightforward: scan a collection of barcoded inventory items, persist them to a database, optionally enrich each record with product metadata from a lookup API, then expose that inventory to other services such as a web dashboard. Flutter and Firebase remain an excellent fit for this kind of cross-platform, cloud-connected workflow β€” and in 2026 the ecosystem around barcode scanning has matured to the point where the old default recommendation no longer holds.

TL;DR β€” If you are starting a new project today, skip flutter_barcode_scanner and reach straight for mobile_scanner. It is actively maintained, performs better on modern hardware, and supports every Flutter deployment target. The rest of this article explains why, shows you the code, and covers the full stack from scan to cloud.

What's changed in 2026

A few years ago, flutter_barcode_scanner was the obvious first recommendation for any Flutter barcode project. That is no longer true, and it is worth being direct about why before getting into implementation details.

  • flutter_barcode_scanner has seen long quiet periods on GitHub. It still installs and runs on Android and iOS, but it does not support Flutter Web, macOS, Windows, or Linux, and it has not kept pace with improvements to the underlying platform ML pipelines.
  • mobile_scanner has become the community-preferred package. It uses Google's ML Kit on Android and Apple's Vision framework on iOS β€” both of which have improved substantially in recent years β€” and it tracks Flutter stable with regular releases. Version 5.x, current as of 2026, introduced a cleaner API and improved Web support via the BarcodeDetector browser API with a polyfill fallback.
  • Flutter itself is well into the 3.x series, with stable support for Android, iOS, Web, macOS, Windows, and Linux from a single codebase. Any package that doesn't support all six targets is increasingly a liability for teams that want deployment flexibility.
  • Dart 3 is the baseline. Null safety is no longer a talking point β€” it is simply assumed. Packages that predate it require workarounds that are not worth carrying into new projects.
  • iOS 18 and Android 15 are now the dominant platform versions in the field. Camera permission flows have been tightened on both platforms, and the Vision framework on iOS has received meaningful accuracy improvements for low-contrast and damaged barcodes.

The short version: mobile_scanner is where the momentum is. This article focuses on it, while keeping a section on flutter_barcode_scanner for teams maintaining older codebases.

The recommended approach: mobile_scanner

Add mobile_scanner to your pubspec.yaml:

dependencies:
  mobile_scanner: ^5.0.0

Run flutter pub get, then drop a scanner widget into your widget tree. A minimal but fully functional implementation looks like this:

import 'package:mobile_scanner/mobile_scanner.dart';

class BarcodeScannerPage extends StatelessWidget {
  const BarcodeScannerPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Scan barcode')),
      body: MobileScanner(
        onDetect: (capture) {
          for (final barcode in capture.barcodes) {
            debugPrint('Barcode found: ${barcode.rawValue}');
          }
        },
      ),
    );
  }
}

That is genuinely all you need to get a live scanning overlay running on Android or iOS. The MobileScanner widget handles camera lifecycle, permission state, and the ML pipeline internally. From there you can layer in additional controls as your use case demands.

Torch, zoom, and camera switching

Real-world inventory scanning often happens in warehouses or stockrooms where lighting is poor. mobile_scanner exposes torch control, zoom, and front/rear camera switching through a MobileScannerController:

class BarcodeScannerPage extends StatefulWidget {
  const BarcodeScannerPage({super.key});

  @override
  State<BarcodeScannerPage> createState() => _BarcodeScannerPageState();
}

class _BarcodeScannerPageState extends State<BarcodeScannerPage> {
  final MobileScannerController _controller = MobileScannerController(
    detectionSpeed: DetectionSpeed.noDuplicates,
    facing: CameraFacing.back,
    torchEnabled: false,
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Scan barcode'),
        actions: [
          IconButton(
            icon: const Icon(Icons.flashlight_on),
            onPressed: () => _controller.toggleTorch(),
          ),
          IconButton(
            icon: const Icon(Icons.flip_camera_ios),
            onPressed: () => _controller.switchCamera(),
          ),
        ],
      ),
      body: MobileScanner(
        controller: _controller,
        onDetect: (capture) {
          for (final barcode in capture.barcodes) {
            debugPrint('Barcode: ${barcode.rawValue}');
          }
        },
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

The DetectionSpeed.noDuplicates setting is particularly useful for inventory workflows β€” it prevents the same barcode from firing the callback repeatedly while it stays in frame, so you don't have to debounce scans yourself.

Supported barcode formats

mobile_scanner supports all formats you are likely to encounter in an inventory context: QR Code, EAN-13, EAN-8, UPC-A, UPC-E, Code 39, Code 93, Code 128, ITF, Codabar, Data Matrix, Aztec, and PDF417. You can restrict detection to a subset of formats to improve scan speed if your use case is narrow:

final controller = MobileScannerController(
  formats: [BarcodeFormat.ean13, BarcodeFormat.ean8, BarcodeFormat.code128],
);

iOS and Android platform setup

Both platforms require explicit camera permission declarations before the scanner will work.

iOS

Add a camera usage description to ios/Runner/Info.plist:

<key>NSCameraUsageDescription</key>
<string>This app uses the camera to scan barcodes.</string>

On iOS 17 and later, the Vision framework's barcode detection has improved noticeably β€” codes that previously required deliberate slow movement are now caught on the first pass in most lighting conditions. iOS 18 tightened the permission prompt UI, so ensure your usage description is clear and user-facing; vague strings are more likely to prompt user denial.

Android

Add the camera permission to android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA" />

On Android 14 and 15, the runtime permission dialog is presented at first camera use. mobile_scanner handles the request automatically, but if you want to prompt the user before opening the scanner β€” for example, to explain why the camera is needed β€” use the permission_handler package to check and request the permission explicitly beforehand.

Enriching scan results with a product lookup API

A raw barcode value is only the starting point. For an inventory app, you want to resolve that value to a human-readable product record β€” name, brand, category, image β€” without requiring the operator to type anything. Several APIs handle this well in 2026.

  • Open Food Facts API β€” free, open, no API key required for read access. Returns product name, brand, ingredients, nutritional data, and images. The v2 API is stable and well-documented. Ideal for food and grocery inventory.
  • Barcode Lookup β€” broader product category coverage beyond food. Free tier available with rate limits; paid plans for higher-volume use.
  • Open Product Data / GS1 β€” for industrial or retail supply-chain contexts where GS1-registered GTINs are in use, the GS1 registry provides authoritative product data directly from brand owners.

A Dart HTTP call to the Open Food Facts v2 API for a scanned EAN-13 confirms the approach works end-to-end:

GET https://world.openfoodfacts.org/api/v2/product/5000157062697.json

β†’ product_name: "Classic Cream of Mushroom Soup"
β†’ brands: "Campbell's"
β†’ quantity: "400g"

In Dart, a minimal lookup function using the http package looks like this:

import 'dart:convert';
import 'package:http/http.dart' as http;

Future<Map<String, dynamic>?> lookupBarcode(String barcode) async {
  final uri = Uri.parse(
    'https://world.openfoodfacts.org/api/v2/product/$barcode.json',
  );
  final response = await http.get(uri);
  if (response.statusCode == 200) {
    final data = jsonDecode(response.body) as Map<String, dynamic>;
    if (data['status'] == 1) {
      return data['product'] as Map<String, dynamic>;
    }
  }
  return null;
}

Persisting to Firebase

Saving scanned and enriched inventory items to Cloud Firestore gives you real-time sync, offline support, and a backend that any other service β€” a web dashboard, a reporting tool, a fulfilment system β€” can read from directly. The cloud_firestore Flutter plugin is mature, fully null-safe, and tracks Flutter stable.

Future<void> saveInventoryItem({
  required String barcode,
  required Map<String, dynamic> product,
}) async {
  await FirebaseFirestore.instance.collection('inventory').add({
    'barcode': barcode,
    'productName': product['product_name'] ?? 'Unknown',
    'brand': product['brands'] ?? 'Unknown',
    'quantity': product['quantity'] ?? '',
    'scannedAt': FieldValue.serverTimestamp(),
  });
}

A Flutter Web dashboard reading from the same Firestore collection in real time closes the loop on the original project requirement β€” one scan on a mobile device, immediately visible on a browser dashboard elsewhere, with no custom backend to maintain.

For teams that prefer a non-Google backend in 2026, Supabase is a strong alternative. The supabase_flutter plugin is well-maintained and the Supabase free tier is generous for inventory-scale workloads. The integration pattern is essentially the same β€” replace the Firestore call with a Supabase table insert.

The older approach: flutter_barcode_scanner

For teams maintaining an existing codebase built on flutter_barcode_scanner, here is the context you need.

The package still installs cleanly on current Flutter toolchains and still works for Android and iOS targets. If your project is pinned to it and you need to ship quickly, it will not block you. The integration involves forking the repository as a precaution against upstream stalls β€” which, as noted, have occurred β€” and referencing your fork in pubspec.yaml:

dependencies:
  flutter_barcode_scanner:
    git:
      url: https://github.com/your-username/flutter_barcode_scanner.git
      ref: main

The scanning overlay and result callback work as documented. For a simple Android or iOS project with no Web or desktop requirement, it remains functional. But the migration path to mobile_scanner is not onerous β€” the API surface is similar enough that a motivated developer can make the switch in an afternoon β€” and the long-term maintenance picture strongly favours moving.

The console output for a scanned EAN-13 barcode is the same either way:

I/flutter (31686): 5000157062697

Choosing between mobile_scanner and google_mlkit_barcode_scanning

A third option worth knowing about is google_mlkit_barcode_scanning β€” Google's ML Kit barcode plugin for Flutter, maintained by the Google ML Kit team. It gives you direct access to the ML Kit barcode scanning pipeline without the camera widget layer that mobile_scanner includes.

Use mobile_scanner when you want a complete, ready-to-use scanning widget with camera management included β€” which covers the majority of inventory app use cases. Use google_mlkit_barcode_scanning when you need to process images from a source other than the device camera β€” for example, scanning barcodes in photos from the gallery, or processing frames from a custom camera implementation. The two packages are complementary rather than competing.

Summary

mobile_scanner is the right starting point for any new Flutter barcode project in 2026. It is actively maintained, performs well on current Android and iOS hardware, supports all six Flutter deployment targets, and requires very little boilerplate to get a working scanner in front of users. The broader stack β€” Firestore or Supabase for persistence, Open Food Facts or Barcode Lookup for product enrichment, Flutter Web for a real-time dashboard β€” is mature and well-supported.

flutter_barcode_scanner still runs on Android and iOS and is not an emergency migration if you have an existing project depending on it. But for anything new, it is the wrong starting point, and the case for migrating existing projects grows stronger as Flutter's multi-platform ambitions expand.

Scan, enrich, persist, expose. The workflow is simple. The tools in 2026 are better than they have ever been.


See also: