ai_barcode_scanner 8.1.0 copy "ai_barcode_scanner: ^8.1.0" to clipboard
ai_barcode_scanner: ^8.1.0 copied to clipboard

A universal AI barcode and QR code scanner for Flutter based on MLKit. Uses CameraX on Android, AVFoundation on iOS and Apple Vision & AVFoundation on macOS.

example/lib/main.dart

import 'package:ai_barcode_scanner/ai_barcode_scanner.dart';
import 'package:flutter/material.dart';

import 'demos/batch_scan_page.dart';
import 'demos/embedded_scanner_page.dart';
import 'demos/full_control_page.dart';
import 'demos/sample_images.dart';
import 'demos/themed_scanner_page.dart';

void main() => runApp(const ExampleApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'AI Barcode Scanner',
      debugShowCheckedModeBanner: false,
      themeMode: ThemeMode.system,
      theme: ThemeData(
        colorSchemeSeed: const Color(0xFF0A84FF),
        useMaterial3: true,
      ),
      darkTheme: ThemeData(
        colorSchemeSeed: const Color(0xFF0A84FF),
        brightness: Brightness.dark,
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  Barcode? _lastBarcode;
  String? _note;

  void _showResult(Barcode? barcode, {String? note}) {
    if (!mounted) return;
    setState(() {
      _lastBarcode = barcode;
      _note = note;
    });
  }

  @override
  Widget build(BuildContext context) {
    final support = ScannerPlatformSupport.current;

    return Scaffold(
      appBar: AppBar(
        title: const Text('AI Barcode Scanner'),
        actions: <Widget>[
          IconButton(
            tooltip: 'Platform support',
            icon: const Icon(Icons.info_outline),
            onPressed: () => _showSupportSheet(context),
          ),
        ],
      ),
      body: ListView(
        padding: const EdgeInsets.fromLTRB(16, 8, 16, 32),
        children: <Widget>[
          _ResultCard(barcode: _lastBarcode, note: _note),
          const SizedBox(height: 8),
          if (!support.isSupported)
            Card(
              color: Theme.of(context).colorScheme.errorContainer,
              child: ListTile(
                leading: const Icon(Icons.warning_amber_outlined),
                title: Text(
                  'Scanning is not available on '
                  '${ScannerPlatformSupport.currentPlatformName}',
                ),
                subtitle: const Text(
                  'The demos still open, and show the built-in '
                  'unsupported-platform screen.',
                ),
              ),
            ),
          const _SectionHeader('One-liners'),
          _DemoTile(
            icon: Icons.bolt_outlined,
            title: 'showAiBarcodeScanner',
            subtitle: 'Open, scan one code, come back with the result.',
            onTap: () async {
              final capture = await showAiBarcodeScanner(context);
              _showResult(capture?.firstBarcode);
            },
          ),
          _DemoTile(
            icon: Icons.qr_code_2_outlined,
            title: 'QR codes only',
            subtitle:
                'Restricting formats is the cheapest accuracy win there is.',
            onTap: () async {
              final capture = await showAiBarcodeScanner(
                context,
                formats: BarcodeFormatSets.qrOnly,
              );
              _showResult(capture?.firstBarcode);
            },
          ),
          _DemoTile(
            icon: Icons.shopping_bag_outlined,
            title: 'Retail barcodes',
            subtitle: 'EAN, UPC and Code 128, with a wide reticle.',
            onTap: () async {
              final capture = await showAiBarcodeScanner(
                context,
                formats: BarcodeFormatSets.retail,
                scanWindowConfig: const ScanWindowConfig(
                  shape: ScanWindowShape.wide,
                ),
              );
              _showResult(capture?.firstBarcode);
            },
          ),
          _DemoTile(
            icon: Icons.link_outlined,
            title: 'Validated scan',
            subtitle:
                'Only accepts links to pub.flutter-io.cn; anything else flashes red.',
            onTap: () async {
              final capture = await showAiBarcodeScanner(
                context,
                validator: ScanValidators.url(
                  allowedHosts: const <String>{'pub.flutter-io.cn'},
                ),
                labels: const ScannerLabels(scanHint: 'Scan a pub.flutter-io.cn link'),
              );
              _showResult(capture?.firstBarcode);
            },
          ),
          _DemoTile(
            icon: Icons.image_search_outlined,
            title: 'Scan image bytes',
            subtitle:
                'A gallery picker that returns bytes — which works on the web '
                'too.',
            onTap: () async {
              final capture = await showAiBarcodeScanner(
                context,
                labels: const ScannerLabels(galleryButton: 'Scan a sample'),
                galleryImagePicker: _pickSampleImage,
                onGalleryScanError: (error, _) {
                  if (!mounted) return;
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(
                      content: Text('Could not read that image: $error'),
                    ),
                  );
                },
              );
              _showResult(capture?.firstBarcode);
            },
          ),
          const _SectionHeader('Scan modes'),
          _DemoTile(
            icon: Icons.playlist_add_check_outlined,
            title: 'Batch collection',
            subtitle: 'Collect up to 10 distinct codes in one session.',
            onTap: () async {
              final barcodes = await Navigator.of(context).push<List<Barcode>>(
                MaterialPageRoute<List<Barcode>>(
                  builder: (_) => const BatchScanPage(),
                ),
              );
              if (barcodes == null || barcodes.isEmpty) return;
              _showResult(
                barcodes.first,
                note: '${barcodes.length} codes collected',
              );
            },
          ),
          _DemoTile(
            icon: Icons.all_inclusive,
            title: 'Continuous scanning',
            subtitle: 'Stays open and reports every code, once per second.',
            onTap:
                () => _openScanner(
                  AiBarcodeScanner(
                    scanMode: ScanMode.continuous,
                    scanCooldown: const Duration(seconds: 1),
                    enabledActionButtons: const <ScannerAction>{
                      ScannerAction.torch,
                      ScannerAction.cameraSwitch,
                      ScannerAction.close,
                    },
                    galleryButtonType: GalleryButtonType.none,
                    onDetect: (capture) => _showResult(capture.firstBarcode),
                  ),
                ),
          ),
          const _SectionHeader('UI and UX'),
          _DemoTile(
            icon: Icons.palette_outlined,
            title: 'Themed scanner',
            subtitle: 'Brand colours, custom copy and a full-border reticle.',
            onTap: () => _openScanner(ThemedScannerPage(onResult: _showResult)),
          ),
          _DemoTile(
            icon: Icons.crop_free,
            title: 'Embedded scanner',
            subtitle: 'A scanner inside your own page, with live results.',
            onTap:
                () => _openScanner(EmbeddedScannerPage(onResult: _showResult)),
          ),
          _DemoTile(
            icon: Icons.tune,
            title: 'Every control',
            subtitle: 'Torch, flip, lens, zoom, gallery, highlights and more.',
            onTap: () => _openScanner(FullControlPage(onResult: _showResult)),
          ),
          _DemoTile(
            icon: Icons.accessibility_new_outlined,
            title: 'Minimal and quiet',
            subtitle:
                'No animation, no blur, no haptics — cheap to render, calm to use.',
            onTap:
                () => _openScanner(
                  AiBarcodeScanner(
                    overlayConfig: const ScannerOverlayConfig.minimal(),
                    feedback: const ScannerFeedbackConfig.silent(),
                    showScanHint: false,
                    galleryButtonType: GalleryButtonType.none,
                    enabledActionButtons: const <ScannerAction>{
                      ScannerAction.close,
                    },
                    onDetect: (capture) {
                      _showResult(capture.firstBarcode);
                      Navigator.of(context).pop();
                    },
                  ),
                ),
          ),
        ],
      ),
    );
  }

  /// Stands in for a file picker that hands out bytes, as most do on the web.
  ///
  /// Anything that yields an encoded image works the same way: wrap bytes in
  /// `ScannerImage.bytes`, an `XFile` in `ScannerImage.xFile`, or a file path
  /// in `ScannerImage.path`. Returning `null` means the user cancelled.
  Future<ScannerImage?> _pickSampleImage(BuildContext context) {
    return showDialog<ScannerImage>(
      context: context,
      builder:
          (context) => SimpleDialog(
            title: const Text('Pick a sample image'),
            children: <Widget>[
              SimpleDialogOption(
                onPressed:
                    () => Navigator.of(context).pop(
                      ScannerImage.bytes(
                        sampleQrPng,
                        name: 'sample_qr.png',
                        mimeType: 'image/png',
                      ),
                    ),
                child: const Text('QR code'),
              ),
              SimpleDialogOption(
                onPressed:
                    () => Navigator.of(context).pop(
                      ScannerImage.bytes(
                        sampleEan13Png,
                        name: 'sample_ean13.png',
                        mimeType: 'image/png',
                      ),
                    ),
                child: const Text('EAN-13 barcode'),
              ),
            ],
          ),
    );
  }

  Future<void> _openScanner(Widget scanner) {
    return Navigator.of(
      context,
    ).push(MaterialPageRoute<void>(builder: (_) => scanner));
  }

  void _showSupportSheet(BuildContext context) {
    final support = ScannerPlatformSupport.current;
    showModalBottomSheet<void>(
      context: context,
      builder:
          (context) => SafeArea(
            child: ListView(
              shrinkWrap: true,
              padding: const EdgeInsets.all(20),
              children: <Widget>[
                Text(
                  'Capabilities on ${ScannerPlatformSupport.currentPlatformName}',
                  style: Theme.of(context).textTheme.titleMedium,
                ),
                const SizedBox(height: 12),
                for (final entry
                    in <String, bool>{
                      'Camera scanning': support.isSupported,
                      'Scan from gallery': support.analyzeImage,
                      'Scan window': support.scanWindow,
                      'Torch': support.torch,
                      'Zoom': support.zoom,
                      'Tap to focus': support.tapToFocus,
                      'Lens selection': support.lensType,
                      'Auto zoom': support.autoZoom,
                      'Invert image': support.invertImage,
                      'Frame bytes': support.returnImage,
                      'Barcode geometry': support.barcodeCorners,
                      'Web reader choice': support.webBarcodeReader,
                    }.entries)
                  ListTile(
                    dense: true,
                    leading: Icon(
                      entry.value
                          ? Icons.check_circle_outline
                          : Icons.remove_circle_outline,
                      color: entry.value ? Colors.green : Colors.grey,
                    ),
                    title: Text(entry.key),
                  ),
              ],
            ),
          ),
    );
  }
}

