DEV3 Technologies home

Barcode scan pick list

Checklist for warehouse or store picking where scanning a barcode ticks the matching line and flags wrong or extra items.

FlutterUtilitiesflutterbarcodescannerpicking

Code

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

class PickLine {
  PickLine({required this.barcode, required this.name, required this.needed});
  final String barcode;
  final String name;
  final int needed;
  int picked = 0;
  bool get complete => picked >= needed;
}

enum ScanResult { picked, alreadyComplete, unknown }

class PickList extends ChangeNotifier {
  PickList(List<PickLine> lines) : lines = List.unmodifiable(lines);
  final List<PickLine> lines;

  bool get allDone => lines.every((l) => l.complete);

  ScanResult scan(String code) {
    final match = lines.where((l) => l.barcode == code);
    if (match.isEmpty) return ScanResult.unknown;
    final line = match.first;
    if (line.complete) return ScanResult.alreadyComplete;
    line.picked++;
    notifyListeners();
    return ScanResult.picked;
  }
}

class PickScreen extends StatefulWidget {
  const PickScreen({super.key, required this.list});
  final PickList list;

  @override
  State<PickScreen> createState() => _PickScreenState();
}

class _PickScreenState extends State<PickScreen> {
  String? _last;
  DateTime _lastAt = DateTime(0);

  void _onDetect(BarcodeCapture capture) {
    final code = capture.barcodes.firstOrNull?.rawValue;
    if (code == null) return;
    final now = DateTime.now();
    // The camera reports the same code many times per second; ignore repeats.
    if (code == _last && now.difference(_lastAt) < const Duration(seconds: 2)) return;
    _last = code;
    _lastAt = now;
    final r = widget.list.scan(code);
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(
      duration: const Duration(seconds: 1),
      content: Text(switch (r) {
        ScanResult.picked => 'Picked',
        ScanResult.alreadyComplete => 'Line already complete',
        ScanResult.unknown => 'Not on this list',
      }),
    ));
  }

  @override
  Widget build(BuildContext context) {
    return Column(children: [
      SizedBox(height: 220, child: MobileScanner(onDetect: _onDetect)),
      Expanded(
        child: ListenableBuilder(
          listenable: widget.list,
          builder: (context, _) => ListView(children: [
            for (final l in widget.list.lines)
              CheckboxListTile(
                value: l.complete,
                onChanged: null,
                title: Text(l.name),
                subtitle: Text('${l.picked} of ${l.needed}'),
              ),
          ]),
        ),
      ),
    ]);
  }
}

Needs: mobile_scanner@^6.0.0

How it works

What it solves

Order pickers and stock counters need to confirm that the right item went into the bag. A visual checklist plus camera scanning catches wrong products before they leave the shelf.

How it works

PickList holds lines with an expected barcode and quantity. scan returns one of three outcomes: picked, already complete, or unknown. Only the first mutates state, and the model notifies listeners so the checklist redraws.

PickScreen shows the camera preview above the list. The scanner emits detections continuously, so a two second repeat filter on the last code avoids counting a single scan five times. A short snackbar tells the picker what happened, using a switch expression over the result enum.

Adapting it

Load lines from your order payload and call allDone to enable a confirm button. For items sold by weight, replace the counter with a manual quantity dialog. Add haptic feedback with HapticFeedback.mediumImpact() on success and a distinct vibration for unknown codes.

Pitfalls

  • Camera permission must be declared in the iOS and Android manifests before the preview works.
  • Some products carry several barcodes; map them to one line before comparing.
  • Dispose of the scanner by leaving the screen rather than hiding it, or the camera stays on.

How to use it

Add mobile_scanner to pubspec.yaml and declare the camera usage text on iOS and the camera permission on Android.

final list = PickList([PickLine(barcode: '4006381333931', name: 'Pens', needed: 2)]);
PickScreen(list: list)

Licence: DEV3 Snippet Licence. Free to use, change and ship in personal and commercial projects. Attribution is appreciated, not required. Please do not redistribute snippets as a collection.

Related snippets