Skip to content

feat(auth): add pickerContent and pickerDestination slots to AuthPickerView - #1403

Open
demolaf wants to merge 2 commits into
auth/themingfrom
auth/picker-slots
Open

demolaf wants to merge 2 commits into
auth/themingfrom
auth/picker-slots

Conversation

@demolaf

@demolaf demolaf commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

Adds .pickerContent and .pickerDestination to AuthPickerView, so apps can replace the auth sheet's first screen or any pushed screen while the library keeps driving navigation, MFA and account conflicts. Stacked on #1402.

Changes

  • AuthPickerView keeps its signature. The new modifiers return AuthPickerContent, which now owns the sheet, NavigationStack, error alert, and MFA and account-conflict handling.
  • Add public defaults DefaultAuthPickerContent, DefaultAuthMethodPicker and DefaultAuthPickerDestination, holding the markup that was private inside AuthPickerView.
  • .pickerContent replaces only the signed-out screen. SignedInView and the "Authenticating..." overlay stay in AuthPickerContent, so custom content can't hide them.
  • DefaultAuthPickerContent is generic over its provider list from the start, so the custom-layout initializer in the next PR is additive.

Naming, pending team review

AuthPickerContent (the wrapper) sits beside DefaultAuthPickerContent (the default for its PickerContent slot). Kept for now; the alternative is renaming the wrapper, e.g. ComposedAuthPickerView, before release.

API Usage

AuthPickerView { authenticatedApp }
  .pickerContent {
    DefaultAuthPickerContent()
      .background(theme.colors.background)
  }
  .pickerDestination { screen in
    switch screen {
    case .enterPhoneNumber:
      MyPhoneEntry()
    default:
      DefaultAuthPickerDestination(screen: screen)
        .background(theme.colors.background)
    }
  }

Preview


Maintainer note: Fixes internal CPRN-516

@demolaf
demolaf added this pull request to stack #1404 September 29, 2026 13:00

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

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 refactors the authentication UI flow in FirebaseAuthSwiftUI by extracting the sheet presentation, navigation stack, and error/MFA handling into a new AuthPickerContent view. This allows AuthPickerView to support customizable first screens and destination views via pickerContent and pickerDestination modifiers, while falling back to the newly introduced DefaultAuthPickerContent and DefaultAuthPickerDestination. Key feedback points out that DefaultAuthPickerContent's generic parameter is overly restricted by its initializer, a UIKit dependency is introduced via UIColor.label, and a redundant Task dispatch is used in reportError on a MainActor-annotated struct.

Comment on lines +24 to +27
public struct DefaultAuthPickerContent<AuthMethodPicker: View>: View {
public init() where AuthMethodPicker == DefaultAuthMethodPicker {
authMethodPicker = { DefaultAuthMethodPicker() }
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The DefaultAuthPickerContent struct is generic over AuthMethodPicker, but the only initializer provided restricts AuthMethodPicker to DefaultAuthMethodPicker. This prevents callers from ever providing a custom AuthMethodPicker view, which defeats the purpose of the generic parameter.

To allow callers to customize the provider buttons list while keeping the default logo, email sign-in, and terms, please add a public initializer that accepts a custom @ViewBuilder closure for AuthMethodPicker.

Suggested change
public struct DefaultAuthPickerContent<AuthMethodPicker: View>: View {
public init() where AuthMethodPicker == DefaultAuthMethodPicker {
authMethodPicker = { DefaultAuthMethodPicker() }
}
public struct DefaultAuthPickerContent<AuthMethodPicker: View>: View {
public init(@ViewBuilder authMethodPicker: @escaping () -> AuthMethodPicker) {
self.authMethodPicker = authMethodPicker
}
public init() where AuthMethodPicker == DefaultAuthMethodPicker {
self.authMethodPicker = { DefaultAuthMethodPicker() }
}

Comment on lines +120 to +122
Image(systemName: "xmark")
.foregroundStyle(Color(UIColor.label))
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using Color(UIColor.label) introduces a dependency on UIKit (via UIColor), which can cause compilation issues on non-iOS platforms (like macOS) and relies on transitive imports since UIKit is not explicitly imported in this file.

Since this is a SwiftUI view, you can use the platform-agnostic Color.primary instead, which automatically adapts to light and dark modes.

Suggested change
Image(systemName: "xmark")
.foregroundStyle(Color(UIColor.label))
}
Image(systemName: "xmark")
.foregroundStyle(Color.primary)
}

Comment on lines +104 to +111
private func reportError(_ error: Error) {
Task { @MainActor in
self.error = AlertError(
message: authService.string.localizedErrorMessage(for: error),
underlyingError: error
)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since AuthPickerContent is annotated with @MainActor, the reportError method is already guaranteed to run on the main actor. Spawning a new asynchronous Task to update self.error is redundant and introduces an unnecessary dispatch hop. You can update the state directly.

  private func reportError(_ error: Error) {
    self.error = AlertError(
      message: authService.string.localizedErrorMessage(for: error),
      underlyingError: error
    )
  }

@demolaf
demolaf marked this pull request as ready for review September 30, 2026 08:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant