Search code examples
swiftswiftuicombine

View not reacting to changes of Published property when its chained from another ObservedObject


Below is the SwiftUI view which owns a ViewModel and the logic is if the viewModel.authenticationService.user contains a user object then it will show the HomeView, else case will be asked for Login. So initially the viewModel.authenticationService.user is nil and user logins successful the user object in no more nil.

View

struct WelcomeView: View {
    
    @ObservedObject private var viewModel: WelcomeView.Model
    @State private var signInActive: Bool = false
    
    init(viewModel: WelcomeView.Model) {
        self.viewModel = viewModel
    }
    
    var body: some View {
        if viewModel.authenticationService.user != nil {
            HomeView()
        } else { 
            LoginView()
        }
    } 

ViewModel

extension WelcomeView {
    
    final class Model: ObservableObject {
        
        @ObservedObject var authenticationService: AuthenticationService
        
        init(authenticationService: AuthenticationService) {
            self.authenticationService = authenticationService
        }
    }
}

AuthenticationService

final class AuthenticationService: ObservableObject {
    
    @Published var user: User?
    private var authenticationStateHandle: AuthStateDidChangeListenerHandle?
    
    init() {
        addListeners()
    }
    
    private func addListeners() {
        if let handle = authenticationStateHandle {
            Auth.auth().removeStateDidChangeListener(handle)
        }
        
        authenticationStateHandle = Auth.auth()
            .addStateDidChangeListener { _, user in
                self.user = user
            }
    }
    
    static func signIn(email: String, password: String, completion: @escaping AuthDataResultCallback) {
        if Auth.auth().currentUser != nil {
            Self.signOut()
        }
        Auth.auth().signIn(withEmail: email, password: password, completion: completion)
    }
}

However, when the user object is updated with some value it does not update the View. I am not sure as I am new to reactive way of programming. There is a chain of View -> ViewModel -> Service and the published user property is in the Service class which gets updated successfully once user login.

Do I need to add a listener in the ViewModel which reacts to Service published property? Or is there any direct way for this scenario to work and get the UI Updated?


Solution

  • In case of scenario where there is a chain from View -> ViewModel -> Services:

    @ObservedObject does not work on classes. Only works in case of SwiftUI View(structs). So if you want to observe changes on your view model from a service you need to manually listen/subscribe to it.

    self.element.$value.sink(
                    receiveValue: { [weak self] _ in
                        self?.objectWillChange.send()
                    }
                )
    

    You can also use the .assign. Find more details here