From 1c27d48987a437d5e739a0b95cd2437434675642 Mon Sep 17 00:00:00 2001 From: Strycher Date: Sat, 20 Jun 2026 03:47:43 -0400 Subject: [PATCH] fix(#28): guard settings_screen action flows against silent failures (pass 2) settings_screen action handlers fire-and-forgot or awaited-without-catch several connector calls, and _loadVersionInfo called setState after an await with no mounted guard. Add a mounted guard to _loadVersionInfo; try/catch + error snackbar to the GPS-enable toggles (inline + _editLocation dialog) and getContacts; await syncTime in _syncTime so the 'synchronized' snackbar shows only on success (it previously fired before the await); and in _editLocation's save, capture the messenger before the pop and wrap the setCustomVar/setNodeLocation/refreshDeviceInfo awaits. Verified non-issues: deleteAllPaths is void, _PrivacySection._apply() already has a try/catch, and exportGPX catches internally and returns gpxExportFailed. Completes #28; error strings English for now (separate l10n sweep). Co-Authored-By: Claude Opus 4.8 --- lib/screens/settings_screen.dart | 93 +++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 12 deletions(-) diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 58fcf6f..c417084 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -53,6 +53,7 @@ class _SettingsScreenState extends State { Future _loadVersionInfo() async { final packageInfo = await PackageInfo.fromPlatform(); + if (!mounted) return; setState(() { _appVersion = packageInfo.version; }); @@ -206,7 +207,18 @@ class _SettingsScreenState extends State { subtitle: Text(l10n.settings_locationGPSEnableSubtitle), value: connector.currentCustomVars?['gps'] == '1', onChanged: (value) async { - await connector.setCustomVar(value ? 'gps:1' : 'gps:0'); + try { + await connector.setCustomVar(value ? 'gps:1' : 'gps:0'); + } catch (_) { + if (context.mounted) { + showDismissibleSnackBar( + context, + content: const Text( + 'Could not update GPS setting. Please try again.', + ), + ); + } + } }, ), ], @@ -463,7 +475,20 @@ class _SettingsScreenState extends State { leading: const Icon(Icons.refresh), title: Text(l10n.settings_refreshContacts), subtitle: Text(l10n.settings_refreshContactsSubtitle), - onTap: () => connector.getContacts(), + onTap: () async { + try { + await connector.getContacts(); + } catch (_) { + if (context.mounted) { + showDismissibleSnackBar( + context, + content: const Text( + 'Could not refresh contacts. Please try again.', + ), + ); + } + } + }, ), const Divider(height: 1), ListTile( @@ -698,10 +723,17 @@ class _SettingsScreenState extends State { value: isGPSEnabled, onChanged: (value) async { setDialogState(() => isGPSEnabled = value); - if (value) { - await connector.setCustomVar("gps:1"); - } else { - await connector.setCustomVar("gps:0"); + try { + await connector.setCustomVar(value ? "gps:1" : "gps:0"); + } catch (_) { + if (context.mounted) { + showDismissibleSnackBar( + context, + content: const Text( + 'Could not update GPS setting. Please try again.', + ), + ); + } } }, ), @@ -715,6 +747,7 @@ class _SettingsScreenState extends State { ), TextButton( onPressed: () async { + final messenger = ScaffoldMessenger.of(context); Navigator.pop(context); if (hasGPS) { @@ -733,8 +766,19 @@ class _SettingsScreenState extends State { return; } - await connector.setCustomVar("gps_interval:$interval"); - await connector.refreshDeviceInfo(); + try { + await connector.setCustomVar("gps_interval:$interval"); + await connector.refreshDeviceInfo(); + } catch (_) { + messenger.showSnackBar( + const SnackBar( + content: Text( + 'Could not update location. Please try again.', + ), + ), + ); + return; + } if (!context.mounted) return; showDismissibleSnackBar( context, @@ -773,8 +817,19 @@ class _SettingsScreenState extends State { return; } - await connector.setNodeLocation(lat: lat, lon: lon); - await connector.refreshDeviceInfo(); + try { + await connector.setNodeLocation(lat: lat, lon: lon); + await connector.refreshDeviceInfo(); + } catch (_) { + messenger.showSnackBar( + const SnackBar( + content: Text( + 'Could not update location. Please try again.', + ), + ), + ); + return; + } if (!context.mounted) return; showDismissibleSnackBar( context, @@ -789,9 +844,23 @@ class _SettingsScreenState extends State { ); } - void _syncTime(BuildContext context, MeshCoreConnector connector) { + Future _syncTime( + BuildContext context, + MeshCoreConnector connector, + ) async { final l10n = context.l10n; - connector.syncTime(); + try { + await connector.syncTime(); + } catch (_) { + if (context.mounted) { + showDismissibleSnackBar( + context, + content: const Text('Could not sync time. Please try again.'), + ); + } + return; + } + if (!context.mounted) return; showDismissibleSnackBar( context, content: Text(l10n.settings_timeSynchronized),