-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add AsyncStream support for Firebase Auth #15362
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
base: main
Are you sure you want to change the base?
Conversation
This commit introduces an `AsyncStream`-based API for observing authentication state changes, aligning the Firebase Auth SDK with modern Swift concurrency patterns. The new `authStateChanges` computed property on `Auth` returns an `AsyncStream<User?>` that emits the current user whenever the authentication state changes. This provides a more ergonomic alternative to the traditional closure-based `addStateDidChangeListener`. Key changes include: - The implementation of `authStateChanges` in a new `Auth+Async.swift` file. - Comprehensive unit tests in `AuthAsyncTests.swift` covering the stream's behavior for sign-in, sign-out, and cancellation scenarios to prevent resource leaks. - An entry in the `FirebaseAuth/CHANGELOG.md` for the new feature. - Detailed API documentation for `authStateChanges`, including a clear usage example, following Apple's documentation best practices.
This commit introduces an `AsyncStream`-based API for observing ID token changes, aligning the Firebase Auth SDK with modern Swift concurrency patterns. The new `idTokenChanges` computed property on `Auth` returns an `AsyncStream<User?>` that emits the current user whenever the ID token changes. This provides a more ergonomic alternative to the traditional closure-based `addIDTokenDidChangeListener`. Key changes include: - The implementation of `idTokenChanges` in `Auth+Async.swift`. - Comprehensive unit tests in `IdTokenChangesAsyncTests.swift` covering the stream's behavior. - Renamed `AuthAsyncTests.swift` to `AuthStateChangesAsyncTests.swift` to better reflect its content.
/gemini review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces AsyncStream
-based APIs for observing authentication state and ID token changes, which is a great step towards modernizing the Firebase Auth SDK for Swift concurrency. The implementation is clean, well-documented, and includes a good set of unit tests.
My review includes a few suggestions to enhance the new APIs and tests:
- Broadening the API availability to support older OS versions where Swift concurrency is available.
- Improving the robustness of the unit tests by replacing
Task.sleep
with expectations. - Refactoring duplicated setup code in the tests to improve maintainability.
@available(iOS 18.0, *) | ||
var authStateChanges: some AsyncSequence<User?, Never> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The @available(iOS 18.0, *)
attribute seems overly restrictive. Swift's AsyncStream
and opaque some AsyncSequence
return types are available on earlier OS versions (iOS 13+, macOS 10.15+, etc.). To maximize the utility of this new API for developers, consider lowering the availability to align with the introduction of Swift's concurrency features, for example:
@available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, watchOS 7.0, *)
This would also make it consistent with the availability of the Auth
class itself. This change should be applied to idTokenChanges
on line 90 as well, and the corresponding test functions in AuthStateChangesAsyncTests.swift
and IdTokenChangesAsyncTests.swift
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a valid comment. We only need to gate on iOS 18 if Never
was an Error
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the current callback-based listener forward errors to the client's listener block? If so, is the listener cancelled?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The protocol is defined as follows:
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public protocol AsyncSequence<Element, Failure> {
/// The type of asynchronous iterator that produces elements of this
/// asynchronous sequence.
associatedtype AsyncIterator : AsyncIteratorProtocol
/// The type of element produced by this asynchronous sequence.
associatedtype Element where Self.Element == Self.AsyncIterator.Element
/// The type of errors produced when iteration over the sequence fails.
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
associatedtype Failure = any Error where Self.Failure == Self.AsyncIterator.Failure
/// Creates the asynchronous iterator that produces elements of this
/// asynchronous sequence.
///
/// - Returns: An instance of the `AsyncIterator` type used to produce
/// elements of the asynchronous sequence.
func makeAsyncIterator() -> Self.AsyncIterator
}
When using @available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, watchOS 7.0, *)
, the compiler says that Failure
is only available on iOS 18+.

I am happy to use the more complete availability spec on the function, but unless I am missing something, the only other way around it is to return the concrete type AsyncStream<User?>
(like we did in a previous version of this branch).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the current callback-based listener forward errors to the client's listener block? If so, is the listener cancelled?
No - the callback based listener doesn't throw errors.
@available(iOS 18.0, *) | ||
var authStateChanges: some AsyncSequence<User?, Never> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a valid comment. We only need to gate on iOS 18 if Never
was an Error
here.
@available(iOS 18.0, *) | ||
var authStateChanges: some AsyncSequence<User?, Never> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the current callback-based listener forward errors to the client's listener block? If so, is the listener cancelled?
To better align the Firebase Auth SDK with modern Swift concurrency, this PR introduces
AsyncStream
-based APIs for observing authentication state and ID token changes.Key Features & Benefits
authStateChanges
andidTokenChanges
properties onAuth
return anAsyncStream<User?>
, allowing developers to usefor await
loops to monitor authentication events, which is more idiomatic in modern Swift.addStateDidChangeListener
andaddIDTokenDidChangeListener
).Implementation Details
FirebaseAuth/Sources/Auth+Async.swift
file.FirebaseAuth/Tests/Unit/AuthAsyncTests.swift
to cover sign-in, sign-out, token refresh, and stream cancellation scenarios.FirebaseAuth/CHANGELOG.md
has been updated to reflect these new features.Example Usage
Monitoring Authentication State
Monitoring ID Token Changes