class _SectionHeader extends StatelessWidget {
  const _SectionHeader(this.title);

  final String title;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.fromLTRB(4, 20, 4, 8),
      child: Text(
        title.toUpperCase(),
        style: Theme.of(context).textTheme.labelMedium?.copyWith(
          letterSpacing: 1.2,
          color: Theme.of(context).colorScheme.primary,
        ),
      ),
    );
  }
}

class _DemoTile extends StatelessWidget {
  const _DemoTile({
    required this.icon,
    required this.title,
    required this.subtitle,
    required this.onTap,
  });

  final IconData icon;
  final String title;
  final String subtitle;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.only(bottom: 8),
      child: ListTile(
        leading: Icon(icon),
        title: Text(title),
        subtitle: Text(subtitle),
        trailing: const Icon(Icons.chevron_right),
        onTap: onTap,
      ),
    );
  }
}

class _ResultCard extends StatelessWidget {
  const _ResultCard({required this.barcode, this.note});

  final Barcode? barcode;
  final String? note;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final code = barcode;

    if (code == null) {
      return Card(
        child: Padding(
          padding: const EdgeInsets.all(20),
          child: Row(
            children: <Widget>[
              const Icon(Icons.qr_code_scanner, size: 32),
              const SizedBox(width: 16),
              Expanded(
                child: Text(
                  'Pick a demo below to scan something.',
                  style: theme.textTheme.bodyMedium,
                ),
              ),
            ],
          ),
        ),
      );
    }

