I am working on a SwiftUI project that pulls data from Firebase Firestore using Combine. Each user has the ability to create "Offers" in the app. In order to list their offers on their account page I am using onAppear to pass the currentUserUid to my View Model so I can filter the database results using the currentUserUid. The OfferHistoryView is below. This works great the when the view first appears. My issue is when I return from the OfferDetailView, I received the following message.
ForEach<Array, String, NavigationLink<OfferRowView, ModifiedContent<OfferDetailView, _EnvironmentKeyWritingModifier<Optional>>>>: the ID occurs multiple times within the collection, this will give undefined results!
While this is not crashing the app it is not ideal. I'v tried, deleting all items from the collection each time the view loads, and each time combine is called and this does not resolve the issue. I've also added print statements to try and catch the duplicates but I never see a duplicate. You can see my print statements and the rest of the corresponding files below. Any help would be appreciated.
OfferViewHistory - Where the message is originating from.
struct OfferHistoryView: View {
let db = Firestore.firestore()
@EnvironmentObject var authSession: AuthSession
@EnvironmentObject var offerHistoryViewModel: OfferHistoryViewModel
var body: some View {
return VStack {
List {
ForEach(self.offerHistoryViewModel.offerRowViewModels, id: \.id) { offerRowViewModel in
NavigationLink(destination: OfferDetailView(offerDetailViewModel: OfferDetailViewModel(offer: offerRowViewModel.offer, listing: offerRowViewModel.listing ?? testListing1))
.environmentObject(authSession)
) {
OfferRowView(offerRowViewModel: offerRowViewModel)
}
} // ForEach
} // List
.navigationBarTitle("Offer History")
} // VStack
.onAppear(perform: {
for offerRowViewModel in self.offerHistoryViewModel.offerRowViewModels {
print("Before startCombine: \(offerRowViewModel.id)")
}
self.offerHistoryViewModel.startCombine(currentUserUid: self.authSession.currentUserUid)
for offerRowViewModel in self.offerHistoryViewModel.offerRowViewModels {
print("After startCombine: \(offerRowViewModel.id)")
}
})
} // View
}
OfferHistoryViewModel - where combine is called.
class OfferHistoryViewModel: ObservableObject {
var offerRepository: OfferRepository
// Published Properties
@Published var offerRowViewModels = [OfferRowViewModel]()
// Combine Cancellable
private var cancellables = Set<AnyCancellable>()
// Intitalizer
init(offerRepository: OfferRepository) {
self.offerRepository = offerRepository
}
// Starting Combine - Filter results for offers created by the current user only.
func startCombine(currentUserUid: String) {
for offerRowViewModel in self.offerRowViewModels {
print("Before startCombine func: \(offerRowViewModel.id)")
}
offerRepository
.$offers
.receive(on: RunLoop.main)
.map { offers in
offers
.filter { offer in
(currentUserUid != "" ? offer.userId == currentUserUid : false)
}
.map { offer in
OfferRowViewModel(offer: offer, listingRepository: ListingRepository())
}
}
.assign(to: \.offerRowViewModels, on: self)
.store(in: &cancellables)
for offerRowViewModel in self.offerRowViewModels {
print("After startCombine func: \(offerRowViewModel.id)")
}
}
}
OfferRowView
struct OfferRowView: View {
@ObservedObject var offerRowViewModel: OfferRowViewModel
var body: some View {
// Convenience variable for accessing the offer & listing.
let offer = offerRowViewModel.offer
let listing = offerRowViewModel.listing
return VStack {
Text(offer.id ?? "ID")
Text(listing?.id ?? "ID")
} // VStack
} // View
}
OfferRowViewModel
class OfferRowViewModel: ObservableObject, Identifiable {
// Properties
var id: String = ""
var listingRepository: ListingRepository
// Published Properties
@Published var offer: Offer
@Published var listing: Listing?
// Combine Cancellable
private var cancellables = Set<AnyCancellable>()
// Initializer
init(offer: Offer, listingRepository: ListingRepository) {
self.offer = offer
self.listingRepository = listingRepository
self.startCombine()
}
// Starting Combine
func startCombine() {
// Get Offer
$offer
.receive(on: RunLoop.main)
.compactMap { offer in
offer.id
}
.assign(to: \.id, on: self)
.store(in: &cancellables)
// Get Connected Listing
listingRepository
.$listings
.receive(on: RunLoop.main)
.map { listings in
listings
.first(where: { $0.id == self.offer.listingId})
}
.assign(to: \.listing, on: self)
.store(in: &cancellables)
}
}