fix(di): robust all-or-nothing init so the social layer never greys out
The idempotency guard used SocialService as its sentinel, but that was registered mid-cascade, so a run that crashed part-way could leave a half-built container the next run would reuse (or hit 'already registered' on) — intermittently falling back to the inventory-only look with market/chat/profile greyed out. - Guard on a dedicated _DepsReady marker registered LAST, so the sentinel means the WHOLE container is wired, never a partial one. - Reset a half-wired container (crashed prior run) before rebuilding, instead of reusing it. Safe: main aborts before runApp on a partial run. - Make the social key derivation non-fatal: on failure the app opens on inventory (social hidden) rather than blanking. main tolerates an absent SocialService.
This commit is contained in:
parent
97b9223cb2
commit
606861bbfa
2 changed files with 46 additions and 9 deletions
|
|
@ -40,15 +40,33 @@ import '../services/social_settings.dart';
|
|||
/// in the tree.
|
||||
final GetIt getIt = GetIt.instance;
|
||||
|
||||
/// End-of-init sentinel: registered last, so `isRegistered<_DepsReady>()` means
|
||||
/// the container is fully wired (see [configureDependencies]). Distinct from
|
||||
/// [SocialService], which is optional and absent in inventory-only mode.
|
||||
class _DepsReady {
|
||||
const _DepsReady();
|
||||
}
|
||||
|
||||
/// Wires the encrypted DB, keystore and repositories. Call once from `main`
|
||||
/// before `runApp`; the DB key must exist before the DB opens.
|
||||
///
|
||||
/// Idempotent: a hot restart (or an Android Activity restart that keeps the
|
||||
/// isolate) re-runs `main` while GetIt still holds the singletons. Without this
|
||||
/// guard the re-registration throws, `main` aborts before rebuilding `TaneApp`
|
||||
/// with the social layer, and the app falls back to the "inventory only" look.
|
||||
/// Idempotent AND all-or-nothing: a hot restart (or an Android Activity restart
|
||||
/// that keeps the isolate) re-runs `main` while GetIt still holds the singletons.
|
||||
///
|
||||
/// A dedicated [_DepsReady] marker is registered LAST and used as the "fully
|
||||
/// wired" sentinel, so `isRegistered<_DepsReady>()` means the WHOLE container is
|
||||
/// ready — never a half-built one. If a prior run crashed part-way through init
|
||||
/// (leaving some singletons registered but not the marker), we reset the
|
||||
/// container and rebuild from scratch rather than early-returning on a partial
|
||||
/// container or throwing "already registered" mid-cascade. That partial state is
|
||||
/// safe to reset because `main` aborts before `runApp` on a partial run, so no
|
||||
/// live UI holds these singletons. Without this, the app intermittently fell
|
||||
/// back to the "inventory only" look (market/chat/profile greyed out).
|
||||
Future<void> configureDependencies() async {
|
||||
if (getIt.isRegistered<SocialService>()) return;
|
||||
if (getIt.isRegistered<_DepsReady>()) return; // fully wired already
|
||||
// A prior run half-wired the container (crashed after some registrations but
|
||||
// before the sentinel). Clear it so we rebuild cleanly.
|
||||
if (getIt.isRegistered<AppDatabase>()) await getIt.reset();
|
||||
|
||||
final secretStore = FlutterSecretStore();
|
||||
final keyStore = SecureKeyStore(store: secretStore);
|
||||
|
|
@ -66,8 +84,15 @@ Future<void> configureDependencies() async {
|
|||
|
||||
// Block 2 social identity: a secp256k1 Nostr key derived (one-way) from the
|
||||
// SAME root seed, so it needs no extra backup. Cheap + offline; no relay is
|
||||
// contacted here (local-first — the social layer only enriches).
|
||||
final socialService = await SocialService.fromRootSeedHex(rootSeedHex);
|
||||
// contacted here (local-first — the social layer only enriches). Non-fatal: if
|
||||
// the derivation ever fails, the app still opens on inventory (market/chat/
|
||||
// profile stay hidden) rather than blanking the whole app.
|
||||
SocialService? socialService;
|
||||
try {
|
||||
socialService = await SocialService.fromRootSeedHex(rootSeedHex);
|
||||
} catch (e, s) {
|
||||
debugPrint('Social identity derivation failed; inventory-only mode: $e\n$s');
|
||||
}
|
||||
|
||||
// Seed the bundled species catalog before the UI opens — but only once per
|
||||
// catalog version. Parsing the ~1.5 MB catalog and scanning the species table
|
||||
|
|
@ -108,7 +133,6 @@ Future<void> configureDependencies() async {
|
|||
..registerSingleton<LabelTextExtractor>(labelExtractor)
|
||||
..registerSingleton<OnboardingStore>(OnboardingStore(secretStore))
|
||||
..registerSingleton<LocaleStore>(LocaleStore(secretStore))
|
||||
..registerSingleton<SocialService>(socialService)
|
||||
..registerSingleton<SocialSettings>(SocialSettings(secretStore))
|
||||
..registerSingleton<OfferOutbox>(OfferOutbox(secretStore))
|
||||
..registerSingleton<MessageStore>(MessageStore(secretStore))
|
||||
|
|
@ -139,6 +163,17 @@ Future<void> configureDependencies() async {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Optional: absent only if the derivation above failed — then the app runs
|
||||
// inventory-only, by design (market/chat/profile hidden).
|
||||
if (socialService != null) {
|
||||
getIt.registerSingleton<SocialService>(socialService);
|
||||
}
|
||||
|
||||
// Registered LAST: the "fully wired" sentinel the guard above checks. Anything
|
||||
// that throws before here leaves the marker unregistered, so the next run
|
||||
// rebuilds cleanly instead of reusing a half-built container.
|
||||
getIt.registerSingleton<_DepsReady>(const _DepsReady());
|
||||
}
|
||||
|
||||
Future<Directory> _backupsDir() async {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,9 @@ Future<void> main() async {
|
|||
repository: getIt<VarietyRepository>(),
|
||||
species: getIt<SpeciesRepository>(),
|
||||
onboarding: onboarding,
|
||||
social: getIt<SocialService>(),
|
||||
social: getIt.isRegistered<SocialService>()
|
||||
? getIt<SocialService>()
|
||||
: null,
|
||||
socialSettings: getIt<SocialSettings>(),
|
||||
location: const GeolocatorCoarseLocation(),
|
||||
outbox: getIt<OfferOutbox>(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue