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