scan_tally

Continuous barcode scanning for stock counts, goods receiving and shipping. During a count the user scans dozens of items in a row. scan_tally turns the raw reads of any scanner into counts:

  • it filters duplicates;
  • it gives feedback that works without looking at the screen;
  • it handles hardware scanners that type like a keyboard;
  • it keeps quantities with undo;
  • it parses GS1 data.

The core has no camera dependency. The camera adapter is a separate package, scan_tally_mobile_scanner.

Türkçe açıklama aşağıda.

Features

  • Duplicate filtering (DedupePolicy):

    • leaveAndReturn: a code counts again only after it has left the view. This is the natural way to count several items of the same product with a camera.
    • cooldown: the same code within X ms is a duplicate.
    • FrameConfirmation: a code must be read in N consecutive frames, so a single misread from a blurry image never counts.
    • EAN-13, EAN-8, UPC-A and UPC-E check digit validation (CheckDigit).
  • Counting session (ScanSession):

    • quantity per code; increment, decrement, set by hand, remove, clear;
    • undoLast() for any of these, scans included;
    • your own validator (e.g. "product not found"), synchronous or async;
    • toJson() / loadJson(); you choose where to store it;
    • an event stream: ScanAccepted, ScanDuplicate, ScanRejected.
  • Feedback:

    • separate sound and vibration patterns for accepted, duplicate and rejected;
    • a short colored screen flash (ScanFlash);
    • silent mode.

    The beeps are generated by this package (see Sounds).

  • One API, any source (ScanSource):

    • camera via scan_tally_mobile_scanner;
    • keyboard wedge scanners (Zebra, Honeywell, USB/Bluetooth HID) via KeyboardWedgeSource. Typed text never becomes a scan.
  • GS1 parsing (Gs1Parser):

    • GS1-128, GS1 DataMatrix, GS1 QR and DataBar element strings, and the (01)…(17)… human readable form;
    • GTIN (01), lot (10), expiry (17), best before (15), serial (21), count (30/37), SSCC (00), measures (310n…) and more than 150 other AIs;
    • FNC1/GS separators, variable-length fields, the day 00 rule and the century rule for dates.

How it differs from existing packages

Need Camera scanner plugins (mobile_scanner, barcode_scan2, simple_barcode_scanner) Wedge listeners (flutter_barcode_listener, barcode_keyboard_listener) flutter_datawedge gs1_barcode_parser scan_tally
Decode barcodes from the camera yes – – – via adapter
Count once per item / per cooldown – (mobile_scanner has noDuplicates, which never counts the same product twice in a row) – – – yes
N-frame confirmation, check digits – – – – yes
Counting session with undo and JSON – – – – yes
Keyboard wedge without vendor SDK – yes (last releases 2023 and 2019) Zebra intents only – yes, with prefix, suffix, custom terminator, TextField safety and an injectable clock
GS1 AI parsing – – – yes yes (integrated with counting)
Feedback patterns, flash, silent mode – – – – yes

The other packages are good at what they do. scan_tally sits on top of a decoder and solves what every inventory app writes again: what counts, and how does the user know?

Installation

dependencies:
  scan_tally: ^0.1.0
  scan_tally_mobile_scanner: ^0.1.0 # only if you use the camera

Usage

import 'package:scan_tally/scan_tally_flutter.dart';

final session = ScanSession(
  dedupe: const DedupePolicy.cooldown(Duration(milliseconds: 1500)),
  confirmation: const FrameConfirmation(frames: 2),
  feedback: const ScanFeedback(sound: true, vibrate: true),
  feedbackPlayer: const FlutterScanFeedbackPlayer(),
  validate: (code) => products.containsKey(code.value)
      ? ScanVerdict.accept
      : const ScanVerdict.reject('Product not found'),
);

session.events.listen((event) {
  switch (event) {
    case ScanAccepted(:final entry):
      print('${entry.key}: ${entry.quantity}');
    case ScanDuplicate():
      print('already counted');
    case ScanRejected(:final message):
      print('rejected: $message');
  }
});

session.counts;      // {'4006381333931': 3, ...}
session.undoLast();  // reverts the last scan or manual change
session.setQuantity('4006381333931', 12);
final json = session.toJson(); // store it where you like

Each read goes through four steps, in this order:

  1. Check digit. A wrong one from a keyboard scanner is a ScanRejected. From a camera it is dropped silently as a misread, because the next frame usually reads it correctly.
  2. Frame confirmation (cameras only).
  3. Duplicate filter. The result is either silent or a ScanDuplicate.
  4. Your validator. The result is a ScanRejected or a ScanAccepted.

