2
votes

I am working on incorporating a Firestore repository into a SwiftUI 2 app. The list view loads appropriately, and refreshes automatically when the data in the Firestore collection is changed. A NavigationLink loads the appropriate DetailView correctly. A button on the DetailView is set to change data within the relevant document, and works correctly, but the DetailView page does not refresh with the correct data (it appears correctly when exiting to the list view and returning); likewise the DetailView will not respond to changes to the collection made elsewhere.

I have attempted to use various configurations of ObservedObject and State, but have been unable to get the result intended.

Any help would be appreciated. Snipped Code listed below shows the flow, and uses the status field as an example.

CustomerRepository

class CustomerRepository: ObservableObject {
    
    private let path: String = "customers"
    private let store = Firestore.firestore()
    @Published var customers: [Customer] = []
    
    private var cancellables: Set<AnyCancellable> = []
    
    init() {
        store.collection(path)
            .addSnapshotListener { querySnapshot, error in
                if let error = error {
                    print("Error getting customers \(error.localizedDescription)")
                    return
                }
                
                self.customers = querySnapshot?.documents.compactMap { document in
                    
                    try? document.data(as: Customer.self)
                    
                } ?? []
            }
    }
    
    func update(_ customer: Customer) {
        guard let customerID = customer.id else { return }
        
        do {
            try store.collection(path).document(customerID).setData(from: customer)
        } catch {
            fatalError("Unable to update record: \(error.localizedDescription)")
        }
    }
}

CustomerListViewModel

class CustomerListViewModel: ObservableObject {
    
    @Published var customerViewModels: [CustomerViewModel] = []
    
    private var cancellables: Set<AnyCancellable> = []
    
    @Published var customerRepository = CustomerRepository()
    
    init() {
        customerRepository.$customers.map { customers in
            customers.map(CustomerViewModel.init)
        }
        .assign(to: \.customerViewModels, on: self)
        
        .store(in: &cancellables)
    }
}

CustomerViewModel

class CustomerViewModel: ObservableObject, Identifiable {
    
    private let customerRepository = CustomerRepository()
    
    @Published var customer: Customer
    
    private var cancellables: Set<AnyCancellable> = []
    
    var id = ""
    
    init(customer: Customer) {
        self.customer = customer
        
        $customer
            .compactMap { $0.id }
            .assign(to: \.id, on: self)
            .store(in: &cancellables)
    }
    
    func update(customer: Customer) {
        customerRepository.update(customer)
    }
}

CustomerListView

struct CustomerListView: View {
    
    @ObservedObject var customerListViewModel = CustomerListViewModel()

    var body: some View {
        NavigationView {
            List {
                ForEach(customerListViewModel.customerViewModels) { customerViewModel in
                    
                    NavigationLink(
                        destination: CustomerDetailView(customerViewModel: customerViewModel)) {
                            CustomerCell(customerViewModel: customerViewModel)
                        }
                }
            }
        }
    }
}

CustomerDetailView

struct CustomerDetailView: View {
    var customerViewModel: CustomerViewModel
    
    var body: some View {
        VStack {
            
            Text(customerViewModel.customer.status.description)
            
        }.onTapGesture {
                    nextTask()
                }
    }

private func nextTask() {
        switch customerViewModel.customer.status {
        case .dispatched:
            customerViewModel.customer.status = .accepted
        
...

        default:
            return
        }
        
        update(customer: customerViewModel.customer)
        
    }
    
    func update(customer: Customer) {
        customerViewModel.update(customer: customer)
    }
}
1
I have similar question here stackoverflow.com/questions/66141387/…egeeke
Thanks. I looked at the two suggestion you had received and neither seems to resolve my issue. But I will bookmark your question in case I get a solution.Craig

1 Answers

0
votes

After some reconfiguring, and the help of this example:

https://peterfriese.dev/swiftui-firebase-update-data/

I was able to solve my issue. Below is a breakdown of the revised code in case it will help others....

CustomerViewModel

class CustomerViewModel: ObservableObject, Identifiable {
    
    @Published var customer: Customer
    @Published var modified = false
    
    private var cancellables: Set<AnyCancellable> = []
    
    init(customer: Customer = Customer(status: .new)) {
        self.customer = customer
        
        self.$customer
            .dropFirst()
            .sing { [weak self] customer in
               self?.modified = true
            }
            .store(in: &cancellables)
    }
    
    func handleDoneTapped() {
      self.updateOrAddCustomer()
  }
}

CustomerListView

// Snip to Navigation Link

    NavigationLink(
        destination: CustomerDetailView(customerViewModel: CustomerViewModel(customer: customerViewModel.customer))) {
                            CustomerCell(customerViewModel: customerViewModel)
                        }

CustomerDetailView

struct CustomerDetailView: View {
    @ObservedObject var customerViewModel = CustomerViewModel()
    
    var body: some View {
        VStack {
            
            Text(customerViewModel.customer.status.description)
            
        }.onTapGesture {
                    nextTask()
                }
    }

private func nextTask() {
        switch customerViewModel.customer.status {
        case .dispatched:
            customerViewModel.customer.status = .accepted
        
...

        default:
            return
        }
        
        update(customer: customerViewModel.customer)
        
    }
    
    func update(customer: Customer) {
        customerViewModel.handleDoneTapped()
    }
}