fix: safe scan notifications

ref:
This commit is contained in:
Janez T
2026-03-05 20:10:08 +01:00
parent 42fc1f9cd2
commit 03c0c7ad38
2 changed files with 21 additions and 14 deletions

View File

@@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import '../models/device_info.dart'; import '../models/device_info.dart';
@@ -461,7 +462,7 @@ class ConnectionProvider with ChangeNotifier {
_isScanning = true; _isScanning = true;
_scannedDevices.clear(); _scannedDevices.clear();
_error = null; _error = null;
notifyListeners(); _notifyListenersSafely();
debugPrint('✅ [Provider] Scan state initialized, notifying listeners'); debugPrint('✅ [Provider] Scan state initialized, notifying listeners');
try { try {
@@ -477,7 +478,7 @@ class ConnectionProvider with ChangeNotifier {
debugPrint( debugPrint(
'✅ [Provider] Added device to list: ${device.platformName} (RSSI: $rssi dBm), total: ${_scannedDevices.length}', '✅ [Provider] Added device to list: ${device.platformName} (RSSI: $rssi dBm), total: ${_scannedDevices.length}',
); );
notifyListeners(); _notifyListenersSafely();
} else { } else {
// Update RSSI if device already exists // Update RSSI if device already exists
final index = _scannedDevices.indexWhere( final index = _scannedDevices.indexWhere(
@@ -488,7 +489,7 @@ class ConnectionProvider with ChangeNotifier {
debugPrint( debugPrint(
' 🔄 [Provider] Updated RSSI for ${device.platformName}: $rssi dBm', ' 🔄 [Provider] Updated RSSI for ${device.platformName}: $rssi dBm',
); );
notifyListeners(); _notifyListenersSafely();
} else { } else {
debugPrint( debugPrint(
' ⏭️ [Provider] Device already in list with same RSSI, skipping', ' ⏭️ [Provider] Device already in list with same RSSI, skipping',
@@ -502,7 +503,7 @@ class ConnectionProvider with ChangeNotifier {
} finally { } finally {
debugPrint('🏁 [Provider] Scan completed'); debugPrint('🏁 [Provider] Scan completed');
_isScanning = false; _isScanning = false;
notifyListeners(); _notifyListenersSafely();
} }
} }
@@ -510,6 +511,18 @@ class ConnectionProvider with ChangeNotifier {
Future<void> stopScan() async { Future<void> stopScan() async {
await FlutterBluePlus.stopScan(); await FlutterBluePlus.stopScan();
_isScanning = false; _isScanning = false;
_notifyListenersSafely();
}
void _notifyListenersSafely() {
final phase = SchedulerBinding.instance.schedulerPhase;
if (phase == SchedulerPhase.transientCallbacks ||
phase == SchedulerPhase.persistentCallbacks) {
SchedulerBinding.instance.addPostFrameCallback((_) {
notifyListeners();
});
return;
}
notifyListeners(); notifyListeners();
} }

View File

@@ -16,6 +16,7 @@ class ConnectionDialog extends StatefulWidget {
class _ConnectionDialogState extends State<ConnectionDialog> class _ConnectionDialogState extends State<ConnectionDialog>
with SingleTickerProviderStateMixin { with SingleTickerProviderStateMixin {
late TabController _tabController; late TabController _tabController;
late final ConnectionProvider _connectionProvider;
final NetworkScannerService _networkScanner = NetworkScannerService(); final NetworkScannerService _networkScanner = NetworkScannerService();
final List<DiscoveredServer> _discoveredServers = []; final List<DiscoveredServer> _discoveredServers = [];
int _scannedCount = 0; int _scannedCount = 0;
@@ -46,16 +47,13 @@ class _ConnectionDialogState extends State<ConnectionDialog>
void initState() { void initState() {
super.initState(); super.initState();
_tabController = TabController(length: 2, vsync: this); _tabController = TabController(length: 2, vsync: this);
_connectionProvider = Provider.of<ConnectionProvider>(context, listen: false);
// Defer scan startup until after the first frame so Provider listeners // Defer scan startup until after the first frame so Provider listeners
// are not notified while this dialog is still being built. // are not notified while this dialog is still being built.
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return; if (!mounted) return;
final connectionProvider = Provider.of<ConnectionProvider>( _connectionProvider.startScan();
context,
listen: false,
);
connectionProvider.startScan();
}); });
// Set up network scanner callbacks // Set up network scanner callbacks
@@ -85,11 +83,7 @@ class _ConnectionDialogState extends State<ConnectionDialog>
@override @override
void dispose() { void dispose() {
final connectionProvider = Provider.of<ConnectionProvider>( _connectionProvider.stopScan();
context,
listen: false,
);
connectionProvider.stopScan();
_networkScanner.stopScan(); _networkScanner.stopScan();
// Remove listener before disposing to prevent memory leaks // Remove listener before disposing to prevent memory leaks
_tabController.removeListener(_onTabChanged); _tabController.removeListener(_onTabChanged);