brux88_beacon 0.1.2
brux88_beacon: ^0.1.2 copied to clipboard
A Flutter plugin for handling BLE beacons with background detection and reliable monitoring.
example/lib/main.dart
import 'package:brux88_beacon_example/permission_screen.dart';
import 'package:flutter/material.dart';
import 'package:brux88_beacon/brux88_beacon.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:permission_handler/permission_handler.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Beacon Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const BeaconHomePage(),
);
}
}
class BeaconHomePage extends StatefulWidget {
const BeaconHomePage({Key? key}) : super(key: key);
@override
State<BeaconHomePage> createState() => _BeaconHomePageState();
}
class _BeaconHomePageState extends State<BeaconHomePage>
with SingleTickerProviderStateMixin {
// Chiave globale per lo Scaffold, che ci fornisce un contesto valido
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final BeaconManager _beaconManager = BeaconManager();
bool _isMonitoring = false;
List<Beacon> _detectedBeacons = [];
List<String> _logs = [];
SelectedBeacon? _selectedBeacon;
late TabController _tabController;
bool _showDetectionNotifications = false;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
_initBeaconManager().then((_) {
_checkServiceStatus();
_checkBatteryOptimization();
});
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
Future<void> _checkBatteryOptimization() async {
try {
final isIgnored = await _beaconManager.isBatteryOptimizationIgnored();
if (!isIgnored) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Ottimizzazione Batteria'),
content: Text(
'Per garantire il corretto funzionamento del monitoraggio beacon in background, ' +
'è necessario disattivare l\'ottimizzazione della batteria per questa app.'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('Non ora'),
),
TextButton(
onPressed: () async {
Navigator.of(context).pop();
await _beaconManager.requestIgnoreBatteryOptimization();
},
child: Text('Disattiva'),
),
],
),
);
}
} catch (e) {
_addLog("Errore nel controllo dell'ottimizzazione batteria: $e");
}
}
Future<void> _checkServiceStatus() async {
try {
// Controlla se il monitoraggio è attivo
final isRunning = await _beaconManager.isMonitoringRunning();
setState(() {
_isMonitoring = isRunning;
});
if (isRunning) {
_addLog("Monitoraggio già attivo");
} else if (_selectedBeacon != null && _selectedBeacon!.enabled) {
// Se abbiamo un beacon selezionato ma il monitoraggio non è attivo, riavviamolo
_addLog("Riavvio automatico del monitoraggio");
await _beaconManager.startMonitoring();
setState(() {
_isMonitoring = true;
});
}
} catch (e) {
_addLog("Errore nel controllo dello stato del servizio: $e");
}
}
Future<void> _initBeaconManager() async {
try {
_addLog("Inizializzazione beacon manager...");
// Se il monitoraggio è attivo, fermalo prima di reinizializzare
if (_isMonitoring) {
_addLog(
"Monitoraggio attivo rilevato, lo fermo prima di reinizializzare");
try {
await _beaconManager.stopMonitoring();
await _beaconManager.cancelRecurringAlarm();
} catch (e) {
_addLog("Errore nel fermare il monitoraggio precedente: $e");
// Continua comunque con l'inizializzazione
}
}
// Prima inizializza in modo basico
final initialized = await _beaconManager.initialize();
if (!initialized) {
_addLog("Errore durante l'inizializzazione del BeaconManager");
return;
}
// Verifica i permessi dopo l'inizializzazione di base
final permissionsStatus = await _beaconManager.checkPermissions();
final allPermissionsGranted = !permissionsStatus.values.contains(false);
if (!allPermissionsGranted) {
_addLog("Permessi mancanti, richiedo i permessi necessari");
// Richiedi permessi di posizione
var locationStatus = await Permission.locationWhenInUse.request();
_addLog("Permesso posizione: $locationStatus");
if (locationStatus.isGranted) {
var backgroundLocationStatus =
await Permission.locationAlways.request();
_addLog(
"Permesso posizione in background: $backgroundLocationStatus");
}
// Richiedi permessi Bluetooth
var bluetoothScanStatus = await Permission.bluetoothScan.request();
_addLog("Permesso Bluetooth scan: $bluetoothScanStatus");
var bluetoothConnectStatus =
await Permission.bluetoothConnect.request();
_addLog("Permesso Bluetooth connect: $bluetoothConnectStatus");
// Richiedi permesso notifiche
var notificationStatus = await Permission.notification.request();
_addLog("Permesso notifiche: $notificationStatus");
}
// Ascolto dei beacon rilevati
_beaconManager.beacons.listen((beacons) {
if (mounted) {
setState(() {
_detectedBeacons = beacons;
});
}
// Mostra toast per ogni beacon rilevato
if (beacons.isNotEmpty) {
for (var beacon in beacons) {
Fluttertoast.showToast(
msg: "Beacon rilevato: ${beacon.uuid}",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
);
}
}
});
// Ascolto dello stato di monitoraggio
_beaconManager.monitoringState.listen((state) {
_addLog("Stato monitoraggio: $state");
});
// Carica i log iniziali
_loadLogs();
// Carica il beacon selezionato
_loadSelectedBeacon();
// Verifica se il monitoraggio era attivo in precedenza
final isMonitoring = await _beaconManager.isMonitoringRunning();
setState(() {
_isMonitoring = isMonitoring;
});
_addLog(
"Inizializzazione beacon manager completata. Monitoraggio attivo: $_isMonitoring");
// Se il monitoraggio era attivo, riavvialo
if (_isMonitoring) {
_addLog("Riavvio monitoraggio automaticamente...");
try {
await _beaconManager.startMonitoring();
await _beaconManager.setupRecurringAlarm();
_addLog("Monitoraggio riavviato con successo");
} catch (e) {
_addLog("Errore nel riavvio del monitoraggio: $e");
setState(() {
_isMonitoring = false;
});
}
}
} catch (e) {
_addLog("Errore nell'inizializzazione: $e");
}
}
Future<void> _loadSelectedBeacon() async {
try {
final beacon = await _beaconManager.getSelectedBeacon();
setState(() {
_selectedBeacon = beacon;
});
if (beacon != null) {
_addLog("Beacon selezionato caricato: ${beacon.uuid}");
}
} catch (e) {
_addLog("Errore nel caricamento del beacon selezionato: $e");
}
}
Future<void> _loadLogs() async {
try {
final logs = await _beaconManager.getLogs();
setState(() {
_logs = logs;
});
} catch (e) {
_addLog("Errore nel caricamento dei log: $e");
}
}
void _addLog(String message) {
setState(() {
final timestamp = DateTime.now().toString().substring(0, 19);
_logs.insert(0, "[$timestamp] $message");
});
}
Future<void> _toggleMonitoring() async {
try {
if (_isMonitoring) {
_addLog("Arresto monitoraggio...");
// Imposta _isMonitoring = false prima di fermare il servizio
// per evitare che i callback possano riattivarlo
setState(() {
_isMonitoring = false;
});
await _beaconManager.stopMonitoring();
await _beaconManager.cancelRecurringAlarm(); // Cancella l'allarme
_addLog("Monitoraggio fermato");
} else {
// Verifica nuovamente i permessi prima di avviare il monitoraggio
final permissionsStatus = await _beaconManager.checkPermissions();
final allPermissionsGranted = !permissionsStatus.values.contains(false);
if (!allPermissionsGranted) {
_addLog(
"Richiesta di permessi mancanti prima di avviare il monitoraggio");
// Richiedi i permessi di base
await Permission.locationWhenInUse.request();
if (await Permission.locationWhenInUse.isGranted) {
await Permission.locationAlways.request();
}
await Permission.bluetoothScan.request();
await Permission.bluetoothConnect.request();
await Permission.notification.request();
// Richiedi di ignorare l'ottimizzazione della batteria
await _beaconManager.requestIgnoreBatteryOptimization();
}
// Richiedi il permesso per gli allarmi esatti
await _beaconManager.requestExactAlarmPermission();
_addLog("Avvio monitoraggio...");
// Verifica che il BeaconManager sia inizializzato
bool isInitialized = await _beaconManager.isInitialized();
if (!isInitialized) {
_addLog("BeaconManager non inizializzato, lo reinizializzo...");
isInitialized = await _beaconManager.initialize();
if (!isInitialized) {
_addLog("Impossibile inizializzare il BeaconManager");
throw Exception("Impossibile inizializzare il BeaconManager");
}
}
// Avvia il monitoraggio
await _beaconManager.startMonitoring();
await _beaconManager.setupRecurringAlarm(); // Configura l'allarme
_addLog("Monitoraggio avviato");
setState(() {
_isMonitoring = true;
});
// Verifica che il servizio sia effettivamente attivo
await _checkServiceStatus();
}
} catch (e) {
_addLog("Errore: $e");
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Si è verificato un errore: $e'),
backgroundColor: Colors.red,
),
);
}
}
}
// Estrai i dialoghi in metodi separati
Future<bool> showPermissionDialog(BuildContext context) async {
return await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (BuildContext dialogContext) {
return AlertDialog(
title: const Text('Permessi necessari'),
content: const Text(
'Per monitorare i beacon, l\'app ha bisogno di tutti i permessi richiesti. '
'Vuoi concedere tutti i permessi necessari per continuare?'),
actions: [
TextButton(
onPressed: () {
Navigator.of(dialogContext).pop(false);
},
child: const Text('Annulla'),
),
TextButton(
onPressed: () {
Navigator.of(dialogContext).pop(true);
},
child: const Text('Concedi permessi'),
),
],
);
},
) ??
false; // Default a false se il dialogo viene chiuso in modo imprevisto
}
Future<bool> showContinueAnywayDialog(BuildContext context) async {
return await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (BuildContext dialogContext) {
return AlertDialog(
title: const Text('Permessi mancanti'),
content: const Text(
'Alcuni permessi necessari non sono stati concessi. '
'Il monitoraggio potrebbe non funzionare correttamente. '
'Vuoi avviare comunque il monitoraggio?'),
actions: [
TextButton(
onPressed: () {
Navigator.of(dialogContext).pop(false);
},
child: const Text('No'),
),
TextButton(
onPressed: () {
Navigator.of(dialogContext).pop(true);
},
child: const Text('Sì, avvia comunque'),
),
],
);
},
) ??
false; // Default a false se il dialogo viene chiuso in modo imprevisto
}
Future<void> requestAllPermissions() async {
// Prima richiedi i permessi di base
await Permission.locationWhenInUse.request();
if (await Permission.locationWhenInUse.isGranted) {
await Permission.locationAlways.request();
}
// Permessi Bluetooth (per Android 12+)
await Permission.bluetoothScan.request();
await Permission.bluetoothConnect.request();
// Permessi notifiche (per Android 13+)
await Permission.notification.request();
// Richiedi di ignorare l'ottimizzazione della batteria
await _beaconManager.requestIgnoreBatteryOptimization();
}
Future<void> _saveSelectedBeacon(Beacon beacon) async {
try {
final result = await _beaconManager.setBeaconToMonitor(
uuid: beacon.uuid,
major: beacon.major,
minor: beacon.minor,
enabled: true,
);
if (result) {
_addLog("Beacon selezionato: ${beacon.uuid}");
Fluttertoast.showToast(
msg: "Beacon selezionato con successo",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
);
// Ricarica il beacon selezionato
_loadSelectedBeacon();
} else {
_addLog("Errore nella selezione del beacon");
}
} catch (e) {
_addLog("Errore: $e");
}
}
Future<void> _clearSelectedBeacon() async {
try {
final result = await _beaconManager.clearSelectedBeacon();
if (result) {
setState(() {
_selectedBeacon = null;
});
_addLog("Selezione beacon cancellata");
Fluttertoast.showToast(
msg: "Selezione beacon cancellata",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
);
} else {
_addLog("Errore nella cancellazione del beacon selezionato");
}
} catch (e) {
_addLog("Errore: $e");
}
}
Future<void> _refreshLogs() async {
_addLog("Aggiornamento log...");
await _loadLogs();
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text('Plugin Beacon Demo'),
bottom: TabBar(
controller: _tabController,
tabs: [
Tab(text: 'Beacon'),
Tab(text: 'Permessi', icon: Icon(Icons.security)),
Tab(text: 'Log'),
],
),
),
body: TabBarView(
controller: _tabController,
children: [
// Tab Beacon
Column(
children: [
SwitchListTile(
title: const Text('Notifiche di rilevamento beacon'),
subtitle: const Text(
'Mostra notifiche quando vengono rilevati nuovi beacon'),
value: _showDetectionNotifications,
onChanged: (bool value) async {
// Chiama il metodo del plugin
await _beaconManager.setShowDetectionNotifications(value);
setState(() {
_showDetectionNotifications = value;
// Se hai un log, aggiungi anche un messaggio
if (mounted && _logs != null) {
_logs.add(
"Notifiche di rilevamento ${value ? 'abilitate' : 'disabilitate'}");
}
});
},
),
Padding(
padding: EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: _toggleMonitoring,
child: Text(_isMonitoring
? 'Ferma monitoraggio'
: 'Avvia monitoraggio'),
),
],
),
),
// Beacon selezionato
if (_selectedBeacon != null)
Card(
margin: EdgeInsets.all(16.0),
color: Colors.lightBlue[50],
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Beacon Selezionato',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
SizedBox(height: 8),
Text('UUID: ${_selectedBeacon!.uuid}'),
if (_selectedBeacon!.major != null)
Text('Major: ${_selectedBeacon!.major}'),
if (_selectedBeacon!.minor != null)
Text('Minor: ${_selectedBeacon!.minor}'),
SizedBox(height: 8),
Text(
'Stato: ${_selectedBeacon!.enabled ? 'Attivo' : 'Inattivo'}'),
SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: _clearSelectedBeacon,
child: Text('Cancella'),
),
],
),
],
),
),
),
Expanded(
child: _detectedBeacons.isEmpty
? Center(child: Text('Nessun beacon rilevato'))
: ListView.builder(
itemCount: _detectedBeacons.length,
itemBuilder: (context, index) {
final beacon = _detectedBeacons[index];
return Card(
margin: EdgeInsets.symmetric(
horizontal: 16.0, vertical: 8.0),
child: ListTile(
title: Text('UUID: ${beacon.uuid}'),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (beacon.major != null)
Text('Major: ${beacon.major}'),
if (beacon.minor != null)
Text('Minor: ${beacon.minor}'),
Text(
'Distanza: ${beacon.distance.toStringAsFixed(2)}m'),
Text('RSSI: ${beacon.rssi} dBm'),
],
),
trailing: IconButton(
icon: Icon(Icons.save),
onPressed: () => _saveSelectedBeacon(beacon),
tooltip: 'Seleziona beacon',
),
),
);
},
),
),
],
),
// Scheda Permessi
const PermissionScreen(),
// Tab Log
Column(
children: [
Padding(
padding: EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: _refreshLogs,
child: Text('Aggiorna Log'),
),
ElevatedButton(
onPressed: () {
setState(() {
_logs.clear();
_addLog("Log cancellati");
});
},
child: Text('Cancella Log'),
),
],
),
),
Expanded(
child: _logs.isEmpty
? Center(child: Text('Nessun log disponibile'))
: ListView.builder(
itemCount: _logs.length,
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 4.0,
),
child: Text(
_logs[index],
style: TextStyle(
fontFamily: 'monospace',
fontSize: 12,
),
),
);
},
),
),
],
),
],
),
);
}
}