diff --git a/packages/firebase_ui_auth/lib/src/email_verification.dart b/packages/firebase_ui_auth/lib/src/email_verification.dart index f20497ba..d51e4c24 100644 --- a/packages/firebase_ui_auth/lib/src/email_verification.dart +++ b/packages/firebase_ui_auth/lib/src/email_verification.dart @@ -57,6 +57,13 @@ class EmailVerificationController extends ValueNotifier WidgetsBinding.instance.addObserver(this); } + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + _disposed = true; + super.dispose(); + } + @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { @@ -73,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; } @@ -80,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; @@ -103,7 +118,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; @@ -113,12 +136,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; 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(() {