Show Core Data fetch results in WidgetKit

I'll say up front that this code was working in Xcode 14 beta 3, but now in beta 4, it's not.

I'm using the default Core Data code that comes when you select the Use Core Data checkbox when creating a new project, except I changed the sample item to a sample Pokemon.

import CoreData

struct PersistenceController {
    static let shared = PersistenceController()
    let container: NSPersistentContainer

    static var preview: PersistenceController = {
        let result = PersistenceController(inMemory: true)
        let viewContext = result.container.viewContext
        let samplePokemon = Pokemon(context: viewContext)
        samplePokemon.id = 1
        samplePokemon.name = "bulbasaur"
        samplePokemon.types = ["grass", "poison"]
        samplePokemon.hp = 45
        samplePokemon.attack = 49
        samplePokemon.defense = 49
        samplePokemon.specialAttack = 65
        samplePokemon.specialDefense = 65
        samplePokemon.speed = 45
        samplePokemon.sprite = URL(string: "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/1.png")
        samplePokemon.shiny = URL(string: "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/shiny/1.png")
        samplePokemon.favorite = false
        do {
            try viewContext.save()
        } catch {
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
        return result
    }()

    init(inMemory: Bool = false) {
        container = NSPersistentContainer(name: "Poke")
        if inMemory {
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        }
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        container.viewContext.automaticallyMergesChangesFromParent = true
    }
}

I have the PersistenceController added to my app and my widget target, so they can both access it. I also added my Core Data model to both targets.

Now, here's my widget code, and remember this was already working in beta 3.

import WidgetKit
import SwiftUI
import CoreData

struct Provider: TimelineProvider {
    let managedObjectContext: NSManagedObjectContext
    
    var randomPokemon: Pokemon {
        let fetchRequest: NSFetchRequest<Pokemon> = Pokemon.fetchRequest()
        
        var results: [Pokemon] = []
        
        do {
            results = try managedObjectContext.fetch(fetchRequest)
        } catch {
            print("Couldn't fetch: \(error)")
        }
        
        print(results)
        
        if let randomPokemon = results.randomElement() {
            return randomPokemon
        }
        
        return SamplePokemon.samplePokemon()
    }
    
    init(context: NSManagedObjectContext) {
        managedObjectContext = context
    }
    
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date(), pokemon: SamplePokemon.samplePokemon())
    }

    func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date(), pokemon: randomPokemon)
        completion(entry)
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [SimpleEntry] = []

        // Generate a timeline consisting of five entries an hour apart, starting from the current date.
        let currentDate = Date()
        for hourOffset in 0 ..< 5 {
            let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
            var entry = SimpleEntry(date: entryDate, pokemon: randomPokemon)
            
            entry = SimpleEntry(date: entryDate, pokemon: randomPokemon)
            
            entries.append(entry)
        }

        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }
}

struct SimpleEntry: TimelineEntry {
    let date: Date
    let pokemon: Pokemon
}

struct RandomPokemonEntryView : View {
    @Environment(\.widgetFamily) var widgetSize
    
    var entry: Provider.Entry

    var body: some View {
        switch widgetSize {
        case .systemSmall:
            WidgetPokemon(pokemon: entry.pokemon, widgetSize: .small)
        case .systemMedium:
            WidgetPokemon(pokemon: entry.pokemon, widgetSize: .medium)
        case .systemLarge:
            WidgetPokemon(pokemon: entry.pokemon, widgetSize: .large)
        default:
            WidgetPokemon(pokemon: entry.pokemon, widgetSize: .large)
        }
    }
}

@main
struct RandomPokemon: Widget {
    let kind: String = "RandomPokemon"

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: Provider(context: PersistenceController.shared.container.viewContext)) { entry in
            RandomPokemonEntryView(entry: entry)
        }
        .configurationDisplayName("Random Pokemon")
        .description("Shows a random Pokemon.")
    }
}

struct RandomPokemon_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            RandomPokemonEntryView(entry: SimpleEntry(date: Date(), pokemon: SamplePokemon.samplePokemon()))
                .previewContext(WidgetPreviewContext(family: .systemSmall))
            
            RandomPokemonEntryView(entry: SimpleEntry(date: Date(), pokemon: SamplePokemon.samplePokemon()))
                .previewContext(WidgetPreviewContext(family: .systemMedium))
            
            RandomPokemonEntryView(entry: SimpleEntry(date: Date(), pokemon: SamplePokemon.samplePokemon()))
                .previewContext(WidgetPreviewContext(family: .systemLarge))
        }
    }
}

The part to focus on is right at the top, that randomPokemon computed property. The thing is, again, this exact code was working before. That's why I'm so frustrated that it just refuses to work now, no matter what I do. In my searching I've seen one suggestion is to create an App Group and share the Core Data stack in there. But I've seen other solutions that don't say that. And obviously, again, mine was already working without an App Group, so yeah...

Now my questions. Was there a bug in Xcode 14 beta 3 that allowed me to access the same Core Data store without an App Group? Or is there now a bug in Xcode 14 beta 4 that is just breaking my code somehow?

And either way, are there any changes I can make to my code so I can get my data to fetch for my widget?

"In my searching I've seen one suggestion is to create an App Group and share the Core Data stack in there". Do that.

@deeje Obviously that's the solution if I can't get it to work the same way it did in beta 3, but I'd rather not do the extra work of creating an app group if I don't need it.

Ok, I finally decided to just go with the App Group, but now I am getting an error in my preview container. This code:

struct PersistenceController {
...
    static var preview: PersistenceController = {
        let result = PersistenceController(inMemory: true)
        let viewContext = result.container.viewContext
        let samplePokemon = Pokemon(context: viewContext)
        samplePokemon.id = 1
        samplePokemon.name = "bulbasaur"
        samplePokemon.types = ["grass", "poison"]
        samplePokemon.hp = 45
        samplePokemon.attack = 49
        samplePokemon.defense = 49
        samplePokemon.specialAttack = 65
        samplePokemon.specialDefense = 65
        samplePokemon.speed = 45
        samplePokemon.sprite = URL(string: "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/1.png")
        samplePokemon.shiny = URL(string: "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/shiny/1.png")
        samplePokemon.favorite = false
        do {
            try viewContext.save()
        } catch {
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
        return result
    }()
...
}

From my PersistenceController catches an error on the try viewContext.save() line when I try to run my widget, but it still works fine when running my app.

Here's my new app group shared container code in the same PersistenceController:

init(inMemory: Bool = false) {
    let sharedStoreURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.myApGroup")!.appending(path: "Poke.sqlite")
    let description = NSPersistentStoreDescription(url: sharedStoreURL)
     
    container = NSPersistentContainer(name: "Poke")
    if inMemory {
      container.persistentStoreDescriptions = [description]
    }
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
      if let error = error as NSError? {
        fatalError("Unresolved error \(error), \(error.userInfo)")
      }
    })
    container.viewContext.automaticallyMergesChangesFromParent = true
}

The error is not here, but again in my static var preview area when I try to save the viewContext.

Here's my console:

Any help with this would be much appreciated.

Please file a Technical Support Incident with Apple. We should be able to help debug the issue there.

https://developer.apple.com/support/technical/

Show Core Data fetch results in WidgetKit
 
 
Q