👮
SwiftUI + Firebase Authで汎用的なモジュールを構築する
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")
}
}
}
Discussion