flutter_imin_lark_scanner

A Flutter plugin for the built-in barcode scanner on iMIN Lark Android devices. It configures the iMIN scanner service, listens for scan broadcasts, and supports continuous scanning with lifecycle-aware pause and resume.

Features

  • Configure the iMIN built-in scanner to deliver results via Android broadcast
  • Stream barcode values to Dart through an EventChannel
  • Trigger single or continuous scans programmatically
  • ContinuousScanSession helper with:
    • Automatic keep-alive while scanning
    • Configurable cooldown between scans
    • Duplicate barcode suppression
    • App lifecycle pause/resume (background/foreground)
    • Reliable cold-start behaviour after app stop and relaunch
  • Example app included

Platform support

Platform Support
Android Yes — iMIN Lark devices with the built-in scanner service
iOS Not supported

This plugin communicates with the iMIN scanner API over Android intents and broadcasts. It is intended for iMIN Lark POS / handheld devices only.

Requirements

  • Flutter >=3.3.0
  • Dart ^3.10.7
  • Android minSdk 24
  • iMIN device with the built-in scanner service installed

Installation

Add the dependency to your pubspec.yaml:

dependencies:
  flutter_imin_lark_scanner: ^0.0.1

Then run:

flutter pub get

Usage

The easiest way to use the plugin is through ContinuousScanSession, which handles scanner setup, keep-alive, cooldown, and lifecycle management for you.

import 'dart:async';

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

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

  @override
  State<ScannerPage> createState() => _ScannerPageState();
}

class _ScannerPageState extends State<ScannerPage> {
  late final ContinuousScanSession _session;
  StreamSubscription<String>? _scanSubscription;
  StreamSubscription<void>? _stateSubscription;

  @override
  void initState() {
    super.initState();

    _session = FlutterIminLarkScanner.instance.createContinuousSession(
      config: const ContinuousScanConfig(
        rescanDelay: Duration(seconds: 3),
        keepAliveInterval: Duration(seconds: 2),
        duplicateSuppression: Duration(milliseconds: 500),
      ),
    );

    _scanSubscription = _session.scans.listen((barcode) {
      debugPrint('Scanned: $barcode');
    });

    // Rebuild UI when scanner active/starting state changes.
    _stateSubscription = _session.stateChanges.listen((_) {
      if (mounted) setState(() {});
    });

    unawaited(_session.start());
  }

  @override
  void dispose() {
    _scanSubscription?.cancel();
    _stateSubscription?.cancel();
    _session.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Text(
          _session.isActive ? 'Scanner active' : 'Scanner stopped',
        ),
      ),
    );
  }
}

Session configuration

Option Default Description
rescanDelay 3 seconds Wait time after a successful scan before scanning again
keepAliveInterval 2 seconds How often to re-trigger the hardware scan button while active
duplicateSuppression 500 ms Ignore identical barcodes received within this window

Session API

Method / property Description
start() Prepare and start continuous scanning once the app is resumed
stop() User-initiated stop
resume() Resume after stop()
dispose() Clean up listeners and stop the scanner
scans Stream of barcode strings
stateChanges Stream that fires when isActive or isStarting changes
isActive Whether scanning is currently running
isStarting Whether the session is waiting for the app or hardware

Lifecycle pause and resume are handled automatically by the session. You do not need a separate WidgetsBindingObserver unless you have other lifecycle logic.

Low-level API

For manual control, use FlutterIminLarkScanner directly:

final scanner = FlutterIminLarkScanner.instance;

await scanner.prepare();
await scanner.start();

scanner.barcodes.listen((barcode) {
  print(barcode);
});

await scanner.trigger(); // trigger a single scan
await scanner.stop();

Example

See the example/ app for a complete working demo with start/stop controls and scan result display.

cd example
flutter run

How it works

On Android, the plugin:

  1. Sends a broadcast to configure the iMIN scanner for broadcast output
  2. Registers a receiver for com.imin.scanner.api.RESULT_ACTION
  3. Triggers scanning by simulating the hardware scan key (KeyEvent code 170) via com.imin.keyevent
  4. Re-arms the scanner on activity resume and after cold start when continuous scanning is active

Troubleshooting

Scanner does not start after reopening the app

Use ContinuousScanSession.start() rather than calling the low-level API directly from initState. The session waits until the app lifecycle is resumed before triggering the hardware.

UI shows "Start Scanner" while scanning is active

Listen to session.stateChanges and call setState when it fires. Scanning starts asynchronously after app launch.

No scans received

  • Confirm you are running on an iMIN Lark device with the built-in scanner service
  • Ensure no other app has exclusive control of the scanner
  • Check logcat for the FLUTTER_IMIN tag

Contributing

Issues and pull requests are welcome in the repository:

https://bitbucket.org/patel-apps/imin_lark_scanner_flutter

License

See LICENSE.