abi_beacon 0.1.0 copy "abi_beacon: ^0.1.0" to clipboard
abi_beacon: ^0.1.0 copied to clipboard

PlatformAndroid

Android iBeacon background monitoring with a resilient foreground service, OEM manufacturer detection, and battery/permission state management.

example/lib/main.dart

import 'dart:async';

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

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'abi_beacon example',
      theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
      home: const BeaconDemoPage(),
    );
  }
}

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

  @override
  State<BeaconDemoPage> createState() => _BeaconDemoPageState();
}

class _BeaconDemoPageState extends State<BeaconDemoPage> {
  static const _defaultUuid = 'B9407F30-F5F8-466E-AFF9-25556B57FE6D';

  final _uuidController = TextEditingController(text: _defaultUuid);
  final _exitSecondsController = TextEditingController(text: '4');

  DeviceInfo? _device;
  PermissionsSnapshot? _perms;
  bool _powerSaveMode = false;
  bool _batteryOptDisabled = false;
  bool _locationEnabled = false;
  bool _monitoring = false;
  RegionState _regionState = RegionState.unknown;
  BeaconReading? _nearest;
  final List<String> _log = [];

  StreamSubscription<BeaconEvent>? _sub;

  @override
  void initState() {
    super.initState();
    _sub = AbiBeacon.events.listen(_onEvent);
    _refreshDeviceState();
  }

  @override
  void dispose() {
    _sub?.cancel();
    _uuidController.dispose();
    _exitSecondsController.dispose();
    super.dispose();
  }

  void _onEvent(BeaconEvent e) {
    final ts = TimeOfDay.now().format(context);
    setState(() {
      switch (e) {
        case EntryEvent():
          _regionState = RegionState.inside;
          _pushLog('$ts  ENTRADA');
        case ExitEvent():
          _regionState = RegionState.outside;
          _nearest = null;
          _pushLog('$ts  SALIDA');
        case RangeEvent(:final nearest):
          _nearest = nearest;
        case ServiceStartedEvent():
          _monitoring = true;
          _pushLog('$ts  servicio iniciado');
        case ServiceStoppedEvent():
          _monitoring = false;
          _pushLog('$ts  servicio detenido');
        case ErrorEvent(:final message):
          _pushLog('$ts  ERROR: $message');
        case UnknownEvent():
          break;
      }
    });
  }

  void _pushLog(String entry) {
    _log.insert(0, entry);
    if (_log.length > 12) _log.removeRange(12, _log.length);
  }

  Future<void> _refreshDeviceState() async {
    final device = await AbiBeacon.getDeviceInfo();
    final perms = await AbiBeacon.checkPermissions();
    final powerSave = await AbiBeacon.isPowerSaveModeEnabled();
    final battOpt = await AbiBeacon.isBatteryOptimizationDisabled();
    final loc = await AbiBeacon.isLocationServiceEnabled();
    final monitoring = await AbiBeacon.isMonitoring();
    if (!mounted) return;
    setState(() {
      _device = device;
      _perms = perms;
      _powerSaveMode = powerSave;
      _batteryOptDisabled = battOpt;
      _locationEnabled = loc;
      _monitoring = monitoring;
    });
  }

  Future<void> _requestPermissions() async {
    final perms = await AbiBeacon.requestPermissions();
    if (!mounted) return;
    setState(() => _perms = perms);
  }

  Future<void> _start() async {
    final exitSecs = int.tryParse(_exitSecondsController.text) ?? 4;
    await AbiBeacon.initialize(BeaconConfig(
      uuid: _uuidController.text.trim(),
      exitPeriod: Duration(seconds: exitSecs),
      notificationTitle: 'iBeacon Monitor',
      notificationText: 'Buscando beacon...',
    ));
    await AbiBeacon.startMonitoring();
    await _refreshDeviceState();
  }

  Future<void> _stop() async {
    await AbiBeacon.stopMonitoring();
    await _refreshDeviceState();
  }

