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

@@ -41,6 +41,7 @@ class _DirectMessageSheetState extends State<DirectMessageSheet> {
}
void _updateCharacterCount() {
if (!mounted) return;
setState(() {
_characterCount = _textController.text.length;
});

View File

@@ -21,6 +21,7 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
final FocusNode _focusNode = FocusNode();
bool _isLoggingIn = false;
bool _obscurePassword = true;
bool _isDisposed = false; // Track disposal state for async callbacks
@override
void initState() {
@@ -30,6 +31,7 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
@override
void dispose() {
_isDisposed = true;
_passwordController.dispose();
_focusNode.dispose();
super.dispose();
@@ -245,15 +247,15 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
' Messages will be fetched when onMessageWaiting callback is triggered',
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.loggedInSuccessfully),
backgroundColor: Theme.of(context).colorScheme.primary,
duration: const Duration(seconds: 3),
),
);
}
// Check both _isDisposed flag and mounted to handle race conditions
if (_isDisposed || !mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.loggedInSuccessfully),
backgroundColor: Theme.of(context).colorScheme.primary,
duration: const Duration(seconds: 3),
),
);
};
connectionProvider.onLoginFail = (publicKeyPrefix) {
@@ -263,15 +265,15 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
debugPrint('❌ [RoomLogin] Login failed - incorrect password');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.loginFailed),
backgroundColor: Theme.of(context).colorScheme.error,
duration: const Duration(seconds: 3),
),
);
}
// Check both _isDisposed flag and mounted to handle race conditions
if (_isDisposed || !mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.loginFailed),
backgroundColor: Theme.of(context).colorScheme.error,
duration: const Duration(seconds: 3),
),
);
};
try {