zebra_rfid_plus 0.0.2 copy "zebra_rfid_plus: ^0.0.2" to clipboard
zebra_rfid_plus: ^0.0.2 copied to clipboard

PlatformAndroid

A Flutter plugin for Zebra RFID readers (RFD8500, RFD40, RFD90) using the Zebra RFID API3 SDK. Supports Bluetooth and USB transport, inventory scanning, tag access, pre-filters, and singulation control.

example/lib/main.dart

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Zebra RFID Example',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorSchemeSeed: Colors.deepPurple,
        useMaterial3: true,
      ),
      home: const RfidHomePage(),
    );
  }
}

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

  @override
  State<RfidHomePage> createState() => _RfidHomePageState();
}

class _RfidHomePageState extends State<RfidHomePage> {
  // ─── Controller ───
  final _controller = ZebraRfidController();

  // Barcode results (not in controller, listen directly)
  final List<String> _barcodes = [];

  @override
  void initState() {
    super.initState();
    _controller.init();
    _controller.addListener(_rebuild);

    // Link trigger button → auto start/stop inventory
    _controller.linkTriggerToInventory(enabled: true);

    // Listen for barcodes separately
    ZebraRfid.onBarcodeRead.listen((code) {
      if (!mounted) return;
      setState(() => _barcodes.insert(0, code));
    });
  }

  void _rebuild() {
    if (mounted) setState(() {});
  }

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

  // ─── Actions ───

  Future<void> _toggleConnection() async {
    try {
      if (_controller.isConnected) {
        await _controller.disconnect();
      } else {
        await _controller.connect(transport: RfidTransport.bluetooth);
      }
    } on ZebraRfidException catch (e) {
      _showError(e.message);
    }
  }

  Future<void> _toggleScan() async {
    try {
      if (_controller.isScanning) {
        await _controller.stopInventory();
      } else {
        await _controller.startInventory();
      }
    } on ZebraRfidException catch (e) {
      _showError(e.message);
    }
  }

  void _showError(String msg) {
    if (!mounted) return;
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(msg), backgroundColor: Colors.red.shade700),
    );
  }

  // ─── UI ───

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;

    return Scaffold(
      appBar: AppBar(
        title: const Text('Zebra RFID'),
        centerTitle: false,
        actions: [
          Padding(
            padding: const EdgeInsets.only(right: 16),
            child: _StatusDot(connected: _controller.isConnected),
          ),
        ],
      ),
      body: Column(
        children: [
          _ConnectionCard(
            controller: _controller,
            onConnectTap: _toggleConnection,
          ),
          const Divider(height: 1),
          _ScanBar(
            controller: _controller,
            onScanTap: _toggleScan,
          ),
          const Divider(height: 1),
          Expanded(
            child: _TagList(tags: _controller.tags),
          ),
        ],
      ),
    );
  }
}

// ─────────────────────────────────────────────
// Sub-widgets
// ─────────────────────────────────────────────

class _StatusDot extends StatelessWidget {
  final bool connected;
  const _StatusDot({required this.connected});

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Container(
          width: 10,
          height: 10,
          decoration: BoxDecoration(
            color: connected ? Colors.green : Colors.grey,
            shape: BoxShape.circle,
          ),
        ),
        const SizedBox(width: 6),
        Text(
          connected ? 'Connected' : 'Disconnected',
          style: Theme.of(context).textTheme.labelSmall,
        ),
      ],
    );
  }
}

class _ConnectionCard extends StatelessWidget {
  final ZebraRfidController controller;
  final VoidCallback onConnectTap;

  const _ConnectionCard({
    required this.controller,
    required this.onConnectTap,
  });

  @override
  Widget build(BuildContext context) {
    final status = controller.connectionStatus;
    final readerName = controller.connectedReaderName;
    final lastError = controller.lastError;
    final isConnecting = status == RfidConnectionStatus.connecting;

    return Padding(
      padding: const EdgeInsets.all(16),
      child: Row(
        children: [
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  readerName ?? 'No reader connected',
                  style: Theme.of(context).textTheme.titleSmall,
                  overflow: TextOverflow.ellipsis,
                ),
                if (lastError != null)
                  Text(
                    lastError,
                    style: TextStyle(
                      color: Colors.red.shade700,
                      fontSize: 12,
                    ),
                    maxLines: 1,
                    overflow: TextOverflow.ellipsis,
                  )
                else
                  Text(
                    status.name,
                    style: TextStyle(
                      color: Colors.grey.shade600,
                      fontSize: 12,
                    ),
                  ),
              ],
            ),
          ),
          const SizedBox(width: 12),
          FilledButton(
            onPressed: isConnecting ? null : onConnectTap,
            style: FilledButton.styleFrom(
              backgroundColor: controller.isConnected
                  ? Colors.red.shade600
                  : null,
            ),
            child: isConnecting
                ? const SizedBox(
                    width: 16,
                    height: 16,
                    child: CircularProgressIndicator(strokeWidth: 2),
                  )
                : Text(controller.isConnected ? 'Disconnect' : 'Connect'),
          ),
        ],
      ),
    );
  }
}