Frames are processed one at a time in arrival order, also with an async validator.

Camera

import 'package:scan_tally_mobile_scanner/scan_tally_mobile_scanner.dart';

final controller = MobileScannerSource.createController(); // DetectionSpeed.normal, 150 ms
session.attach(MobileScannerSource(controller));
// in build: ScanFlash(session: session, child: MobileScanner(controller: controller))

Keyboard wedge (handheld terminal)

final wedge = KeyboardWedgeSource(
  config: const WedgeConfig(
    maxKeyInterval: Duration(milliseconds: 50),
    terminators: {WedgeTerminator.enter},
    prefix: '',     // optional
    suffix: '',     // optional
    terminatorCharacters: {}, // e.g. {'#'} for a custom end character
  ),
);
session.attach(wedge);

// Around the counting screen; starts and stops the listener with the widget.
KeyboardWedgeListener(source: wedge, child: const CountScreen());

Counting GS1 cartons under their GTIN

validate: (code) {
  final gs1 = code.gs1; // null for non-GS1 codes
  if (gs1?.gtin == null) return ScanVerdict.accept;
  return ScanVerdict.acceptWith(key: gs1!.gtin, quantity: gs1.count ?? 1);
},
final data = Gs1Parser.parse(']C10109501101530003172612001' '0LOT42\u001D3712');
data.gtin;     // 09501101530003
data.expiry;   // 2026-12-31 (day 00 = end of month), dayUnspecified: true
data.batch;    // LOT42
data.count;    // 12

Design decisions and risks

1. Camera dependency: core plus adapter

Decision: one repository with two packages in a Dart pub workspace (no melos):

Package Contains Dependencies
scan_tally dedupe, session, GS1, check digits, wedge assembler, tones, feedback Flutter SDK only
scan_tally_mobile_scanner MobileScannerSource (≈ 100 lines) scan_tally, mobile_scanner
  • package:scan_tally/scan_tally.dart is pure Dart: it imports nothing from Flutter. Use it in isolates, on a server or in plain unit tests.
  • package:scan_tally/scan_tally_flutter.dart adds the Flutter layer: wedge listener, feedback player, flash and builder.

Why:

  • Camera plugins change often (mobile_scanner changed its API several times). Apps that only use hardware scanners should not ship a camera plugin, ML Kit and camera permissions.
  • A source is just a stream of ScanFrames (abstract interface class ScanSource), so another decoder takes a few lines to connect. See ScanSourceController or the adapter's source.

Considered: a third package for the Flutter layer, so the core would not depend on the Flutter SDK. It was rejected for 0.1: the Flutter layer is small, uses no plugins, and a separate entry point already keeps the logic Flutter-free. It can be split later without breaking the pure library.

2. Keyboard wedge: tested without a real device

Decision: the wedge logic is a pure state machine (WedgeAssembler) whose time is passed in, so tests control the clock.

How a scan is told apart from typing:

  • A scan is a burst of keys, each at most maxKeyInterval apart (default 50 ms), that ends with a terminator (Enter, Tab or a custom character) within the same interval.
  • A person types at 100 ms or more per key. Enter pressed after slow typing therefore drops the burst.
  • With a prefix or suffix configured, bursts without them are not scans.
  • idleTimeout supports scanners that send no terminator.

Interplay with the app:

  • KeyboardWedgeSource listens to HardwareKeyboard.
  • The first character of a burst cannot be told apart from typing, so it passes through. The rest of the burst is consumed.
  • The terminator is consumed too. KeyboardWedgeListener also stops it at the focus system, so the scanner's Enter does not press a focused button or move focus.

TextField safety:

  • While an EditableText (TextField) has focus, the listener stands aside (ignoreWhenEditing: true, the default).
  • Typed text is never taken for a scan, and a scan made into a focused field lands there, as users of hardware scanners expect.
  • Set ignoreWhenEditing: false to capture scans everywhere.

Timing:

  • Key event timestamps are used when the platform provides them, so a busy UI thread does not split a scan.
  • Otherwise a monotonic Stopwatch (injectable) is used.

