【SwiftUI】Firebase Authenticationでログイン機能を作ってみる

## 前提
こちらの記事の続きでFirebaseAuthを用いてログイン機能を実装します。

## 参考
導入手順などは公式サイトの手順通りに行いました。

https://firebase.google.com/docs/auth/ios/start?hl=ja

## GitHub
https://github.com/yuskey38/SwiftUI-MVVM-Login

## 実装
アプリ起動時にFirebase.configure()を呼びます。

```swift
import SwiftUI
import Firebase

@main
struct SwiftUI_MVVM_LoginApp: App {
    init() {
        FirebaseApp.configure()
    }

    var body: some Scene {
        WindowGroup {
            NavigationView {
                ContentView(viewModel: ContentViewModel(firebaseAuthService: FirebaseAuthService.shared))
            }
        }
    }
}
```

FirebaseAuth用のServiceを作成します。

シングルトンで作ってみました。
addStateDidChangeListenerのクロージャーで受け取ったuserを保持しておけば
ログイン中のユーザー情報をこの常にこのServiceから取得することもできます。

```swift
import FirebaseAuth

protocol IFirebaseAuthService: AnyObject {
    func addStateDidChangeListener(completion: @escaping (Bool) -> Void)
    func removeStateDidChangeListener()
    func signIn(email: String?, password: String?, completion: @escaping (Error?) -> Void)
    func signUp(email: String?, password: String?, completion: @escaping (Error?) -> Void)
    func signOut()
}

final class FirebaseAuthService: IFirebaseAuthService {
    public static let shared = FirebaseAuthService()

    private var handler: AuthStateDidChangeListenerHandle?

    private init() {}

    deinit {
        removeStateDidChangeListener()
    }

    func addStateDidChangeListener(completion: @escaping (Bool) -> Void) {
        handler = Auth.auth().addStateDidChangeListener { (auth, user) in
            if let _ = user {
                completion(true)
            } else {
                completion(false)
            }
        }
    }

    func removeStateDidChangeListener() {
        if let handler = handler {
            Auth.auth().removeStateDidChangeListener(handler)
        }
    }

    func signUp(email: String?, password: String?, completion: @escaping (Error?) -> Void) {
        guard let email = email, let password = password else {
            completion(nil)
            return
        }

        Auth.auth().createUser(withEmail: email, password: password) { authResult, error in
            completion(error)
        }
    }

    func signIn(email: String?, password: String?, completion: @escaping (Error?) -> Void) {
        guard let email = email, let password = password else {
            completion(nil)
            return
        }

        Auth.auth().signIn(withEmail: email, password: password) { authResult, error in
            completion(error)
        }
    }

    func signOut() {
        do {
            try Auth.auth().signOut()
        }
        catch(let error) {
            debugPrint(error.localizedDescription)
        }
    }
}
```

ContentViewのViewModelを作成して、ログイン中かどうかのフラグを持たせています。

```swift
import FirebaseAuth
import Combine

final class ContentViewModel: ObservableObject {
    @Published var isLogin: Bool = false

    private let firebaseAuthService: IFirebaseAuthService

    // Input: Viewで発生するイベントをViewModelで検知するためのもの
    let didTapLogoutButton = PassthroughSubject<Void, Never>()

    // cancellable
    private var cancellables = Set<AnyCancellable>()

    init(firebaseAuthService: IFirebaseAuthService) {
        self.firebaseAuthService = firebaseAuthService
        firebaseAuthService.addStateDidChangeListener(completion: { [weak self] in
            self?.isLogin = $0
        })

        didTapLogoutButton
            .sink(receiveValue: {
                firebaseAuthService.signOut()
            })
            .store(in: &cancellables)
    }
}
```

あとはログイン画面や新規登録画面でFirebaseAuthServiceのメソッドを呼ぶだけです

ログイン処理

```swift
didTapLoginButton
    .sink(receiveValue: { [weak self] in
        firebaseAuthService.signIn(email: self?.email, password: self?.password, completion: {
            if let error = $0 {
                self?.isShowError = (true, error.localizedDescription)
            }
        })
    })
    .store(in: &cancellables)
```

新規登録処理

```swift
didTapSignUpButton
    .sink(receiveValue: { [weak self] in
        firebaseAuthService.signUp(email: self?.email, password: self?.password, completion: { [weak self] in
            if let error = $0 {
                self?.isShowError = (true, error.localizedDescription)
            }
        })
    })
    .store(in: &cancellables)
```