fix: Add mounted checks and fix memory leaks across UI components

Address potential memory leaks and state-after-dispose issues:

- AppProvider: Add dispose method to clean up listeners and callbacks
- MapTab: Store and restore original location callback instead of nullifying
- MessagesTab: Use Timer instead of Future.delayed for highlight cleanup
- ConnectionDialog: Use named listener method for proper tab controller cleanup
- RoomLoginSheet: Add _isDisposed flag to handle async callback race conditions
- SettingsScreen: Clear location service callbacks in dispose
- Various screens: Add mounted checks before setState after async operations
  (contacts_tab, device_config, home_screen, map_management, packet_log,
   sar_template_management, sar_update_sheet, permission_request_dialog)
This commit is contained in:
Janez Troha
2025-12-04 19:34:48 +01:00
parent 3e2e4aa4d9
commit 96824e455e
15 changed files with 119 additions and 57 deletions

View File

@@ -78,6 +78,9 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
// MBTiles layers
List<MapLayer> _mbtilesLayers = [];
// Store original location callback to restore in dispose
void Function(Position)? _originalLocationCallback;
// WMS layers (Slovenian)
late final MapLayer _slovenianAerialLayer;
late final MapLayer _dtk25Layer;
@@ -128,6 +131,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
// Listen to map provider for navigation requests
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return; // Check if widget is still mounted
final mapProvider = context.read<MapProvider>();
mapProvider.addListener(_handleMapNavigation);
// Load WMS overlay state
@@ -146,13 +150,13 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
/// Note: LocationTrackingService is initialized and started by AppProvider
/// This method only adds map-specific callbacks for rotation and UI updates
void _setupLocationCallbacks() {
// Store the original callback from AppProvider
final originalCallback = _locationService.onPositionUpdate;
// Store the original callback from AppProvider to restore in dispose
_originalLocationCallback = _locationService.onPositionUpdate;
// Add map-specific callback that chains with the original
_locationService.onPositionUpdate = (position) {
// Call original callback first (AppProvider's logging)
originalCallback?.call(position);
_originalLocationCallback?.call(position);
// Then handle map-specific logic - early exit if not mounted or disposing
if (!mounted || _isDisposing) {
@@ -437,8 +441,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
mapProvider.removeListener(_handleMapNavigation);
// DO NOT stop location tracking - it's managed by AppProvider
// Just clear the map-specific callback
_locationService.onPositionUpdate = null;
// Restore the original callback instead of setting to null
_locationService.onPositionUpdate = _originalLocationCallback;
_mapController.dispose();
_tileCache.dispose();
super.dispose();