From b93ba3b419b31e7e45ec367a1bbb0f101695b962 Mon Sep 17 00:00:00 2001 From: Karel Klic Date: Thu, 16 Jan 2025 13:47:48 +0100 Subject: [PATCH 1/3] fix(ui_auth): Dispose controller after email verification Before this change, EmailVerificationController created during email verification workflow remained active on the background. When user signed out within the same session, FurebaseAuth.currentUser become null and the controller accessed it assuming it's never null. After the change, controller is disposed after leaving the verification screen. --- packages/firebase_ui_auth/lib/src/email_verification.dart | 6 ++++++ .../lib/src/screens/email_verification_screen.dart | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/packages/firebase_ui_auth/lib/src/email_verification.dart b/packages/firebase_ui_auth/lib/src/email_verification.dart index f20497ba..9e32faaa 100644 --- a/packages/firebase_ui_auth/lib/src/email_verification.dart +++ b/packages/firebase_ui_auth/lib/src/email_verification.dart @@ -57,6 +57,12 @@ class EmailVerificationController extends ValueNotifier WidgetsBinding.instance.addObserver(this); } + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { diff --git a/packages/firebase_ui_auth/lib/src/screens/email_verification_screen.dart b/packages/firebase_ui_auth/lib/src/screens/email_verification_screen.dart index 02934204..5bc6c02e 100644 --- a/packages/firebase_ui_auth/lib/src/screens/email_verification_screen.dart +++ b/packages/firebase_ui_auth/lib/src/screens/email_verification_screen.dart @@ -145,6 +145,12 @@ class __EmailVerificationScreenContentState super.initState(); } + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + void _sendEmailVerification(_) { controller ..addListener(() { From 0ef9a83e65e0022f8565ba7d473686dcd316d7b4 Mon Sep 17 00:00:00 2001 From: Karel Klic Date: Fri, 17 Jan 2025 22:10:32 +0100 Subject: [PATCH 2/3] fix(ui_auth): Terminate sendVerificationEmail early when controller is disposed. When EmailVerificationController is no longer needed, it's disposed. However, sendVerificationEmail might still be running after disposal because it's async. It updates ValueNotifier's value, which throws an exception when called after dispose. This change prevents that by terminating sendVerificationEmail early when its result is no longer needed. --- .../lib/src/email_verification.dart | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/firebase_ui_auth/lib/src/email_verification.dart b/packages/firebase_ui_auth/lib/src/email_verification.dart index 9e32faaa..a581090a 100644 --- a/packages/firebase_ui_auth/lib/src/email_verification.dart +++ b/packages/firebase_ui_auth/lib/src/email_verification.dart @@ -60,6 +60,7 @@ class EmailVerificationController extends ValueNotifier @override void dispose() { WidgetsBinding.instance.removeObserver(this); + _disposed = true; super.dispose(); } @@ -79,6 +80,11 @@ class EmailVerificationController extends ValueNotifier /// Contains an [Exception] if [state] is [EmailVerificationState.failed]. Exception? error; + /// Controller might be disposed while [sendVerificationEmail] is waiting for + /// a future to complete. This flag is used to inform the method that it + /// should terminate early. + bool _disposed = false; + bool _isMobile(TargetPlatform platform) { return platform == TargetPlatform.android || platform == TargetPlatform.iOS; } @@ -109,7 +115,15 @@ class EmailVerificationController extends ValueNotifier value = EmailVerificationState.sending; try { await user.sendEmailVerification(actionCodeSettings); + // Controller might be disposed while waiting for the future to complete. + // In this case, avoid updating its value, as it would cause an exception. + if (_disposed) { + return; + } } on Exception catch (e) { + if (_disposed) { + return; + } error = e; value = EmailVerificationState.failed; return; @@ -119,12 +133,27 @@ class EmailVerificationController extends ValueNotifier value = EmailVerificationState.pending; // ignore: deprecated_member_use final linkData = await FirebaseDynamicLinks.instance.onLink.first; + if (_disposed) { + return; + } try { final code = linkData.link.queryParameters['oobCode']!; await auth.checkActionCode(code); + if (_disposed) { + return; + } + await auth.applyActionCode(code); + if (_disposed) { + return; + } + await user.reload(); + if (_disposed) { + return; + } + value = EmailVerificationState.verified; } on Exception catch (err) { error = err; From 253770bf28c75fbd6614d084be13217d94cc6857 Mon Sep 17 00:00:00 2001 From: Karel Klic Date: Fri, 17 Jan 2025 23:58:03 +0100 Subject: [PATCH 3/3] fix(ui_auth): Terminate reload early when controller is disposed --- packages/firebase_ui_auth/lib/src/email_verification.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/firebase_ui_auth/lib/src/email_verification.dart b/packages/firebase_ui_auth/lib/src/email_verification.dart index a581090a..d51e4c24 100644 --- a/packages/firebase_ui_auth/lib/src/email_verification.dart +++ b/packages/firebase_ui_auth/lib/src/email_verification.dart @@ -92,6 +92,9 @@ class EmailVerificationController extends ValueNotifier /// Reloads firebase user and updates the [state]. Future reload() async { await user.reload(); + if (_disposed) { + return; + } if (user.email == null) { value = EmailVerificationState.unresolved;