SwiftUI + Firebase Authで汎用的なモジュールを構築する

2 min read読了の目安(約2300字

Model Layer

ここではAuthの状態をFirebaseAuthから受け取りアプリケーションで利用できる形に変換します。
今回は認証情報の監視のみの役割を与えてますが、場合によっては外部サービスログインのユーティリティツールを纏める機能を与えても問題ないと思います。

AuthObserver.swift
import FirebaseAuth
import Combine

class AuthObserver: ObservableObject {
    private var listener: AuthStateDidChangeListenerHandle!
    @Published var isSubscribed: Bool = false
    @Published var uid: String? = nil
    init() {
        self.listener = Auth.auth().addStateDidChangeListener { [weak self] (_, user) in
	    self?.isSubscribed = true
	    self?.uid = user.uid
	}
    }
    deinit {
        Auth.auth().removeStateDidChangeListener(listener!)
	self.isSubscribed = true
    }
}

EnvironmentではuidとsubscribedAuthを定義します。なぜuidのみではないかというと、subscribeした直後はまだauth情報が流れて来ずログイン済みであるにも関わらずuidが流れて来ないという状況が発生します。
そこでlistenerで初めてコールバックが呼ばれたタイミングで監視しているというflagを立てることで上手く、

  • uidがなく監視されていない
  • uidがなく監視されている
  • uidがあり監視されている

という状態を作り出すことができます。

Environment.swift
import SwiftUI

private struct SubscribedAuthEnvironmentKey: EnvironmentKey {
    static let defaultValue: Bool = false
}

private struct UIDEnvironmentKey: EnvironmentKey {
    static let defaultValue: String? = nil
}

extension EnvironmentValues {
    var uid: String? {
        get { self[UIDEnvironmentKey.self] }
        set { self[UIDEnvironmentKey.self] = newValue }
    }
    var subscribedAuth: Bool {
        get { self[SubscribedAuthEnvironmentKey.self] }
        set { self[SubscribedAuthEnvironmentKey.self] = newValue }
    }
}

Application Layer

Auth情報はApplication全体で管理したいのでAppで監視をします。また、View層でそれぞれ監視したい場合はそれぞれのView層でStateObjectもしくはObservedObjectでインスタンスを生成することによって監視することができます。

App.swift

import SwiftUI

@main
struct ExampleApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    @StateObject var auth = AuthObserver()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.uid, auth.uid)
                .environment(\.subscribedAuth, auth.subscribed)
        }
    }
}

上記の設定をすることでこのように認証情報を利用することができるようになりました

ContentView.swift
struct ContentView: View {
    @Environment(\.uid) var uid
    @Environment(\.subscribedAuth) var isSubscribed
    var body: some View {
        if (isSubscribed) {
	    Text(uid ?? "nil")
	} else {
	    Text("unsubscribed auth")
	}
    }
}