fix: only rebuild AuthGate on actual login state changes

- AuthGate now tracks wasLoggedIn vs isLoggedIn and only calls setState
  when the login status actually flips, preventing token-refresh events
  from tearing down open dialogs and causing _dependents.isEmpty crashes
- Fix TextEditingController disposal order in scanner manual entry dialog
This commit is contained in:
Lukas Müllner 2026-02-23 08:50:39 +01:00
parent 7a6975e146
commit bbcacc0664
2 changed files with 16 additions and 9 deletions

View file

@ -59,18 +59,24 @@ class _AuthGateState extends State<AuthGate> {
// 1. Check for an existing session on cold start. // 1. Check for an existing session on cold start.
_session = supabase.auth.currentSession; _session = supabase.auth.currentSession;
// 2. Listen for auth state changes (sign-in, sign-out, // 2. Listen for auth state changes but only rebuild when login
// password-recovery deep link, token refresh, etc.) // status actually changes (signed-in signed-out), NOT on
// every token refresh, to avoid tearing down open dialogs.
supabase.auth.onAuthStateChange.listen( supabase.auth.onAuthStateChange.listen(
(AuthState authState) { (AuthState authState) {
if (!mounted) return; if (!mounted) return;
setState(() {
_session = authState.session; final wasLoggedIn = _session != null;
}); final isLoggedIn = authState.session != null;
_session = authState.session;
// Only rebuild the tree when login state actually flips.
if (wasLoggedIn != isLoggedIn) {
setState(() {});
}
// If the user just clicked a password-reset link from their email, // If the user just clicked a password-reset link from their email,
// Supabase fires a PASSWORD_RECOVERY event. We can navigate them // Supabase fires a PASSWORD_RECOVERY event.
// to a "set new password" screen here.
if (authState.event == AuthChangeEvent.passwordRecovery) { if (authState.event == AuthChangeEvent.passwordRecovery) {
_showResetPasswordDialog(); _showResetPasswordDialog();
} }

View file

@ -243,10 +243,11 @@ class _ScannerScreenState extends State<ScannerScreen> {
), ),
); );
controller.dispose();
if (result != null && context.mounted) { if (result != null && context.mounted) {
controller.dispose();
Navigator.of(context).pop(result); Navigator.of(context).pop(result);
} else {
controller.dispose();
} }
} }
} }