chore(02-01): add provider dependencies and refactor GpsDataManager
- Added provider ^6.1.2 and shared_preferences ^2.3.4 to pubspec.yaml - Removed singleton pattern from GpsDataManager (deleted _instance and instance getter) - Changed GpsDataManager._internal() to public GpsDataManager() constructor - Extended ChangeNotifier to enable reactive state updates - Added _isDisposed guard field to prevent disposed notifications - Updated _updateData() to call notifyListeners() after stream emission - Added _isDisposed guards to _onGpsError() and _handleStaleDataTimeout() - Updated dispose() to set _isDisposed, call super.dispose() last, removed _isInitialized reset
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:geolocator/geolocator.dart';
|
import 'package:geolocator/geolocator.dart';
|
||||||
import '../gps_service.dart';
|
import '../gps_service.dart';
|
||||||
import '../config/speed_unit.dart';
|
import '../config/speed_unit.dart';
|
||||||
@@ -6,11 +7,8 @@ import '../config/gps_constants.dart';
|
|||||||
import '../models/processed_gps_data.dart';
|
import '../models/processed_gps_data.dart';
|
||||||
import 'logger.dart';
|
import 'logger.dart';
|
||||||
|
|
||||||
class GpsDataManager {
|
class GpsDataManager extends ChangeNotifier {
|
||||||
static GpsDataManager? _instance;
|
GpsDataManager();
|
||||||
static GpsDataManager get instance => _instance ??= GpsDataManager._internal();
|
|
||||||
|
|
||||||
GpsDataManager._internal();
|
|
||||||
|
|
||||||
StreamSubscription<Position>? _gpsSubscription;
|
StreamSubscription<Position>? _gpsSubscription;
|
||||||
final StreamController<ProcessedGpsData> _dataController =
|
final StreamController<ProcessedGpsData> _dataController =
|
||||||
@@ -26,6 +24,7 @@ class GpsDataManager {
|
|||||||
);
|
);
|
||||||
|
|
||||||
Timer? _staleDataTimer;
|
Timer? _staleDataTimer;
|
||||||
|
bool _isDisposed = false;
|
||||||
|
|
||||||
// Public stream for UI components to subscribe to
|
// Public stream for UI components to subscribe to
|
||||||
Stream<ProcessedGpsData> get dataStream => _dataController.stream;
|
Stream<ProcessedGpsData> get dataStream => _dataController.stream;
|
||||||
@@ -157,6 +156,7 @@ class GpsDataManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onGpsError(dynamic error) {
|
void _onGpsError(dynamic error) {
|
||||||
|
if (_isDisposed) return;
|
||||||
// Handle GPS errors gracefully
|
// Handle GPS errors gracefully
|
||||||
final errorString = error.toString().toLowerCase();
|
final errorString = error.toString().toLowerCase();
|
||||||
if (!errorString.contains('timeout') && !errorString.contains('temporarily')) {
|
if (!errorString.contains('timeout') && !errorString.contains('temporarily')) {
|
||||||
@@ -175,6 +175,7 @@ class GpsDataManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleStaleDataTimeout() {
|
void _handleStaleDataTimeout() {
|
||||||
|
if (_isDisposed) return;
|
||||||
// Avoid overriding explicit GPS error states or repeated stale notifications
|
// Avoid overriding explicit GPS error states or repeated stale notifications
|
||||||
final displayText = _currentData.displaySpeed;
|
final displayText = _currentData.displaySpeed;
|
||||||
if (displayText == '--' || displayText.startsWith('GPS') || displayText == 'NO PERM') {
|
if (displayText == '--' || displayText.startsWith('GPS') || displayText == 'NO PERM') {
|
||||||
@@ -198,9 +199,11 @@ class GpsDataManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _updateData(ProcessedGpsData newData) {
|
void _updateData(ProcessedGpsData newData) {
|
||||||
|
if (_isDisposed) return;
|
||||||
Logger.debug('Broadcasting data: ${newData.displaySpeed} ${newData.displayHeading}', 'GpsDataManager');
|
Logger.debug('Broadcasting data: ${newData.displaySpeed} ${newData.displayHeading}', 'GpsDataManager');
|
||||||
_currentData = newData;
|
_currentData = newData;
|
||||||
_dataController.add(newData);
|
_dataController.add(newData);
|
||||||
|
notifyListeners();
|
||||||
Logger.debug('Data broadcast complete', 'GpsDataManager');
|
Logger.debug('Data broadcast complete', 'GpsDataManager');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,7 +215,9 @@ class GpsDataManager {
|
|||||||
return convertedSpeed < 1.0 ? '--' : convertedSpeed.toStringAsFixed(1);
|
return convertedSpeed < 1.0 ? '--' : convertedSpeed.toStringAsFixed(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_isDisposed = true;
|
||||||
Logger.info('Disposing GPS manager...', 'GpsDataManager');
|
Logger.info('Disposing GPS manager...', 'GpsDataManager');
|
||||||
if (_gpsSubscription != null) {
|
if (_gpsSubscription != null) {
|
||||||
Logger.debug('Cancelling GPS subscription...', 'GpsDataManager');
|
Logger.debug('Cancelling GPS subscription...', 'GpsDataManager');
|
||||||
@@ -223,7 +228,7 @@ class GpsDataManager {
|
|||||||
_staleDataTimer?.cancel();
|
_staleDataTimer?.cancel();
|
||||||
_staleDataTimer = null;
|
_staleDataTimer = null;
|
||||||
_dataController.close();
|
_dataController.close();
|
||||||
_isInitialized = false;
|
|
||||||
Logger.info('GPS manager disposed', 'GpsDataManager');
|
Logger.info('GPS manager disposed', 'GpsDataManager');
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ dependencies:
|
|||||||
# Bring app to foreground from background/overlay
|
# Bring app to foreground from background/overlay
|
||||||
bg_launcher: ^0.1.0
|
bg_launcher: ^0.1.0
|
||||||
|
|
||||||
|
# State management
|
||||||
|
provider: ^6.1.2
|
||||||
|
|
||||||
|
# Persistent settings storage
|
||||||
|
shared_preferences: ^2.3.4
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user