onReceive is not triggered after published value has changed

Viewed 437

The point is that after successfully adding a record to the database (when I tap confirm button), the view should be closed. The problem is that it does not close after adding, .onReceive does not triggered, although the publisher PassthroughSubject sends a new value. There is one thing: the view will close if an alert was triggered before clicking the confirm button (for example, when not all fields are filled in, a warning is displayed)

Also properties viewModel.name and viewModel.name (for TextField's) after adding a record become equal to empty strings, although I do not explicitly assign such a value to them anywhere in the code (as if a new instance of the view model is created where such default values are)

View:

struct AddChallengeView: View {
    @Environment(\.presentationMode) var presentationMode
    @ObservedObject var viewModel = AddChallengeViewModel()
    
    
    var body: some View {
        Form{
            Section(header: Text("Name")){
                TextField("Type challenge name", text: $viewModel.name) //viewModel.name == "" after new record added
            }
            
            Section(header: Text("Description")){
                TextEditor(text: $viewModel.description) //viewModel.description == "" after new record added
            }
            //...
            
            Section{
                Button(action: { viewModel.addChallenge()}){
                    HStack{
                        Spacer()
                        Text("Submit").bold()
                        Spacer()
                    }
                }
            }
            
        }.alert(isPresented: $viewModel.showErrorAlert){
            Alert(title: Text("Please, set all values!"))
        }
        .onReceive(viewModel.viewDismissalModePublisher) { shouldDismiss in
        print("new value received") //not printed 
        if shouldDismiss {
            self.presentationMode.wrappedValue.dismiss()
        }
    }
        
    }
}

ViewModel:

class AddChallengeViewModel: ObservableObject{
    var viewDismissalModePublisher = PassthroughSubject<Bool, Never>()
    
    private var shouldDismissView = false {
        print("sending new value") //printed
        didSet {
            viewDismissalModePublisher.send(shouldDismissView)
        }
    }
    
    @Published var showErrorAlert = false

    @Published var name = ""
    @Published var description = ""

    //...
    
    func addChallenge () {
        //...
        
        if (name != "" && description != "" && grounds.count != 0){
            Firestore.firestore().collection("users").document(Auth.auth().currentUser!.uid).collection("challenges").addDocument(data: [
                "name": "\(name)",
                "description": "\(description)",
                "grounds": grounds
            ]) { err in
                if let err = err {
                    print("Error adding document: \(err)")
                } else {
                    print("setting new value") //printed
                    self.shouldDismissView = true
                }
            }
        } else {
            showErrorAlert.toggle()
        }
    }
}

UPDATE: I found a solution. We need to replace @ObservedObject var viewModel = AddChallengeViewModel() with @StateObject var viewModel = AddChallengeViewModel() in AddChallengeView but why does it work?

1 Answers

but why does it work?

Most probably you use AddChallengeView in NavigationView (or another container, which recreates content during workflow), so having

@ObservedObject var viewModel = AddChallengeViewModel()

creates new instance of AddChallengeViewModel class on each such view re-creation, so any previous changes are lost. However

@StateObject var viewModel = AddChallengeViewModel()

preserves instance of model (created at first time) and injects it into new view of same type re-created in same place of view hierarchy. Moreover, new view is notified about all changes of that same model.

Actually @StateObject property wrapper gives same behaviour for ObservableObject as @State gives for value types.

Related