Search code examples
swiftuiswiftdata

SwiftData changes in singleton don't persist


I have a pretty simple singleton that holds the SwiftData modelContainer so that I can manipulate the DB a bit outside of the View but for some reason changes are not persisting.

import SwiftData
import SwiftUI

@Model
class Item {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

class DatabaseManager {
    static let shared = DatabaseManager()
    var modelContainer: ModelContainer {
        do {
            let container = try ModelContainer(for: Item.self)
            return container
        } catch {
            fatalError("Unable to create ModelContainer: \(error)")
        }
    }
    
    private init() {
        print(modelContainer.configurations.first?.url.path ?? "No database file")
        // Private initialization to ensure just one instance is created.
    }
    
    @MainActor func addItem() {
        let item = Item(name: UUID().uuidString)
        modelContainer.mainContext.insert(item)
    }
    
    @MainActor func itemCount() -> Int {
        let fetchDescriptor = FetchDescriptor<Item>()
        if let items = try? modelContainer.mainContext.fetch(fetchDescriptor) {
            return items.count
        }
        return 0
    }
}

I pass my modelContainer into my main window:

@main
struct testApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(DatabaseManager.shared.modelContainer)
    }
}

Just as a test, all I do in my view is push the button to add an item and display the itemCount.

struct ContentView: View {
    @State private var itemCount: Int = 0
    
    var body: some View {
        Button {
            DatabaseManager.shared.addItem()
            itemCount = DatabaseManager.shared.itemCount()
        } label: {
            Text("\(itemCount) Items")
        }
    }
}

The item count stays at zero no matter how many times I push the button


Solution

  • Exactly as @vadian said. As a test, try swapping this:

    var modelContainer: ModelContainer {
        do {
            let container = try ModelContainer(for: Item.self)
            return container
        } catch {
            fatalError("Unable to create ModelContainer: \(error)")
        }
    }
    

    with this:

    let modelContainer = try! ModelContainer(for: Item.self)
    

    and see if it works.

    Also, unrelated to your question but you can use @Query to handle that itemCount function in the view:

    struct ContentView: View {
        @Query var items: [Item]
        
        var body: some View {
            Button {
                DatabaseManager.shared.addItem()
            } label: {
                Text("\(items.count) Items")
            }
        }
    }