0

I am creating a SwiftUI List with Details.

This list is fetching JSON data from Firebase Realtime. The data consist of 5 birds with an ID, a name and an image URL. My problem is the following:

Each time I click on the back button after I navigate to details, the data get doubled every single time, what am I doing wrong? (see screenshots).

I am using MVVM design pattern, I am listening and removing that listener every time the View appears and disappears.

Please, find the code below:

Main View:

 var body: some View {
        NavigationStack {
            List(viewModel.birds) { bird in
                NavigationLink(destination: DetailsView(bird: bird)) {
                    HStack {
                        VStack(alignment: .leading) {
                            Text(bird.name).font(.title3).bold()
                        }
                        Spacer()
                        AsyncImage(url: URL(string: bird.imageURL)) { phase in
                            switch phase {
                            // downloading image here 
                            }
                        }
                }
                }  
            }.onAppear {
                viewModel.listentoRealtimeDatabase()
            }
            .onDisappear {
                viewModel.stopListening()
            }.navigationTitle("Birds")
        }
    }

DetailsView:

struct DetailsView: View {
    var bird: Bird
    var body: some View {
        Text("\(bird.name)")
    }
}

Model:

struct Bird: Identifiable, Codable {
    var id: String
    var name: String
    var imageURL: String
}

View Model:

final class BirdViewModel: ObservableObject {
    @Published var birds: [Bird] = []
    
    private lazy var databasePath: DatabaseReference? = {
        let ref = Database.database().reference().child("birds")
        return ref
    }()
    
    private let encoder = JSONEncoder()
    private let decoder = JSONDecoder()
    
    func listentoRealtimeDatabase() {
        guard let databasePath = databasePath else {
            return
        }
        databasePath
            .observe(.childAdded) { [weak self] snapshot in
                guard
                    let self = self,
                    var json = snapshot.value as? [String: Any]
                else {
                    return
                }
                json["id"] = snapshot.key
                do {
                    let birdData = try JSONSerialization.data(withJSONObject: json)
                    let bird = try self.decoder.decode(Bird.self, from: birdData)
                    self.birds.append(bird)
                } catch {
                    print("an error occurred", error)
                }
            }
    }
    
    func stopListening() {
        databasePath?.removeAllObservers()
    }
}

screenshot how it should be

screenshot after navigating

Sully07
  • 11
  • 2
  • In `listentoRealtimeDatabase`, you could try clearing the array `self.birds = []` before `let birdData = try ...` – workingdog support Ukraine Oct 12 '22 at 22:22
  • Thank you for your suggestion. I actually empty the array in the stopListening() function and it resolved the issue. However, it is loading after navigating back which is not the best UX. I tried to put 'self.birds = []' where you suggested it but it is only returning one row then. – Sully07 Oct 16 '22 at 14:05

0 Answers0