Fix: BootstrapGate als Overlay statt Baum-Austausch (markNeedsBuild during build)

Das Umschalten vom Warte-Bildschirm auf die App remountete den ganzen
App-Baum, während Riverpod noch Provider-Abhängigkeiten nachzog — das
löste die 'setState() or markNeedsBuild() called during build'-Warnung
aus. Der Splash liegt jetzt als deckende Stack-Ebene über der durchgehend
gemounteten App. Handoff: Bug vom Nutzer als behoben bestätigt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
cschlaefke 2026-07-23 21:50:29 +02:00
parent 0cc39f69b1
commit 51a304e986
2 changed files with 28 additions and 18 deletions

View file

@ -27,27 +27,22 @@ Private Flutter-App (iOS+Android) zur Pflanzenpflege für Haushalt + Pflanzen-Si
## Offen / Nächstes
### 🟡 Apple-Login-Bug: Fix implementiert, wartet auf Gerätetest durch den Nutzer
### ✅ Apple-Login-Bug behoben und vom Nutzer bestätigt (2026-07-23)
**Der Bug:** Mit einem neuen Apple-Account schlug das Anlegen einer Pflanze fehl („Speichern fehlgeschlagen"). Ursache (per Code-Analyse bestätigt, Firestore-Sichtung stand aus): Der Haushalts-Bootstrap hing an `signInWithApple` **nach** `signInWithProvider` — der Router leitet aber schon bei „angemeldet" weiter (redirect prüft nur `currentUser != null`). Wirft der Bootstrap oder wird die App vorher beendet, bleibt der Nutzer dauerhaft ohne `users`-Doc + Haushalt, und die (korrekten) Rules lehnen jeden Schreibzugriff ab.
**Der Bug in Kurzform:** Mit einem neuen Apple-Account schlug jedes Speichern fehl. Zwei Ursachen-Schichten:
1. Der Haushalts-Bootstrap hing an `signInWithApple` — der Router leitet aber schon bei „angemeldet" weiter; scheiterte der Bootstrap danach, blieb der Account dauerhaft ohne Haushalt (Rules lehnen dann korrekt jeden Write ab).
2. **Die eigentliche Falle:** Die Push-Registrierung (`_saveToken`, `set(..., merge: true)`) hatte das `users`-Dokument beim ersten Apple-Login längst angelegt (fcmTokens/timezone, aber ohne `householdId`). Eine Reparatur, die nur auf „users-Doc existiert" prüft, hielt den Account deshalb fälschlich für fertig.
**Der Fix (2026-07-23 implementiert, Option „reaktiver Bootstrap"):**
- `householdBootstrapProvider` (`lib/features/auth/data/household_bootstrap_provider.dart`): beobachtet das `users/{uid}`-Dokument; bei „angemeldet, aber kein Profil" legt er Profil + Haushalt an. Deckt **alle** Login-Wege ab (auch künftig Google).
- `BootstrapGate` (`lib/features/auth/presentation/bootstrap_gate.dart`, eingehängt in `app.dart` builder): hält die App zurück, bis das Profil existiert; zeigt Warte-Bildschirm bzw. Fehler mit „Erneut versuchen".
- `AuthRepository`: Bootstrap jetzt öffentlich (`ensureHouseholdBootstrap`, idempotent + In-Flight-Schutz); `signInWithApple` ruft ihn nicht mehr selbst auf.
- **Selbstheilend:** Der bereits kaputte Apple-Account wird beim nächsten App-Start automatisch repariert (Gate sieht „kein Profil" → legt Haushalt an).
- Analyzer grün, alle 9 Tests grün (neuer Test: „Erster Login ohne Profil → Haushalt wird reaktiv angelegt").
**Nachbesserung (gleicher Tag, nach erstem Fehlversuch des Nutzers):** Mit dem Gate-Build schlug Speichern **weiter** fehl, und zwar ohne dass der Einrichtungs-Bildschirm je erschien (mit und ohne Foto). Befund: Die **Push-Registrierung** (`_saveToken`, `set(..., merge: true)`) hatte das `users`-Dokument beim ersten Apple-Login längst angelegt (fcmTokens/timezone, aber ohne `householdId`) — Gate und Bootstrap prüften nur die **Dokument-Existenz** und hielten den Account deshalb für fertig. Fix: Überall ist jetzt **`householdId` vorhanden** der Maßstab (Gate, Bootstrap-Provider, `_bootstrapHouseholdIfNeeded`), und der Bootstrap schreibt das users-Dokument mit **merge**, damit Push-Felder erhalten bleiben. Außerdem zeigt die Speichern-Fehlermeldung jetzt den konkreten Firebase-Fehlercode an (plugin/code statt Generik). Neuer Test stellt exakt den kaputten Zustand nach (users-Doc nur mit fcmTokens) — alle 10 Tests grün.
**Was der Nutzer jetzt testen sollte:**
1. App auf dem iPhone neu bauen/starten, mit dem **bestehenden Apple-Account** anmelden → jetzt sollte kurz „Dein Haushalt wird eingerichtet …" erscheinen (diesmal wirklich, da das Gate jetzt auf `householdId` prüft), danach Pflanze-Anlegen mit und ohne Foto.
2. Optional Härtetest „echter Erst-Login": unter <https://appleid.apple.com> die Freigabe für „LeafItToMe" entfernen → nächster Login ist wieder „erster Login" → Ende-zu-Ende inkl. Pflanze mit Foto.
3. Falls es **weiter** fehlschlägt: Die Fehlermeldung nennt jetzt den Code — `firebase_storage/…` heißt Foto-Upload/Storage-Rules/IAM, `cloud_firestore/…` heißt Plant-Write/Firestore-Rules. Den Code melden, dann gezielt weiter.
**Der Fix (alles committet und gepusht):**
- `householdBootstrapProvider` (`lib/features/auth/data/household_bootstrap_provider.dart`): reaktiver Bootstrap — legt Profil + Haushalt an, sobald „angemeldet, aber **kein `householdId`** im users-Doc". Maßstab ist überall `householdId`, nie die Dokument-Existenz. Schreibt per **merge** (Push-Felder bleiben erhalten). Deckt alle Login-Wege ab (auch künftig Google).
- `BootstrapGate` (`lib/features/auth/presentation/bootstrap_gate.dart`, in `app.dart` eingehängt): legt bis dahin einen deckenden Warte-Bildschirm **über** die App (Stack, kein Austausch des App-Baums — ein Remount mitten im Provider-Update löste „markNeedsBuild during build" aus); Fehler werden mit „Erneut versuchen" sichtbar.
- `AuthRepository`: `ensureHouseholdBootstrap` öffentlich, idempotent, In-Flight-Schutz; `signInWithApple` ruft ihn nicht mehr selbst auf. Selbstheilend für bereits kaputte Accounts.
- Speicher-Fehler in `plant_form_screen` zeigen jetzt den Firebase-Fehlercode (plugin/code) statt einer Generik.
- **Vom Nutzer auf dem iPhone bestätigt:** Anmeldung + Pflanze anlegen funktionieren mit dem Apple-Account.
### Zuletzt erledigt
- **2026-07-23 (2):** Reaktiver Haushalts-Bootstrap + BootstrapGate implementiert (Fix für den Apple-Login-Bug, siehe oben); Architektur-Doku um die Entscheidung ergänzt.
- **2026-07-23 (2):** Apple-Login-Bug diagnostiziert und in zwei Schritten behoben (reaktiver Bootstrap + householdId-Maßstab, siehe oben); Riverpod-„markNeedsBuild during build"-Warnung per Overlay-Gate beseitigt; Architektur-Doku um die Bootstrap-Entscheidung ergänzt.
- **2026-07-23 (1):** Apple-Login **implementiert und committet** (`signInWithApple` via eingebautem `AppleAuthProvider`, „oder"-Trenner + Button im Login, iOS-Entitlement `com.apple.developer.applesignin`, l10n-Strings, Doku `firebase-einrichtung.md` Schritt 7). Konfiguration vom Nutzer erledigt, Login funktioniert.
- **2026-07-20:** iOS-Push eingerichtet (Apple Developer Account freigeschaltet, APNs/Firebase/Xcode). V1 damit auch auf iOS vollständig.

View file

@ -6,10 +6,12 @@ import '../../../l10n/generated/app_localizations.dart';
import '../data/household_bootstrap_provider.dart';
/// Hält die App zurück, solange zum angemeldeten Nutzer noch kein
/// users-Dokument (und damit kein Haushalt) existiert.
/// Haushalt existiert.
///
/// Beim ersten Login (z. B. mit Apple) legt [householdBootstrapProvider]
/// beides an; bis dahin gibt es einen Warte-Bildschirm. Schlägt der
/// Profil + Haushalt an; bis dahin liegt ein deckender Warte-Bildschirm
/// ÜBER der App (Stack statt Austausch ein Remount des App-Baums mitten
/// im Provider-Update löst markNeedsBuild during build aus). Schlägt der
/// Bootstrap fehl, wird der Fehler hier sichtbar vorher konnte man in
/// der App landen, deren Schreibzugriffe dann alle an den Rules scheiterten.
class BootstrapGate extends ConsumerWidget {
@ -30,6 +32,19 @@ class BootstrapGate extends ConsumerWidget {
profile.data()?['householdId'] != null;
if (profileReady) return child;
return Stack(
children: [
child,
_BootstrapSplash(),
],
);
}
}
/// Deckender Warte-/Fehler-Bildschirm, solange der Bootstrap läuft.
class _BootstrapSplash extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final bootstrap = ref.watch(householdBootstrapProvider);
final l10n = AppLocalizations.of(context);
final theme = Theme.of(context);