Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[firebase_auth] iOS Firebase Auth Stream Not Emitting State Changes After Sign-In #16874

Open
1 task done
daredammy opened this issue Dec 13, 2024 · 4 comments
Open
1 task done
Labels
blocked: customer-response Waiting for customer response, e.g. more information was requested. Needs Attention This issue needs maintainer attention. platform: ios Issues / PRs which are specifically for iOS. plugin: auth type: bug Something isn't working

Comments

@daredammy
Copy link

Is there an existing issue for this?

  • I have searched the existing issues.

Which plugins are affected?

Core, Auth

Which platforms are affected?

iOS

Description

The authStateChanges() stream in Firebase Auth Flutter is not emitting updates after successful Google Sign-In and Sign-In with Apple on iOS. The same code works correctly on Android devices and was working on iOS until recently.

Environment

  • Flutter: 3.24.5 (Channel stable)
  • Xcode: 16.0
  • macOS: 15.1
  • firebase_auth: ^5.3.4
  • firebase_core: ^3.8.1
  • google_sign_in: ^6.2.1
  • sign_in_with_apple: ^6.1.3
  • Platform: iOS only (works correctly on Android)

Reproducing the issue

Steps to Reproduce

  1. Implement StreamBuilder with FirebaseAuth.instance.authStateChanges()
  2. Implement Google Sign-In or Sign-In with Apple
  3. Attempt to sign in
  4. Observe that sign-in succeeds (UserCredential is obtained)
  5. Observe that StreamBuilder does not receive auth state update

Code Example

StreamBuilder<User?> authCheck({Widget redirectLandingPage = const LandingPageView()}) {
  return StreamBuilder<User?>(
    stream: _auth.authStateChanges(),
    builder: (BuildContext context, AsyncSnapshot<User?> snapshot) {
      if (snapshot.connectionState == ConnectionState.waiting) {
        return const Center(child: CircularProgressIndicator());
      }
      if (!snapshot.hasData || snapshot.data == null) {
        return CustomAuthScreen(redirectLandingPage: redirectLandingPage);
      } else {
        // This part never executes after sign-in on iOS
        Analytics.setUserId(id: snapshot.data!.uid);
        return redirectLandingPage;
      }
    }
  );
}

### Firebase Core version

3.8.1

### Flutter Version

3.24.5

### Relevant Log Output

_No response_

### Flutter dependencies

<details>
<summary>Expand <code>Flutter dependencies</code> snippet</summary>
<br>

```yaml
Dart SDK 3.5.4
Flutter SDK 3.24.5

Key dependencies:
firebase_auth: 5.3.4
firebase_core: 3.8.1
google_sign_in: 6.2.2
sign_in_with_apple: 6.1.3

Full dependency tree:
firebase_auth 5.3.4 [firebase_auth_platform_interface firebase_auth_web firebase_core firebase_core_platform_interface flutter meta]
firebase_auth_platform_interface 7.4.10 [_flutterfire_internals collection firebase_core flutter meta plugin_platform_interface]
firebase_auth_web 5.13.5 [firebase_auth_platform_interface firebase_core firebase_core_web flutter flutter_web_plugins http_parser meta web]
firebase_core 3.8.1 [firebase_core_platform_interface firebase_core_web flutter meta]
firebase_core_platform_interface 5.3.1 [collection flutter flutter_test meta plugin_platform_interface]
firebase_core_web 2.18.2 [firebase_core_platform_interface flutter flutter_web_plugins meta web]
google_sign_in 6.2.2 [flutter google_sign_in_android google_sign_in_ios google_sign_in_platform_interface google_sign_in_web]
sign_in_with_apple 6.1.3 [flutter meta sign_in_with_apple_platform_interface sign_in_with_apple_web]

### Additional context and comments

**Additional context and comments**

1. Platform-specific behavior:
- Issue only occurs on iOS
- Same code works correctly on Android (auth state changes are properly emitted)
- Previously working correctly on iOS until recently

2. Testing & Verification:
- Issue occurs with both Google Sign-In and Sign-In with Apple
- Successfully obtain UserCredential after sign-in
- StreamBuilder prints show connection state changes but no user data update
- Console logs confirm successful sign-in with UID
- No error messages in the console

3. Current state:
- UserCredential is obtained successfully
- The `currentUser` is set in FirebaseAuth
- StreamBuilder never receives the update
- Affects both `authStateChanges()` and `userChanges()`

4. Temporary workaround:
Currently using direct navigation after successful authentication instead of relying on StreamBuilder:
```dart
final UserCredential userCredential = await _auth.signInWithCredential(credential);
if (mounted) {
  Navigator.pushReplacement(
    context,
    MaterialPageRoute(builder: (context) => redirectLandingPage),
  );
}
@daredammy daredammy added Needs Attention This issue needs maintainer attention. type: bug Something isn't working labels Dec 13, 2024
@SelaseKay SelaseKay added plugin: auth platform: ios Issues / PRs which are specifically for iOS. labels Dec 13, 2024
@SelaseKay
Copy link
Contributor