Tests:

  • unit tests: fast and slow typing, inclusive gap limit, Enter, Tab, custom and control terminators, prefix and suffix, min and max length, cut-off scans, idle timeout, GS characters;
  • widget tests that send real key events through HardwareKeyboard: Shift + capitals, Enter not pressing a focused button, a focused TextField receiving the text instead, enable/disable, idle timer, flow into a ScanSession.

Not verified on a real Zebra or Honeywell device. The behavior is verified with simulated key events in tests, and with Android key events injected by adb shell input keyevent on an emulator: they were joined into one scan at the default 50 ms limit. Please report results with your device model.

Recommended device settings:

Zebra DataWedge Honeywell (Scanner settings / Data Processing)
Output Profile for your app → Keystroke output: on, Intent output: off Wedge method: Keyboard (also "Data Intent" off)
Terminator Keystroke output → Basic data formatting → Send ENTER key (or Send TAB) Suffix: Enter (\r / 0D), or Tab (09)
Speed Keep the default inter-character delay (0 ms); if you add a delay, raise maxKeyInterval above it Keep the default wedge speed (fast)
Prefix/suffix (optional) Basic data formatting → prefix/suffix to data Prefix / Suffix fields
GS1 Send FNC1 as GS (\u001D); Advanced data formatting can replace it "Transmit GS / FNC1" or a replacement character (use Gs1Parser.parse(..., separators: {'~'}))

Both brands can also deliver scans as an Android broadcast intent (DataWedge Intent output, Honeywell Data Intent). This is more robust than keystrokes, and some packages such as flutter_datawedge use it. Intent support is planned for a later version as another ScanSource.

3. Sounds without license issues