  @override
  Widget build(BuildContext context) {
    final device = _device;
    return Scaffold(
      appBar: AppBar(title: const Text('abi_beacon demo')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _deviceCard(device),
          const SizedBox(height: 12),
          _permissionsCard(),
          const SizedBox(height: 12),
          if (device != null && device.oem.isAggressive) _oemCard(device),
          if (device != null && device.oem.isAggressive)
            const SizedBox(height: 12),
          _configCard(),
          const SizedBox(height: 12),
          _statusCard(),
          const SizedBox(height: 12),
          _logCard(),
        ],
      ),
    );
  }

  Widget _deviceCard(DeviceInfo? device) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text('Dispositivo',
                style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 4),
            Text(device == null
                ? 'Cargando...'
                : '${device.manufacturer} ${device.model} · API ${device.sdkInt} · ${device.oem.name}'),
            const Divider(),
            _flag('Modo ahorro de energia', _powerSaveMode, badWhenTrue: true),
            _flag('Optimizacion de bateria desactivada', _batteryOptDisabled),
            _flag('Ubicacion activada', _locationEnabled),
            const SizedBox(height: 8),
            Wrap(
              spacing: 8,
              children: [
                OutlinedButton(
                  onPressed: AbiBeacon.requestDisableBatteryOptimization,
                  child: const Text('Desactivar opt. bateria'),
                ),
                OutlinedButton(
                  onPressed: _refreshDeviceState,
                  child: const Text('Refrescar'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _permissionsCard() {
    final perms = _perms;
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text('Permisos',
                style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 4),
            if (perms == null)
              const Text('Sin consultar')
            else
              ...perms.statuses.entries.map((e) => _flag(
                    e.key.split('.').last,
                    e.value == PermissionStatus.granted,
                  )),
            const SizedBox(height: 8),
            FilledButton(
              onPressed: _requestPermissions,
              child: const Text('Solicitar permisos'),
            ),
          ],
        ),
      ),
    );
  }

  Widget _oemCard(DeviceInfo device) {
    return Card(
      color: Theme.of(context).colorScheme.tertiaryContainer,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('Ajustes ${device.oem.name}',
                style: const TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 4),
            const Text(
                'Este fabricante puede matar el servicio aun con la optimizacion '
                'de bateria desactivada. Habilita estos ajustes:'),
            const SizedBox(height: 8),
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: [
                OutlinedButton(
                  onPressed: () =>
                      AbiBeacon.openOemSetting(OemSettingType.autostart),
                  child: const Text('Autostart'),
                ),
                OutlinedButton(
                  onPressed: () =>
                      AbiBeacon.openOemSetting(OemSettingType.batterySaver),
                  child: const Text('Ahorro bateria'),
                ),
                OutlinedButton(
                  onPressed: () =>
                      AbiBeacon.openOemSetting(OemSettingType.protectedApps),
                  child: const Text('Apps protegidas'),
                ),
                OutlinedButton(
                  onPressed: () =>
                      AbiBeacon.openOemSetting(OemSettingType.appDetails),
                  child: const Text('Detalles app'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _configCard() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text('Configuracion',
                style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            TextField(
              controller: _uuidController,
              decoration: const InputDecoration(
                labelText: 'UUID',
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 8),
            TextField(
              controller: _exitSecondsController,
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(
                labelText: 'Exit period (segundos)',
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 8),
            Row(
              children: [
                Expanded(
                  child: FilledButton(
                    onPressed: _monitoring ? null : _start,
                    child: const Text('Iniciar'),
                  ),
                ),
                const SizedBox(width: 8),
                Expanded(
                  child: FilledButton.tonal(
                    onPressed: _monitoring ? _stop : null,
                    child: const Text('Detener'),
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _statusCard() {
    final (color, label) = switch (_regionState) {
      RegionState.inside => (Colors.green, 'DENTRO DE LA REGION'),
      RegionState.outside => (Colors.red, 'FUERA DE LA REGION'),
      RegionState.unknown => (Colors.blueGrey, 'BUSCANDO...'),
    };
    return Card(
      color: color,
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          children: [
            Text(label,
                style: const TextStyle(
                    color: Colors.white,
                    fontWeight: FontWeight.bold,
                    fontSize: 18)),
            if (_nearest != null) ...[
              const SizedBox(height: 6),
              Text(
                'RSSI ${_nearest!.rssi} dBm · ~${_nearest!.distance.toStringAsFixed(2)} m',
                style: const TextStyle(color: Colors.white),
              ),
            ],
          ],
        ),
      ),
    );
  }

  Widget _logCard() {
    return Card(
      color: const Color(0xFF1E1E1E),
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text('Eventos',
                style: TextStyle(
                    color: Color(0xFF80CBC4), fontWeight: FontWeight.bold)),
            const SizedBox(height: 4),
            if (_log.isEmpty)
              const Text('Sin eventos aun',
                  style: TextStyle(color: Colors.white54))
            else
              ..._log.map((e) => Text(e,
                  style: const TextStyle(
                      color: Color(0xFFE0E0E0),
                      fontFamily: 'monospace',
                      fontSize: 12))),
          ],
        ),
      ),
    );
  }

  Widget _flag(String label, bool ok, {bool badWhenTrue = false}) {
    final isGood = badWhenTrue ? !ok : ok;
    return Row(
      children: [
        Icon(isGood ? Icons.check_circle : Icons.cancel,
            size: 16, color: isGood ? Colors.green : Colors.orange),
        const SizedBox(width: 6),
        Expanded(child: Text(label, style: const TextStyle(fontSize: 13))),
        Text(ok ? 'si' : 'no', style: const TextStyle(fontSize: 13)),
      ],
    );
  }
}
0
likes
160
points
42
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Android iBeacon background monitoring with a resilient foreground service, OEM manufacturer detection, and battery/permission state management.

Repository (GitHub)
View/report issues

Topics

#ibeacon #bluetooth #beacon #background-service #ble

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on abi_beacon

Packages that implement abi_beacon