leafittome/lib/features/auth/data/household_bootstrap_provider.dart
cschlaefke 5ed3a68e45 Fix: Reaktiver Haushalts-Bootstrap + BootstrapGate (Apple-Login legte keinen Haushalt an)
Der Bootstrap hing an signInWithApple und lief erst, nachdem der Router
den Nutzer schon in die App geleitet hatte — Fehler blieben unsichtbar,
der Account blieb ohne users-Doc/Haushalt und alle Writes scheiterten an
den Rules. Jetzt beobachtet householdBootstrapProvider das users-Dokument
und legt Profil + Haushalt reaktiv an (alle Login-Wege); das BootstrapGate
hält die App so lange zurück und macht Fehler mit „Erneut versuchen"
sichtbar. Bestehende kaputte Accounts heilen beim nächsten Start selbst.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 17:49:49 +02:00

71 lines
2.5 KiB
Dart

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/firebase/firebase_providers.dart';
import 'auth_repository.dart';
/// users/{uid}-Dokument des angemeldeten Nutzers, live (null = ausgeloggt).
///
/// Existiert das Dokument nicht (erster Login), stößt
/// [householdBootstrapProvider] den Haushalts-Bootstrap an.
final userProfileProvider =
StreamProvider<DocumentSnapshot<Map<String, dynamic>>?>((ref) {
final user = ref.watch(authStateProvider).value;
if (user == null) return Stream.value(null);
return ref
.watch(firestoreProvider)
.collection('users')
.doc(user.uid)
.snapshots();
});
/// Reaktiver Haushalts-Bootstrap: legt Profil + eigenen Haushalt an, sobald
/// ein Nutzer angemeldet ist, für den noch kein users-Dokument existiert.
///
/// Deckt alle Login-Wege ab (E-Mail-Registrierung, Apple, künftig Google) —
/// unabhängig davon, wann der Router weiterleitet. Das BootstrapGate hält
/// die App zurück, bis das Profil da ist, und zeigt Fehler samt
/// „Erneut versuchen“ an.
class HouseholdBootstrap extends Notifier<AsyncValue<void>> {
@override
AsyncValue<void> build() {
final user = ref.watch(authStateProvider).value;
final profile = ref.watch(userProfileProvider).value;
// Nur aktiv, wenn der Snapshot sicher zum aktuellen Nutzer gehört und
// das Profil fehlt — bei Kontowechsel hält .value kurz den alten Stand.
if (user == null ||
profile == null ||
profile.id != user.uid ||
profile.exists) {
return const AsyncValue.data(null);
}
_run(user);
return const AsyncValue.loading();
}
Future<void> _run(User user) async {
try {
await ref
.read(authRepositoryProvider)
.ensureHouseholdBootstrap(user.uid, user.email);
// Kein Erfolgs-State nötig: das neue users-Dokument kommt über den
// Snapshot-Stream herein und baut diesen Notifier als „fertig“ neu.
} catch (error, stackTrace) {
if (!ref.mounted) return;
state = AsyncValue.error(error, stackTrace);
}
}
/// Nach einem Fehler erneut versuchen (Knopf im BootstrapGate).
void retry() {
final user = ref.read(authStateProvider).value;
if (user == null) return;
state = const AsyncValue.loading();
_run(user);
}
}
final householdBootstrapProvider =
NotifierProvider<HouseholdBootstrap, AsyncValue<void>>(
HouseholdBootstrap.new);