fix: survive corrupt preferences at startup; drop CocoaPods leftovers
Some checks failed
Security / Security check (push) Failing after 2s
Some checks failed
Security / Security check (push) Failing after 2s
A black window on launch, no error anywhere: main() awaited loadPersistedEndpoint before the first frame, and SharedPreferences.getBool threw 'int is not a subtype of bool?' — the store is writable from outside the app and hub.secure had been written as int 0. Pref reads now go through defensive typed helpers (int coerces to bool, wrong types fall back to defaults), and every pre-frame restore step is failure-isolated: a broken store can cost a preference, never the first frame. Regression tests stage the corrupt store (the exact observed value and worse). Also removes the CocoaPods leftovers from the macOS project (Podfile, [CP] script phases, Pods framework references, xcconfig includes): the project builds via Swift Package Manager, and the dual wiring ran both dependency managers on every build — Flutter's persistent 'removing CocoaPods will improve build time' warning. Verified: clean profile build produces a launchable bundle (plugins statically linked via SwiftPM), suite green, analyze clean. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
61ca365d5e
commit
06f023aada
9 changed files with 130 additions and 148 deletions
|
|
@ -106,14 +106,39 @@ class HubService {
|
|||
/// from "caller passed null to drop the token".
|
||||
static const Object _unset = Object();
|
||||
|
||||
/// Defensive typed preference reads. `shared_preferences` THROWS
|
||||
/// on a type mismatch — and the store is writable from outside
|
||||
/// the app (`defaults write` puts an int where a bool belongs).
|
||||
/// A corrupt preference must never take the app down with it
|
||||
/// (a thrown startup read = black window before the first
|
||||
/// frame); the worst allowed outcome is the built-in default.
|
||||
static String? _prefString(SharedPreferences prefs, String key) {
|
||||
final v = prefs.get(key);
|
||||
return v is String ? v : null;
|
||||
}
|
||||
|
||||
static int? _prefInt(SharedPreferences prefs, String key) {
|
||||
final v = prefs.get(key);
|
||||
if (v is int) return v;
|
||||
if (v is bool) return v ? 1 : 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
static bool? _prefBool(SharedPreferences prefs, String key) {
|
||||
final v = prefs.get(key);
|
||||
if (v is bool) return v;
|
||||
if (v is int) return v != 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Read persisted endpoint + auth token, then reconnect.
|
||||
/// Called once at app start; safe to call again after the
|
||||
/// operator updates the token in Settings.
|
||||
Future<void> loadPersistedEndpoint() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final host = prefs.getString(_kHostKey);
|
||||
final port = prefs.getInt(_kPortKey);
|
||||
final secure = prefs.getBool(_kSecureKey);
|
||||
final host = _prefString(prefs, _kHostKey);
|
||||
final port = _prefInt(prefs, _kPortKey);
|
||||
final secure = _prefBool(prefs, _kSecureKey);
|
||||
final token = await HubAuthToken.read();
|
||||
|
||||
// 1) An endpoint the operator explicitly chose in Settings wins.
|
||||
|
|
@ -202,7 +227,7 @@ class HubService {
|
|||
/// initial app startup. Defaults to system.
|
||||
Future<ThemeModeValue> loadThemeMode() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final raw = prefs.getString(_kThemeKey);
|
||||
final raw = _prefString(prefs, _kThemeKey);
|
||||
return ThemeModeValue.fromWire(raw) ?? ThemeModeValue.system;
|
||||
}
|
||||
|
||||
|
|
@ -217,7 +242,7 @@ class HubService {
|
|||
/// English; the sidebar toggle flips to German on demand.
|
||||
Future<Locale> loadLocale() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final raw = prefs.getString(_kLocaleKey);
|
||||
final raw = _prefString(prefs, _kLocaleKey);
|
||||
if (raw == 'de') return const Locale('de');
|
||||
return const Locale('en');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,16 +34,44 @@ import 'widgets/widgets.dart';
|
|||
/// and quick-glance proof that you're seeing the current build.
|
||||
const String kStudioVersion = '0.70.0';
|
||||
|
||||
/// Run one pre-frame restore step, absorbing any failure. Everything
|
||||
/// before `runApp` is a black window waiting to happen: an exception
|
||||
/// here (a corrupt preference store, an unreadable file) used to kill
|
||||
/// startup with no visible error at all. Each step degrades to its
|
||||
/// default instead; the app MUST reach the first frame.
|
||||
Future<T> _restoreOr<T>(T fallback, Future<T> Function() step) async {
|
||||
try {
|
||||
return await step();
|
||||
} catch (e) {
|
||||
debugPrint('startup restore step failed (using default): $e');
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
// Restore the persisted endpoint, theme mode, locale, and
|
||||
// (when installed) the operator's theme-plugin choice
|
||||
// before the first frame so nothing flickers on startup.
|
||||
await HubService.instance.loadPersistedEndpoint();
|
||||
await SystemActions.loadFaiBinaryOverride();
|
||||
final themeMode = await HubService.instance.loadThemeMode();
|
||||
final locale = await HubService.instance.loadLocale();
|
||||
final themePlugin = await loadActiveThemePlugin();
|
||||
// Every step is failure-isolated: one broken store must not
|
||||
// stop the others, and nothing here may prevent runApp.
|
||||
await _restoreOr(null, () async {
|
||||
await HubService.instance.loadPersistedEndpoint();
|
||||
return null;
|
||||
});
|
||||
await _restoreOr(null, () async {
|
||||
await SystemActions.loadFaiBinaryOverride();
|
||||
return null;
|
||||
});
|
||||
final themeMode = await _restoreOr(
|
||||
ThemeModeValue.system,
|
||||
() => HubService.instance.loadThemeMode(),
|
||||
);
|
||||
final locale = await _restoreOr(
|
||||
const Locale('en'),
|
||||
() => HubService.instance.loadLocale(),
|
||||
);
|
||||
final themePlugin = await _restoreOr(null, loadActiveThemePlugin);
|
||||
runApp(
|
||||
StudioApp(
|
||||
initialThemeMode: themeMode,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue