Skip to content

example of ViewBuilder issue #1250

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

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
@preconcurrency import FirebaseAuth
import SwiftUI

public protocol ExternalAuthProvider {
var id: String { get }
public protocol ExternalAuthProvider: Identifiable {
var id: String { get }
associatedtype ButtonType: View
@MainActor func authButton() -> AnyView
@MainActor var authButtonView: Self.ButtonType { get }
}

public protocol GoogleProviderProtocol: ExternalAuthProvider {
Expand Down Expand Up @@ -65,15 +67,9 @@ private final class AuthListenerManager {
@MainActor
@Observable
public final class AuthService {
public init(configuration: AuthConfiguration = AuthConfiguration(), auth: Auth = Auth.auth(),
googleProvider: (any GoogleProviderProtocol)? = nil,
facebookProvider: (any FacebookProviderProtocol)? = nil,
phoneAuthProvider: (any PhoneAuthProviderProtocol)? = nil) {
public init(configuration: AuthConfiguration = AuthConfiguration(), auth: Auth = Auth.auth()) {
self.auth = auth
self.configuration = configuration
self.googleProvider = googleProvider
self.facebookProvider = facebookProvider
self.phoneAuthProvider = phoneAuthProvider
string = StringUtils(bundle: configuration.customStringsBundle ?? Bundle.module)
listenerManager = AuthListenerManager(auth: auth, authEnvironment: self)
}
Expand All @@ -96,19 +92,13 @@ public final class AuthService {
private var listenerManager: AuthListenerManager?
private var signedInCredential: AuthCredential?

private var providers: [ExternalAuthProvider] = []
public func register(provider: ExternalAuthProvider) {
private var providers: [any ExternalAuthProvider] = []
public func register(provider: any ExternalAuthProvider) {
providers.append(provider)
}

public func renderButtons(spacing: CGFloat = 16) -> AnyView {
AnyView(
VStack(spacing: spacing) {
ForEach(providers, id: \.id) { provider in
provider.authButton()
}
}
)
var availableProviders: [any ExternalAuthProvider] {
return providers
}

private var safeGoogleProvider: any GoogleProviderProtocol {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ extension AuthPickerView: View {
Text(authService.authenticationFlow == .login ? "Login" : "Sign up")
VStack { Divider() }
EmailAuthView()
authService.renderButtons()
// Xcode compiler exception: Type 'any ExternalAuthProvider' cannot conform to 'ExternalAuthProvider'
Copy link
Member Author

Choose a reason for hiding this comment

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

@peterfriese - Following your gist example, we get this Xcode compiler error now.

I tried to remove any ExternalAuthProvider from the types in auth service for each provider but it isn't allowed because ExternalAuthProvider uses an associated type (ButtonType), Swift requires you to explicitly acknowledge it as an existential by using any ExternalAuthProvider.

RenderButtonsView(providers: authService.availableProviders)
VStack { Divider() }
HStack {
Text(authService
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// RenderButtonsView.swift
// FirebaseUI
//
// Created by Russell Wheatley on 08/05/2025.
//
import SwiftUI

struct RenderButtonsView<Provider: ExternalAuthProvider>: View {
var providers: [Provider] = []

public func renderButtonViews(spacing: CGFloat = 16) -> some View {
VStack(spacing: spacing) {
ForEach(providers, id: \.id) { provider in
provider.authButtonView
}
}
}

var body: some View {
VStack {
renderButtonViews()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public extension AuthService {
@discardableResult
func withFacebookSignIn(scopes scopes: [String]? = nil) -> AuthService {
facebookProvider = FacebookProviderSwift(scopes: scopes)
register(provider: facebookProvider!)
return self
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,15 @@ public class FacebookProviderSwift: FacebookProviderProtocol {
rawNonce = CommonUtils.randomNonce()
shaNonce = CommonUtils.sha256Hash(of: rawNonce)
}

@MainActor public func authButton() -> AnyView {
AnyView(SignInWithFacebookButton())
}

@MainActor public var authButtonView: some View {
SignInWithFacebookButton()
}

@MainActor public func signInWithFacebook(isLimitedLogin: Bool) async throws -> AuthCredential {
let trackingStatus = ATTrackingManager.trackingAuthorizationStatus
let tracking: LoginTracking = trackingStatus != .authorized ? .limited :
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ public class GoogleProviderSwift: @preconcurrency GoogleProviderProtocol {
})
}

@MainActor public var authButtonView: some View {
GoogleSignInButton {
Task {
try await self.signInWithGoogle(clientID: self.clientID)
}
}
}

@MainActor public func signInWithGoogle(clientID: String) async throws -> AuthCredential {
guard let presentingViewController = await (UIApplication.shared.connectedScenes
.first as? UIWindowScene)?.windows.first?.rootViewController else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ public typealias VerificationID = String
public class PhoneAuthProviderSwift: @preconcurrency PhoneAuthProviderProtocol {
public let id: String = "phone"
public init() {}

@MainActor public func authButton() -> AnyView {
AnyView(Text("phone button TODO"))
}

@MainActor public var authButtonView: some View {
Text("phone button TODO")
}

@MainActor public func verifyPhoneNumber(phoneNumber: String) async throws -> VerificationID {
return try await withCheckedThrowingContinuation { continuation in
PhoneAuthProvider.provider()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,9 @@ struct ContentView: View {
shouldAutoUpgradeAnonymousUsers: true,
emailLinkSignInActionCodeSettings: actionCodeSettings
)
let facebookProvider = FacebookProviderSwift()
let phoneAuthProvider = PhoneAuthProviderSwift()

authService = AuthService(
configuration: configuration,
facebookProvider: facebookProvider,
phoneAuthProvider: phoneAuthProvider
configuration: configuration
)
// Transition to this api
.withGoogleSignIn()
Expand Down