Surface persistence and fetch errors instead of swallowing them

PrefUtil.setValue now returns the underlying SharedPreferences result
and throws on unsupported types; settings save reports failures rather
than always showing success. Device detail and event history report
the real Dio error message and skip cancellations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
rzuasti
2026-05-31 18:43:46 -04:00
co-authored by Claude Sonnet 4.6
parent f7f255e20f
commit e70676a193
5 changed files with 47 additions and 21 deletions
+14 -4
View File
@@ -1,3 +1,4 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
@@ -42,8 +43,9 @@ class _DeviceDetailState extends State<DeviceDetail> {
});
} catch (e) {
if (!mounted) return;
if (e is DioException && e.type == DioExceptionType.cancel) return;
setState(() {
_error = e.toString();
_error = dioErrorToUserMessage(e);
_isLoading = false;
});
}
@@ -140,7 +142,10 @@ class _DeviceHeader extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SelectableText(device.ipv4Address, style: theme.textTheme.headlineSmall),
SelectableText(
device.ipv4Address,
style: theme.textTheme.headlineSmall,
),
const SizedBox(height: 4),
if (device.isRegistered)
StatusBadge(label: 'Registered', color: BadgeColor.success)
@@ -166,7 +171,10 @@ class _DeviceInfoCard extends StatelessWidget {
Widget build(BuildContext context) {
final formatter = FriendlyDateFormatter();
final rows = <(String, String)>[
('Name', device.name == null || device.name!.isEmpty ? '' : device.name!),
(
'Name',
device.name == null || device.name!.isEmpty ? '' : device.name!,
),
('MAC Address', device.macAddress),
('IP Address', device.ipv4Address),
('Vendor', device.vendor.isEmpty ? '' : device.vendor),
@@ -251,7 +259,9 @@ class _InfoRow extends StatelessWidget {
),
),
),
Expanded(child: SelectableText(value, style: theme.textTheme.bodyMedium)),
Expanded(
child: SelectableText(value, style: theme.textTheme.bodyMedium),
),
],
),
);
@@ -1,3 +1,4 @@
import 'package:dio/dio.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
@@ -98,8 +99,9 @@ class _DeviceEventHistoryState extends State<DeviceEventHistory> {
});
} catch (e) {
if (!mounted) return;
if (e is DioException && e.type == DioExceptionType.cancel) return;
setState(() {
_error = e.toString();
_error = dioErrorToUserMessage(e);
_isLoading = false;
});
}
@@ -115,9 +117,9 @@ class _DeviceEventHistoryState extends State<DeviceEventHistory> {
}
if (_error != null) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Center(child: Text('Failed to load event history')),
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Center(child: Text('Failed to load event history: $_error')),
);
}
+4 -3
View File
@@ -44,10 +44,11 @@ class AppState extends ChangeNotifier {
String get themeKey => _themeKey;
ThemeData get theme => _themes[_themeKey]!;
void setTheme(String key) {
if (!_themes.containsKey(key) || key == _themeKey) return;
Future<bool> setTheme(String key) async {
if (!_themes.containsKey(key)) return false;
if (key == _themeKey) return true;
_themeKey = key;
PrefUtil.setValue('theme', key);
notifyListeners();
return PrefUtil.setValue('theme', key);
}
}
+16 -6
View File
@@ -67,13 +67,23 @@ class _SettingsState extends State<Settings> {
}
}
void _save() {
Future<void> _save() async {
if (!_formKey.currentState!.validate()) return;
PrefUtil.setValue('base_url', _baseUrlController.text);
PrefUtil.setValue('api_key', XOR().xorEncode(_apiKeyController.text));
BackendAPI.instance.reconfigureFromPrefs();
context.read<AppState>().setTheme(_selectedTheme);
UISnackbars.showSuccess(context, 'Settings saved successfully');
final urlOk = await PrefUtil.setValue('base_url', _baseUrlController.text);
if (!mounted) return;
final keyOk = await PrefUtil.setValue(
'api_key',
XOR().xorEncode(_apiKeyController.text),
);
if (!mounted) return;
final themeOk = await context.read<AppState>().setTheme(_selectedTheme);
if (!mounted) return;
if (urlOk && keyOk && themeOk) {
BackendAPI.instance.reconfigureFromPrefs();
UISnackbars.showSuccess(context, 'Settings saved successfully');
} else {
UISnackbars.showError(context, 'Failed to save settings');
}
}
@override
+7 -4
View File
@@ -10,15 +10,18 @@ class PrefUtil {
return preferences;
}
static void setValue(String key, Object value) {
static Future<bool> setValue(String key, Object value) {
switch (value) {
case String s:
preferences.setString(key, s);
return preferences.setString(key, s);
case bool b:
preferences.setBool(key, b);
return preferences.setBool(key, b);
case int i:
preferences.setInt(key, i);
return preferences.setInt(key, i);
default:
throw ArgumentError(
'Unsupported pref value type: ${value.runtimeType}',
);
}
}