Hi @daredammy, thanks for the report. I'm unable to reproduce this issue. Could you provide a complete minimal code reproducing this issue?

@SelaseKay SelaseKay added blocked: customer-response Waiting for customer response, e.g. more information was requested. and removed Needs Attention This issue needs maintainer attention. labels Dec 16, 2024
@daredammy
Copy link
Author

daredammy commented Dec 16, 2024

this is most of my auth code-

mixin AuthCheckMixin {
  final FirebaseAuth _auth = FirebaseAuth.instance;

  StreamBuilder<User?> authCheck(
      {Widget redirectLandingPage = const LandingPageView()}) {
    return StreamBuilder<User?>(
        stream: _auth.authStateChanges(),
        builder: (BuildContext context, AsyncSnapshot<User?> snapshot) {
          if (snapshot.hasError) {
            ErrorReporting.reportError(snapshot.error!, StackTrace.current,
                reason: 'AuthCheckMixin');
          }
          if (snapshot.connectionState == ConnectionState.waiting) {
            // Show a loading indicator while waiting for auth state
            return const Center(child: CircularProgressIndicator());
          }
          if (!snapshot.hasData || snapshot.data == null) {
            // User is not signed in, show the sign-in screen
            return CustomAuthScreen(redirectLandingPage: redirectLandingPage);
          } else {
            // User is signed in
            Analytics.setUserId(id: snapshot.data!.uid);
            Analytics.logLogin();
            FCMMessagingService.getAndSaveFCMToken();
            UserService.updateUserMetadataMusicPreferences();

            return redirectLandingPage;
          }
        });
  }
}
class CustomAuthScreen extends StatefulWidget {
  final Widget redirectLandingPage;

  const CustomAuthScreen({super.key, required this.redirectLandingPage});

  @override
  CustomAuthScreenState createState() => CustomAuthScreenState();
}

class CustomAuthScreenState extends State<CustomAuthScreen> {
  // Controllers for email and password fields
  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();

  // Firebase Auth instance
  final FirebaseAuth _auth = FirebaseAuth.instance;

  // Error message display
  String? _errorMessage;

  bool _isLoading = false;
  bool _isSignUp = false;