Decision: the beeps are synthesized by this package.

  • ScanTones defines sine tones:
    • accepted: one 2 kHz beep, 110 ms;
    • duplicate: two 1.2 kHz beeps;
    • rejected: a falling 620 → 440 Hz buzz.
  • ScanTones.renderWav() writes 16-bit mono PCM WAV with short fades.
  • tool/generate_sounds.dart writes assets/sounds/*.wav from these definitions, and a test checks that the committed files match the generator byte for byte. The sounds are therefore this package's own work under its MIT license.
  • You can also call ScanTones.wavOf(kind) at runtime.
cd packages/scan_tally && dart run tool/generate_sounds.dart

Playing sound:

  • The core has no audio dependency. Sound is behind the ScanSoundPlayer interface (and ScanFeedbackPlayer for everything).
  • The dependency-free default is SystemScanSoundPlayer. It uses system click/alert sounds; on Android only the click exists, and only when touch sounds are on.
  • For real beeps, play the bundled WAVs with an audio plugin. audioplayers in low latency mode (Android SoundPool) is light, maintained and suits short sounds:
class AudioplayersScanSoundPlayer implements ScanSoundPlayer {
  final _players = {
    for (final k in ScanFeedbackKind.values)
      k: AudioPlayer()
        ..audioCache = AudioCache(prefix: '')
        ..setPlayerMode(PlayerMode.lowLatency),
  };

  @override
  Future<void> play(ScanFeedbackKind kind) async {
    final p = _players[kind]!;
    await p.stop();
    await p.play(AssetSource(ScanTones.assetKeyOf(kind)));
  }
}

// FlutterScanFeedbackPlayer(sound: AudioplayersScanSoundPlayer())

Vibration:

  • Flutter's built-in HapticFeedback is used (FlutterScanHaptics). It needs no plugin and no permission.
  • Default patterns:
    • accepted: one medium pulse;
    • duplicate: two light pulses;
    • rejected: three long vibrations.
  • It follows the system "touch feedback" setting. For stronger patterns felt through gloves, implement ScanHaptics with a vibration plugin.

Silent mode (ScanFeedback.silent) mutes sound but keeps vibration and flash, like a phone's silent switch.

Example

example/ is an Android app in English and Turkish, built with Material 3. It has four parts:

  • Camera tab.
  • Handheld tab:
    • simulated scanner buttons that send real key events;
    • a keyboard demo mode that accepts slow typing, for trying it on an emulator;
    • a manual entry field that shows typed text is never a scan.
  • Count list: +/−, set quantity, swipe to remove, clear, export JSON.
  • Undo and GS1: undo in the app bar, and a breakdown of GS1 data for the last scan.

Limitations

  • Keyboard wedge is not tested on real Zebra or Honeywell devices (see above). Android intent output is not supported yet.
  • The camera adapter has been compiled and unit tested, but not tried on a device in this release.
  • The duplicate filter state is not saved by toJson(). After a restore, the first read of a code counts as new.

Other packages

More packages by the same author:

License

MIT. The bundled sounds are generated by tool/generate_sounds.dart and are covered by the same license.


Türkçe

Stok sayımı, mal kabul ve sevkiyat için sürekli barkod okutma paketi. Sayımda kullanıcı art arda onlarca ürün okutur. scan_tally herhangi bir tarayıcının ham okumalarını adede çevirir:

  • tekrarları ayıklar;
  • ekrana bakmadan anlaşılan geri bildirim verir;
  • klavye gibi çalışan el terminallerini destekler;
  • adetleri geri alınabilir biçimde tutar;
  • GS1 verisini çözer.

Çekirdeğin kamera bağımlılığı yoktur. Kamera adaptörü ayrı bir pakettir: scan_tally_mobile_scanner.

Özellikler

  • Tekrar ayıklama (DedupePolicy):

    • leaveAndReturn: kod ancak görüntüden çıkıp geri gelince yeniden sayılır. Kamerayla aynı üründen birden fazla saymanın doğal yolu budur.
    • cooldown: aynı kod X ms içinde tekrar gelirse tekrar sayılır.
    • FrameConfirmation: kod art arda N karede görülmelidir. Böylece bulanık görüntüden gelen tek bir yanlış okuma asla sayılmaz.
    • EAN-13, EAN-8, UPC-A ve UPC-E kontrol hanesi doğrulaması (CheckDigit).
  • Sayım oturumu (ScanSession):

    • kod başına adet; artır, azalt, elle gir, sil, temizle;
    • bunların hepsi için undoLast(); okutmalar da geri alınabilir;
    • uygulamanın kendi doğrulama fonksiyonu (ör. "Ürün bulunamadı"), senkron ya da asenkron;
    • toJson() / loadJson(); nerede saklanacağına siz karar verirsiniz;
    • olay akışı: ScanAccepted, ScanDuplicate, ScanRejected.
  • Geri bildirim:

    • kabul, tekrar ve red için ayrı ses ve titreşim desenleri;
    • kısa renkli ekran parlaması (ScanFlash);
    • sessiz mod.

    Bip sesleri paketin kendisi tarafından üretilir.

  • Tek API, her kaynak (ScanSource):

    • kamera: scan_tally_mobile_scanner;
    • el terminali: KeyboardWedgeSource (Zebra, Honeywell, USB/Bluetooth HID). Elle yazılan metin hiçbir zaman tarama sayılmaz.
  • GS1 ayrıştırma (Gs1Parser):

    • GS1-128, GS1 DataMatrix, GS1 QR ve DataBar verisi ile (01)…(17)… okunur biçim;
    • GTIN (01), lot (10), son kullanma (17), tavsiye edilen tüketim (15), seri (21), adet (30/37), SSCC (00), ölçüler (310n…) ve 150'den fazla başka AI;
    • FNC1/GS ayırıcısı, değişken uzunluklu alanlar, tarihte gün=00 kuralı ve yüzyıl kuralı.

Mevcut paketlerden farkı

  • Kamera tarayıcı eklentileri (mobile_scanner, barcode_scan2, simple_barcode_scanner) yalnızca "barkod görüldü" bilgisini verir.
    • mobile_scanner'ın noDuplicates modu aynı ürünü art arda ikinci kez hiç saymaz.
  • Klavye dinleyicileri (flutter_barcode_listener, barcode_keyboard_listener):
    • son sürümleri 2023 ve 2019'dan;
    • ön ek, son ek, özel bitiş karakteri ve TextField çakışması gibi konuları ele almaz.
  • flutter_datawedge yalnızca Zebra'ya ve intent yoluna özeldir.
  • gs1_barcode_parser yalnızca ayrıştırıcıdır.

scan_tally bir okuyucunun üstüne oturur ve her stok uygulamasının yeniden yazdığı soruyu çözer: Ne sayılır ve kullanıcı bunu nasıl anlar?

Kurulum ve kullanım

Kurulum ve kod örnekleri için yukarıdaki İngilizce Installation ve Usage bölümlerine bakın. API aynıdır.

Her okuma şu dört adımdan sırayla geçer:

  1. Kontrol hanesi. El terminalinden gelen hatalı kontrol hanesi ScanRejected olur. Kameradan gelen ise yanlış okuma sayılıp sessizce atlanır, çünkü bir sonraki kare çoğunlukla doğru okur.
  2. Kare teyidi (yalnızca kamera).
  3. Tekrar filtresi. Sonuç ya sessizdir ya da ScanDuplicate.
  4. Sizin doğrulama fonksiyonunuz. Sonuç ScanRejected ya da ScanAccepted olur.

Tasarım kararları ve riskler

1. Kamera bağımlılığı

Karar: bir depoda iki paket, Dart pub workspace ile (melos gerekmez).

  • scan_tally: ayıklama, oturum, GS1, kontrol hanesi, wedge birleştirici, ses üretimi ve geri bildirim. Kamera bağımlılığı yoktur.
    • package:scan_tally/scan_tally.dart saf Dart'tır, Flutter'dan hiçbir şey içe aktarmaz. İsolate'ta, sunucuda ve düz birim testlerinde kullanılabilir.
    • Flutter katmanı ayrı bir giriş noktasındadır: scan_tally_flutter.dart.
  • scan_tally_mobile_scanner: yaklaşık 100 satırlık ince bir adaptör.

Neden:

  • Kamera eklentileri sık değişir. Yalnızca el terminali kullanan bir uygulama kamera eklentisi, ML Kit ve kamera izni taşımamalıdır.
  • Kaynak, ScanFrame akışı veren soyut bir arayüzdür (ScanSource). Başka bir tarayıcı paketi birkaç satırla bağlanır.

Değerlendirilen alternatif: Flutter katmanı için üçüncü bir paket. 0.1 için gerekli görülmedi: Flutter katmanı küçüktür, eklenti kullanmaz ve ayrı giriş noktası mantığı zaten Flutter'dan bağımsız tutar.

2. El terminali testi

Karar: birleştirici (WedgeAssembler), zamanı dışarıdan verilen saf bir durum makinesidir.

Taramanın yazmadan ayrılması:

  • Tarama, tuşları en fazla maxKeyInterval (varsayılan 50 ms) arayla gelen ve aynı süre içinde bitiş karakteriyle (Enter, Tab ya da özel karakter) biten bir tuş dizisidir.
  • İnsan tuş başına 100 ms ve üzeri sürede yazar. Yavaş yazmanın ardından basılan Enter diziyi düşürür.
  • Ön ek ya da son ek ayarlanmışsa, bunları taşımayan diziler tarama sayılmaz.
  • Bitiş karakteri göndermeyen cihazlar için idleTimeout vardır.

Uygulamayla etkileşim:

  • Dizinin ilk karakteri yazmadan ayırt edilemediği için geçer. Dizinin geri kalanı yutulur.
  • Bitiş tuşu da yutulur. KeyboardWedgeListener onu odak sisteminde de durdurur; böylece tarayıcının Enter'ı odaktaki butona basmaz, Tab odağı kaydırmaz.

TextField çakışması:

  • Bir TextField odaktayken dinleyici devreden çıkar (ignoreWhenEditing: true, varsayılan).
  • Elle yazılan metin hiçbir zaman tarama sayılmaz. Odaktaki alana yapılan okutma, el terminali kullanıcılarının beklediği gibi alana yazılır.

Zamanlama: platform tuş olaylarına zaman damgası veriyorsa o kullanılır; böylece meşgul bir UI iş parçacığı taramayı bölmez. Vermiyorsa enjekte edilebilir monoton bir saat kullanılır.

Testler:

  • birim testleri: hızlı ve yavaş yazma, Enter, Tab, özel ve kontrol karakteri ile bitiş, ön ek ve son ek, uzunluk sınırları, kesilen tarama, boşta kalma süresi, GS karakteri;
  • HardwareKeyboard üzerinden gerçek tuş olayları gönderen widget testleri: Shift ile büyük harf, odaktaki butona basılmaması, odaktaki TextField'ın metni alması, açma/kapama, ScanSession'a akış.

Gerçek bir Zebra ya da Honeywell cihazında doğrulanmadı. Davranış testlerde benzetilmiş tuş olaylarıyla, emülatörde de adb shell input keyevent ile gönderilen Android tuş olaylarıyla doğrulandı; bu olaylar varsayılan 50 ms sınırında tek bir taramaya birleşti. Cihaz modelinizle sonuç bildirirseniz seviniriz.

Önerilen cihaz ayarları:

  • Zebra DataWedge: uygulamanıza özel profilde
    • Keystroke output açık, Intent output kapalı;
    • Basic data formatting → Send ENTER key (ya da TAB);
    • karakterler arası gecikme varsayılan (0 ms) kalsın. Gecikme eklerseniz maxKeyInterval'ı ondan büyük yapın.
  • Honeywell:
    • Wedge method: Keyboard, Data Intent kapalı;
    • Suffix: Enter (0D) ya da Tab (09);
    • wedge hızı varsayılan (hızlı) kalsın.
  • GS1: FNC1'i GS (\u001D) olarak gönderin. Başka bir karakterle değiştirildiyse Gs1Parser.parse(..., separators: {'~'}) kullanın.

Her iki marka okumaları Android broadcast intent ile de gönderebilir. Bu yol tuş vuruşundan daha sağlamdır; desteği ileriki bir sürümde ayrı bir ScanSource olarak planlanmaktadır.

3. Ses dosyaları

Karar: bip sesleri paketin kendisi tarafından üretilir.

  • ScanTones sinüs tonlarını tanımlar:
    • kabul: 2 kHz, 110 ms tek bip;
    • tekrar: 1,2 kHz iki kısa bip;
    • red: 620 → 440 Hz inen uğultu.
  • tool/generate_sounds.dart bu tanımlardan assets/sounds/*.wav dosyalarını üretir. Bir test, depodaki dosyaların betiğin çıktısıyla bayt bayt aynı olduğunu denetler. Sesler paketin kendi eseridir ve MIT kapsamındadır.
  • Sesi çalışma anında üretmek için ScanTones.wavOf(kind) de kullanılabilir.

Ses çalma:

  • Çekirdeğin ses bağımlılığı yoktur; çalma ScanSoundPlayer arayüzünün arkasındadır.
  • Bağımlılıksız varsayılan SystemScanSoundPlayer'dır ve sistem seslerini kullanır. Android'de yalnızca "tık" sesi vardır ve dokunma sesleri açıksa çalar.
  • Gerçek bip için paketle gelen WAV'ları bir ses eklentisiyle çalın. Örnek uygulama audioplayers'ı düşük gecikme modunda (Android SoundPool) kullanır. Bu eklenti hafif ve bakımlıdır, kısa seslere uygundur. Örnek kod yukarıdaki İngilizce bölümde.

Titreşim:

  • Flutter'ın yerleşik HapticFeedback özelliği kullanılır (FlutterScanHaptics). Eklenti ve izin gerekmez.
  • Desenler:
    • kabul: bir orta darbe;
    • tekrar: iki hafif darbe;
    • red: üç uzun titreşim.
  • Sistemdeki "dokunma geri bildirimi" ayarına uyar. Eldivenle hissedilecek güçlü desenler için ScanHaptics arayüzünü bir titreşim eklentisiyle uygulayın.

Sessiz mod sesi kapatır, titreşim ve parlamayı korur.

Örnek uygulama

example/, Türkçe ve İngilizce bir Android uygulamasıdır (Material 3). Dört bölümü vardır:

  • Kamera sekmesi.
  • El terminali sekmesi:
    • gerçek tuş olayları gönderen taklit tarama düğmeleri;
    • emülatörde klavyeyle denemek için yavaş yazmayı kabul eden "klavye deneme modu";
    • elle yazılan metnin tarama sayılmadığını gösteren giriş alanı.
  • Sayım listesi: +/−, adedi elle girme, kaydırarak silme, temizleme, JSON dışa aktarma.
  • Geri alma ve GS1: uygulama çubuğunda geri alma ve son okutmanın GS1 dökümü.

Sınırlamalar

  • El terminali desteği gerçek Zebra veya Honeywell cihazında denenmedi. Android intent çıkışı henüz desteklenmiyor.
  • Kamera adaptörü derlendi ve birim testlerinden geçti, ancak bu sürümde cihazda denenmedi.
  • Tekrar filtresinin durumu toJson() ile kaydedilmez. Geri yüklemeden sonra bir kodun ilk okuması yeni sayılır.

Diğer paketler

Aynı geliştiricinin diğer paketleri:

Lisans

MIT. Paketle gelen sesler tool/generate_sounds.dart ile üretilir ve aynı lisans kapsamındadır.

Libraries

scan_tally
Continuous barcode scanning for stock counts, receiving and shipping.
scan_tally_flutter
The Flutter layer of scan_tally: everything in scan_tally.dart plus KeyboardWedgeSource, KeyboardWedgeListener, FlutterScanFeedbackPlayer, ScanFlash and ScanSessionBuilder.