0

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?

Parth Adroja
  • 13,198
  • 5
  • 37
  • 71
  • I‘m guessing that User is a class and not a struct, right? Due to the fact that classes are reference types, the @published property wrapper wonˋt notice changes within your user object. – Adrian Dec 03 '21 at 09:10
  • 1
    The ObservedObject wrapper does not work in a class. You can get updates using sink and ant cancellable – lorem ipsum Dec 03 '21 at 09:22

1 Answers1

3

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

Parth Adroja
  • 13,198
  • 5
  • 37
  • 71