Skip to content

refactor: the way providers are initialized. AnyView is used to render provider buttons. #1249

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

Merged
merged 8 commits into from
May 9, 2025
Merged
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
Expand Up @@ -2,8 +2,8 @@
import SwiftUI

public protocol ExternalAuthProvider {
associatedtype ButtonType: View
@MainActor var authButton: ButtonType { get }
var id: String { get }
@MainActor func authButton() -> AnyView
Copy link
Contributor

Choose a reason for hiding this comment

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

Did you try returning some View? You might need to annotate the function with @ViewBuilder

Copy link
Member Author

Choose a reason for hiding this comment

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

cannot have some View as return type on protocol apparently. Get Xcode compiler error: 'some' type cannot be the return type of a protocol requirement; did you mean to add an associated type?

Copy link
Contributor

Choose a reason for hiding this comment

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

I put together something that might help: https://gist.github.com/peterfriese/d9745f366fb4f857daf077e93b1eb01f

import SwiftUI

protocol AuthProvider: Identifiable {
  var id: String { get }
  associatedtype ButtonType: View
  var authButtonView: Self.ButtonType { get }
}

protocol FooProviderProtocol: AuthProvider {
  func doFoo()
}

struct FooProviderImpl1: FooProviderProtocol {
  var id: String = "FooProviderImpl1"

  func doFoo() {
    print("1")
  }

  var authButtonView: some View {
    Text("1")
  }
}

struct FooProviderImpl2: FooProviderProtocol {
  var id: String = "FooProviderImpl2"

  func doFoo() {
    print("2")
  }

  var authButtonView: some View {
    Text("2")
      .background(Color.red)
  }
}

protocol BarProviderProtocol: AuthProvider {
  func doBar()
}

struct BarProviderImpl1: BarProviderProtocol {
  var id: String = "BarProviderImpl1"
  
  func doBar() {
    print("1")
  }
  
  var authButtonView: some View {
    Text("1")
  }
}

struct BarProviderImpl2: BarProviderProtocol {
  var id: String = "BarProviderImpl2"
  
  func doBar() {
    print("2")
  }
  
  var authButtonView: some View {
    Text("2")
      .background(Color.red)
  }
}


struct ContentView<Provider: AuthProvider>: View {
  var provider: Provider
  var body: some View {
    VStack {
      provider.authButtonView
    }
  }
}

#Preview {
  ContentView(provider: BarProviderImpl2())
}

Copy link
Member Author

Choose a reason for hiding this comment

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

I've responded in the other PR: #1250

}