    return Card(
      color: theme.colorScheme.primaryContainer,
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Row(
              children: <Widget>[
                Icon(code.typeIcon, size: 20),
                const SizedBox(width: 8),
                Text(code.typeLabel, style: theme.textTheme.titleSmall),
                const Spacer(),
                Text(
                  code.format.displayName,
                  style: theme.textTheme.labelSmall,
                ),
              ],
            ),
            const SizedBox(height: 10),
            SelectableText(
              code.bestValue.isEmpty ? '(empty)' : code.bestValue,
              style: theme.textTheme.bodyLarge,
            ),
            // The structured payload — Wi-Fi credentials, a contact card, a
            // calendar event — comes straight from ML Kit; `fields` just makes
            // it presentable.
            for (final field in code.fields)
              Padding(
                padding: const EdgeInsets.only(top: 8),
                child: Text(
                  '${field.label}: '
                  '${field.obscure ? '••••••' : field.value}',
                  style: theme.textTheme.bodySmall,
                ),
              ),
            if (note != null) ...<Widget>[
              const SizedBox(height: 10),
              Text(note!, style: theme.textTheme.labelMedium),
            ],
          ],
        ),
      ),
    );
  }
}
247
likes
160
points
11.7k
downloads
screenshot

Documentation

API reference

Publisher

verified publishermohesu.com

Weekly Downloads

A universal AI barcode and QR code scanner for Flutter based on MLKit. Uses CameraX on Android, AVFoundation on iOS and Apple Vision & AVFoundation on macOS.

Homepage
Repository (GitHub)
View/report issues

Topics

#qr-code #barcode #scanner #camera #mobile-scanner

License

Apache-2.0 (license)

Dependencies

flutter, image_picker, mobile_scanner, web

More

Packages that depend on ai_barcode_scanner