class _ScanBar extends StatelessWidget {
  final ZebraRfidController controller;
  final VoidCallback onScanTap;

  const _ScanBar({required this.controller, required this.onScanTap});

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
      child: Row(
        children: [
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  '${controller.tagCount} unique tags',
                  style: Theme.of(context).textTheme.titleSmall,
                ),
                Text(
                  controller.isScanning ? 'Scanning…' : 'Idle',
                  style: TextStyle(
                    fontSize: 12,
                    color: controller.isScanning
                        ? Theme.of(context).colorScheme.primary
                        : Colors.grey.shade600,
                  ),
                ),
              ],
            ),
          ),
          const SizedBox(width: 12),
          FilledButton.icon(
            onPressed: controller.isConnected ? onScanTap : null,
            icon: Icon(
              controller.isScanning ? Icons.stop_rounded : Icons.wifi_tethering,
              size: 18,
            ),
            label: Text(controller.isScanning ? 'Stop' : 'Scan'),
          ),
          const SizedBox(width: 8),
          OutlinedButton(
            onPressed: controller.tagCount > 0 ? controller.clearTags : null,
            child: const Text('Clear'),
          ),
        ],
      ),
    );
  }
}

class _TagList extends StatelessWidget {
  final List<RfidTag> tags;
  const _TagList({required this.tags});

  @override
  Widget build(BuildContext context) {
    if (tags.isEmpty) {
      return Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(Icons.nfc, size: 52, color: Colors.grey.shade300),
            const SizedBox(height: 12),
            Text(
              'No tags yet',
              style: TextStyle(color: Colors.grey.shade500),
            ),
            const SizedBox(height: 4),
            Text(
              'Connect a reader and start scanning',
              style: TextStyle(color: Colors.grey.shade400, fontSize: 12),
            ),
          ],
        ),
      );
    }

    return ListView.separated(
      padding: const EdgeInsets.symmetric(vertical: 4),
      itemCount: tags.length,
      separatorBuilder: (_, __) => const Divider(height: 1, indent: 56),
      itemBuilder: (context, index) {
        final tag = tags[index];
        return ListTile(
          dense: true,
          leading: CircleAvatar(
            radius: 18,
            backgroundColor: _rssiColor(tag.peakRssi).withOpacity(0.15),
            child: Icon(
              Icons.nfc,
              size: 16,
              color: _rssiColor(tag.peakRssi),
            ),
          ),
          title: Text(
            tag.tagId,
            style: const TextStyle(
              fontFamily: 'monospace',
              fontSize: 13,
              fontWeight: FontWeight.w500,
            ),
          ),
          trailing: _RssiChip(rssi: tag.peakRssi),
        );
      },
    );
  }

  Color _rssiColor(int rssi) {
    if (rssi > -50) return Colors.green;
    if (rssi > -70) return Colors.orange;
    return Colors.red;
  }
}

class _RssiChip extends StatelessWidget {
  final int rssi;
  const _RssiChip({required this.rssi});

  Color get _color {
    if (rssi > -50) return Colors.green;
    if (rssi > -70) return Colors.orange;
    return Colors.red;
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
      decoration: BoxDecoration(
        color: _color.withOpacity(0.12),
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: _color.withOpacity(0.4), width: 0.8),
      ),
      child: Text(
        '$rssi dBm',
        style: TextStyle(
          color: _color,
          fontWeight: FontWeight.bold,
          fontSize: 11,
        ),
      ),
    );
  }
}
2
likes
150
points
13
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for Zebra RFID readers (RFD8500, RFD40, RFD90) using the Zebra RFID API3 SDK. Supports Bluetooth and USB transport, inventory scanning, tag access, pre-filters, and singulation control.

Repository (GitHub)
View/report issues
Contributing

Topics

#rfid #zebra #bluetooth #hardware #nfc

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on zebra_rfid_plus

Packages that implement zebra_rfid_plus