public protocol GoogleProviderAuthUIProtocol: ExternalAuthProvider {
Expand Down Expand Up @@ -65,15 +65,9 @@ private final class AuthListenerManager {
@MainActor
@Observable
public final class AuthService {
public init(configuration: AuthConfiguration = AuthConfiguration(), auth: Auth = Auth.auth(),
googleProvider: (any GoogleProviderAuthUIProtocol)? = nil,
facebookProvider: (any FacebookProviderAuthUIProtocol)? = nil,
phoneAuthProvider: (any PhoneAuthProviderAuthUIProtocol)? = 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,6 +90,21 @@ public final class AuthService {
private var listenerManager: AuthListenerManager?
private var signedInCredential: AuthCredential?

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

public func renderButtons(spacing: CGFloat = 16) -> AnyView {
AnyView(
VStack(spacing: spacing) {
ForEach(providers, id: \.id) { provider in
provider.authButton()
}
}
)
}

private var safeGoogleProvider: any GoogleProviderAuthUIProtocol {
get throws {
guard let provider = googleProvider else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,9 @@
}
}
}
},
"Update password" : {

},
"UpdateEmailAlertMessage" : {
"comment" : "Alert action message shown before updating email action. Use short/abbreviated translation for 'email' which is less than 15 chars.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ extension AuthPickerView: View {
Text(authService.authenticationFlow == .login ? "Login" : "Sign up")
VStack { Divider() }
EmailAuthView()
providerButtons()
authService.renderButtons()
VStack { Divider() }
HStack {
Text(authService
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// AuthService+Facebook.swift
// FirebaseUI
//
// Created by Russell Wheatley on 01/05/2025.
//

import FirebaseAuthSwiftUI

public extension AuthService {
@discardableResult
func withFacebookSignIn(scopes scopes: [String]? = nil) -> AuthService {
facebookProvider = FacebookProviderAuthUI(scopes: scopes)
register(provider: facebookProvider!)
return self
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public enum FacebookProviderError: Error {
}

public class FacebookProviderAuthUI: FacebookProviderAuthUIProtocol {
public let id: String = "facebook"
let scopes: [String]
let shortName = "Facebook"
let providerId = "facebook.com"
Expand All @@ -35,8 +36,8 @@ public class FacebookProviderAuthUI: FacebookProviderAuthUIProtocol {
shaNonce = CommonUtils.sha256Hash(of: rawNonce)
}

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

@MainActor public func signInWithFacebook(isLimitedLogin: Bool) async throws -> AuthCredential {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import FirebaseAuthSwiftUI

public extension AuthService {
@discardableResult
func withGoogleSignIn() -> AuthService {
func withGoogleSignIn(scopes scopes: [String]? = nil) -> AuthService {
let clientID = auth.app?.options.clientID ?? ""
googleProvider = GoogleProviderAuthUI(clientID: clientID)
googleProvider = GoogleProviderAuthUI(scopes: scopes, clientID: clientID)
register(provider: googleProvider!)
return self
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public enum GoogleProviderError: Error {
}

public class GoogleProviderAuthUI: @preconcurrency GoogleProviderAuthUIProtocol {
public let id: String = "google"
let scopes: [String]
let shortName = "Google"
let providerId = "google.com"
Expand All @@ -25,12 +26,12 @@ public class GoogleProviderAuthUI: @preconcurrency GoogleProviderAuthUIProtocol
self.clientID = clientID
}

@MainActor public var authButton: GoogleSignInButton {
return GoogleSignInButton {
@MainActor public func authButton() -> AnyView {
AnyView(GoogleSignInButton {
Task {
try await self.signInWithGoogle(clientID: self.clientID)
}
}
})
}

@MainActor public func signInWithGoogle(clientID: String) async throws -> AuthCredential {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// AuthService+Phone.swift
// FirebaseUI
//
// Created by Russell Wheatley on 09/05/2025.
//

import FirebaseAuthSwiftUI

public extension AuthService {
@discardableResult
func withPhoneSignIn() -> AuthService {
phoneAuthProvider = PhoneAuthProviderAuthUI()
register(provider: phoneAuthProvider!)
return self
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ import SwiftUI
public typealias VerificationID = String

public class PhoneAuthProviderAuthUI: @preconcurrency PhoneAuthProviderAuthUIProtocol {
public var authButton: Button<Text> {
// TODO: implement me
return Button("Phone", action: {})
}
public let id: String = "phone"

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

@MainActor public func verifyPhoneNumber(phoneNumber: String) async throws -> VerificationID {
return try await withCheckedThrowingContinuation { continuation in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,16 @@ struct ContentView: View {
shouldAutoUpgradeAnonymousUsers: true,
emailLinkSignInActionCodeSettings: actionCodeSettings
)
let facebookProvider = FacebookProviderAuthUI()
let phoneAuthProvider = PhoneAuthProviderAuthUI()
authService = AuthService(
configuration: configuration,
facebookProvider: facebookProvider,
phoneAuthProvider: phoneAuthProvider
configuration: configuration
)
.withGoogleSignIn()
.withFacebookSignIn()
.withPhoneSignIn()
}

var body: some View {
AuthPickerView {
SignInWithGoogleButton()
SignInWithFacebookButton()
PhoneAuthButtonView()
}.environment(authService)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@
import FacebookCore
import FirebaseAuth
import FirebaseCore
import FirebaseGoogleSwiftUI
import FirebasePhoneAuthSwiftUI
import GoogleSignIn
import SwiftData
import SwiftUI

class AppDelegate: NSObject, UIApplicationDelegate {
Expand Down Expand Up @@ -61,8 +58,6 @@ class AppDelegate: NSObject, UIApplicationDelegate {
struct FirebaseSwiftUIExampleApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

init() {}

var body: some Scene {
WindowGroup {
NavigationView {
Expand Down