-1

I’m working on Testing a SwiftUI project using SwiftData where I need to add Book objects to my model context from both the ContentView and a background task. I want to ensure that both operations use the same ModelContainer for consistency.

I’ve created a simple project to illustrate my setup, but I’m unsure if I’m approaching this correctly. Could someone provide guidance on how to properly add Book objects from both the ContentView and the background task while using the same context?

import SwiftUI
import SwiftData

@main
struct testQueryApp: App {
    var sharedModelContainer: ModelContainer = {
        let schema = Schema([
            Book.self,
        ])
        let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)

        do {
            return try ModelContainer(for: schema, configurations: [modelConfiguration])
        } catch {
            fatalError("Could not create ModelContainer: \(error)")
        }
    }()
    
    var body: some Scene {
        WindowGroup {
            ContentView(modelContainer: sharedModelContainer)
        }
        .modelContainer(sharedModelContainer)
    }
}

import SwiftUI
import SwiftData

struct ContentView: View {
    @Environment(\.modelContext) private var modelContext
    @Query private var items: [Book]
    @StateObject private var viewModel: NetworkingManager
    
    init(modelContainer: ModelContainer) {
        _viewModel = StateObject(wrappedValue: NetworkingManager(modelContainer: modelContainer))
    }
    
    var body: some View {
        NavigationSplitView {
            List {
                ForEach(items) { item in
                    NavigationLink {
                        Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))")
                    } label: {
                        Text("Book: \(item.title)")
                    }
                }
                .onDelete(perform: deleteItems)
            }
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    EditButton()
                }
                ToolbarItem {
                    Button(action: addItem) {
                        Label("Add Item", systemImage: "plus")
                    }
                }
                ToolbarItem(placement: .topBarLeading) {
                    Button {
                        Task {
                            await viewModel.parser.addBook()
                        }
                    } label: {
                        Text("Return")
                    }
                }
            }
        } detail: {
            Text("Select an item")
        }
    }

    private func addItem() {
        withAnimation {
            let newItem = Book(timestamp: Date(), title: "Book01")
            modelContext.insert(newItem)
        }
    }

    private func deleteItems(offsets: IndexSet) {
        withAnimation {
            for index in offsets {
                modelContext.delete(items[index])
            }
        }
    }
}

import SwiftData
import SwiftUI
import Foundation

@Observable
class NetworkingManager: ObservableObject {
    let parser: BackgroundParser
    
    init(modelContainer: ModelContainer) {
        self.parser = BackgroundParser(modelContainer: modelContainer)
    }
}

actor BackgroundParser: ObservableObject {
    let executor: any ModelExecutor
    let context: ModelContext
    
    init(modelContainer: ModelContainer) {
        context = ModelContext(modelContainer)
        executor = DefaultSerialModelExecutor(modelContext: context)
    }
    
    func addBook() {
        let newItem = Book(timestamp: Date(), title: "Background Book")
        context.insert(newItem)
    }
}

import Foundation
import SwiftData

@Model
final class Book {
    var timestamp: Date
    var title : String
    
    init(timestamp: Date, title: String) {
        self.timestamp = timestamp
        self.title = title
    }
}

I appreciate any insights or suggestions on how to properly manage adding items from both the ContentView and background tasks using the same context.

Thanks in advance!

4
  • Note, you should not use @Observable and ObservableObject, use one or the other, not both. If you use ObservableObject then you should not nest them, eg BackgroundParser inside NetworkingManager. Commented Jul 9 at 8:15
  • You should change NetworkingManager to be a struct in an EnvironmentKey so you can mock it for testing. It should only have funcs no mutable data in it.
    – malhal
    Commented Jul 9 at 9:35
  • can make me an example?not so clear Commented Jul 9 at 10:01
  • You talk about using the same ModelContext sometimes but also about using the same ModelContainer which is confusing, I assume you mean the latter? And there is nothing to observe in the types you have marked with Observable/ObservableObject. Commented Jul 9 at 10:23

0

Browse other questions tagged or ask your own question.