  // Method to handle sign-in or sign-up
  Future<void> _signInOrSignUp() async {
    setState(() {
      _isLoading = true;
      _errorMessage = null;
    });
    try {
      if (_isSignUp) {
        // Sign Up
        await _auth.createUserWithEmailAndPassword(
          email: _emailController.text.trim(),
          password: _passwordController.text.trim(),
        );
      } else {
        // Sign In
        await _auth.signInWithEmailAndPassword(
          email: _emailController.text.trim(),
          password: _passwordController.text.trim(),
        );
      }
    } on FirebaseAuthException catch (e) {
      setState(() {
        _errorMessage = e.message;
      });
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  // Method to handle Google Sign-In
  Future<void> _signInWithGoogle() async {
    setState(() {
      _isLoading = true;
      _errorMessage = null;
    });
    try {
      final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
      if (googleUser == null) {
        // User canceled the sign-in
        if (mounted) {
          setState(() {
            _isLoading = false;
          });
        }
        return;
      }

      final GoogleSignInAuthentication googleAuth =
          await googleUser.authentication;

      final OAuthCredential credential = GoogleAuthProvider.credential(
          accessToken: googleAuth.accessToken, idToken: googleAuth.idToken);

      final UserCredential userCredential =
          await _auth.signInWithCredential(credential);

      if (!kIsWeb && Platform.isIOS) {
        _navigateToRedirectLandingPage(userCredential);
      }
    } on FirebaseAuthException catch (e) {
      ErrorReporting.reportError(e, StackTrace.current,
          reason: 'Google Sign-In failed');
      if (mounted) {
        setState(() {
          _errorMessage =
              e.message ?? GENERIC_ERROR_MSG; // Use a default message
        });
      }
    } on PlatformException catch (e) {
      ErrorReporting.reportError(e, StackTrace.current,
          reason: 'Google Sign-In failed');
      if (mounted) {
        setState(() {
          _errorMessage = e.message ?? GENERIC_ERROR_MSG;
        });
      }
    } finally {
      if (mounted) {
        setState(() {
          _isLoading = false;
        });
      }
    }
  }

  // Method to handle Apple Sign-In
  Future<void> _signInWithApple() async {
    setState(() {
      _isLoading = true;
      _errorMessage = null;
    });
    try {
      final appleCredential = await SignInWithApple.getAppleIDCredential(
        scopes: [
          AppleIDAuthorizationScopes.email,
          AppleIDAuthorizationScopes.fullName,
        ],
      );

      final oauthCredential = OAuthProvider("apple.com").credential(
        idToken: appleCredential.identityToken,
        accessToken: appleCredential.authorizationCode,
      );

      final UserCredential userCredential =
          await _auth.signInWithCredential(oauthCredential);
      // if ios
      if (!kIsWeb && Platform.isIOS) {
        _navigateToRedirectLandingPage(userCredential);
      }
    } on FirebaseAuthException catch (e) {
      ErrorReporting.reportError(e, StackTrace.current,
          reason: 'Apple Sign-In failed');
      if (mounted) {
        setState(() {
          _errorMessage = e.message ?? GENERIC_ERROR_MSG;
        });
      }
    } on SignInWithAppleAuthorizationException catch (e) {
      ErrorReporting.reportError(e, StackTrace.current,
          reason: 'Apple Sign-In failed');
      if (mounted) {
        setState(() {
          _errorMessage = GENERIC_ERROR_MSG;
        });
      }
    } finally {
      if (mounted) {
        setState(() {
          _isLoading = false;
        });
      }
    }
  }

As i highlighted the issue is here-

  StreamBuilder<User?> authCheck(
      {Widget redirectLandingPage = const LandingPageView()}) {
    return StreamBuilder<User?>(
        stream: _auth.authStateChanges(),
.....
  else {
        // This part never executes after sign-in on iOS
        Analytics.setUserId(id: snapshot.data!.uid);
        return redirectLandingPage;
      }

@daredammy
Copy link
Author

Gettin this in my crashlytics as well-

MissingPluginException(No implementation found for method listen on channel plugins.flutter.io/firebase_auth/auth-state/__FIRAPP_DEFAULT)

@google-oss-bot google-oss-bot added Needs Attention This issue needs maintainer attention. and removed blocked: customer-response Waiting for customer response, e.g. more information was requested. labels Dec 22, 2024
@SelaseKay
Copy link
Contributor

I'm still unable to reproduce this issue. Could you create a minimal repository reproducing this issue and share the link here?

@SelaseKay SelaseKay added the blocked: customer-response Waiting for customer response, e.g. more information was requested. label Dec 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
blocked: customer-response Waiting for customer response, e.g. more information was requested. Needs Attention This issue needs maintainer attention. platform: ios Issues / PRs which are specifically for iOS. plugin: auth type: bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants