在不相关的ViewModel中从我的身份验证类订阅用户变量-Swift/Combine [英] Subscribing to a user variable from my authentication class in an unrelated ViewModel - Swift/Combine

查看:40
本文介绍了在不相关的ViewModel中从我的身份验证类订阅用户变量-Swift/Combine的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试根据我的AuthenticationState类(使用Firebase Auth)中登录的用户实例化用户配置文件.该用户个人资料是我的UserProfileViewModel的一部分,该用户个人资料应该会提供一个视图来编辑用户的个人资料.

I'm trying to instantiate a user profile based on the logged in user from my AuthenticationState class (using Firebase Auth). This user profile is part of my UserProfileViewModel, which should power a view for editing the user's profile.

但是,当实例化UserProfileViewModel时,loginInUser似乎仍然显示为nil,并且我不确定是否有办法使用这样的另一个类的Combine订阅来确保我已订阅了身份验证状态的特定实例的loggingInUser发布的变量.

But it appears that the loggedInUser is still seen as nil when the UserProfileViewModel is instantiated, and I'm not sure if there's a way I can use a Combine subscription from another class like this to make sure I'm subscribed to the loggedInUser published variable of that specific instance of Authentication state.

我的主应用程序文件将身份验证状态作为一个环境对象调用-用户登录后,loginInUser设置为该Firebase Auth用户:

Authentication state is called by my main app file, as an environment object - once the user logs in, the loggedInUser is set to that Firebase Auth user:

class AuthenticationState: NSObject, ObservableObject {
    
    // The firebase logged in user, and the userProfile associated with the users collection
    @Published var loggedInUser: User?
    
    
    @Published var isAuthenticating = false
    @Published var error: NSError?
    
    static let shared = AuthenticationState()
    
    private let auth = Auth.auth()
    fileprivate var currentNonce: String?

我在主应用程序文件中初始化AuthenticationState:

I initialize AuthenticationState in my main app file:

@main
struct GoalTogetherApp: App {

    let authState: AuthenticationState
    
    init() {
        FirebaseApp.configure()
        self.authState = AuthenticationState.shared
        
        setupFirebase()
    }
 
 
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(authState)
        }
    }
}

我还有另一个要捕获的LoggedInUser的类,然后使用该用户的uid从Cloud Firestore创建或找到userProfile:

And I have this other class that I want to grab the loggedInUser, and then use that user's uid to create or find a userProfile from Cloud Firestore:

class UserProfileViewModel: ObservableObject {
    
    @Published var loggedInUser: User?
    @Published var userProfile: UserProfile?
    
    private let auth = Auth.auth()
    private let db = Firestore.firestore()
    
    init() {
        self.loggedInUser = AuthenticationState.shared.loggedInUser
        
        if self.loggedInUser != nil {
            self.userProfile = self.loadUser()
        }
    }

个人资料"页面应该可以抓取并从UserProfile中提取电子邮件,但它始终显示为空白:

And the Profile page is supposed to grab that and pull the email from the UserProfile, but it keeps coming up as blank:

struct ProfilePage: View {
    @ObservedObject var userProfileVM = UserProfileViewModel()
    
    @State var email: String = ""
    
    init() {
        print("User Profile VM equals: \(String(describing: userProfileVM.userProfile))")
        if userProfileVM.userProfile?.email != nil {
            _email = State(initialValue: userProfileVM.userProfile!.email!)
        } else {
            _email = State(initialValue: "")
        }
    }

推荐答案

您可以使用Firebase Auth类的实例方法 addStateDidChangeListener(_:)并分配 User 实例通过完成处理程序传递给您在 AuthenticationState 中的 loggedInUser 自己的属性.这样,无论何时用户登录或注销,您都会收到通知-保持同步.像这样:

You could use the instance method addStateDidChangeListener(_:) of Firebase's Auth class and assign the User instance passed in via the completion handler to your own property of loggedInUser in AuthenticationState. This way, you'll get notified whenever the user logs in or out - staying in sync. Like so:

class AuthenticationState: NSObject, ObservableObject {
    
    @Published var loggedInUser: User?
    
    //...other properties...

    static let shared = AuthenticationState()
    private let auth = Auth.auth()
    
    override init() {
        super.init()
        auth.addStateDidChangeListener { [weak self] (_, user) in
            self?.loggedInUser = user
        }
    }
    
}

那么,您可以使用Combine来在您的 AuthenticationState 实例和 UserProfileViewModel 实例之间形成数据管道,这是正确的.您可以使用Combine的 sink(receiveValue:)方法来绑定 UserProfileViewModel ,而不是在 init()期间进行一次性分配(如您目前所使用的).code>的 loggedInUser 属性设置为 AuthenticationState 的:

Then, you're correct in that you can use Combine to form a data pipeline between your AuthenticationState instance and UserProfileViewModel instance. Instead of a one-time assignment during init() (as you have currently), you can use Combine's sink(receiveValue:) method to bind UserProfileViewModel's loggedInUser property to AuthenticationState's:

class UserProfileViewModel: ObservableObject {
    @Published var loggedInUser: User?
    init() {
        AuthenticationState.shared.$loggedInUser
            .sink { [weak self] user in
                self?.loggedInUser = user
            }
            .store(in: &subs)
    }
    private var subs: Set<AnyCancellable> = .init()
}

使用 $ loggedInUser 访问 @Published 提供的内置发布者.在这里,您可以接收并创建订阅.同样请注意,由 sink(receiveValue:)返回的 AnyCancellable 的存储.对于需要长时间使用 UserProfileViewModel 的情况,请牢牢引用.

Using $loggedInUser accesses the built-in publisher provided by @Published. And here you can sink and create a subscription. Note, also, the storage of the AnyCancellable returned by sink(receiveValue:). Keep a strong reference to this for however long UserProfileViewModel needs to be around for.

这篇关于在不相关的ViewModel中从我的身份验证类订阅用户变量-Swift/Combine的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