WidgetKit doesn't fetch updated data from Core Data when WidgetCenter.shared.reloadAllTimelines() gets called

The app uses Core Data + CloudKit. When the app gets launched WidgetKit file fetches correct entries from Core Data but when I add new entries to Core Data from the main app and call WidgetCenter.shared.reloadTimelines() updated entries don't get fetched and old data is displayed inside the widget.

Main app and Widget target have the same App group, iCloud capability enabled for both targets.

When I call WidgetCenter.shared.reloadTimelines() from the Main app Widget reloads (timer added to Widget view sets to zero) but fetch from Core Data doesn't return newly added entries. Then I restart the app and correct entries get fetched.

Why doesn't it fetch correct entries when WidgetCenter.shared.reloadTimelines() gets called and only works when app is relaunched?

Widget's code:

Code Block import WidgetKitimport SwiftUIimport CoreDatastruct Provider: TimelineProvider {    func placeholder(in context: Context) -> HabitsEntry {        HabitsEntry(date: Date(), habitsCount: 0)    }    func getSnapshot(in context: Context, completion: @escaping (HabitsEntry) -> ()) {        let entry = HabitsEntry(date: Date(), habitsCount: 0)        completion(entry)    }    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {                let managedObjectContext = Storage.viewContext        let request = NSFetchRequest<NSFetchRequestResult>(entityName: "DailyHabit")        let predicate = Storage.getCurrentDatePredicate()        request.predicate = predicate        var habits = 0        do { habits = try managedObjectContext.count(for: request)  }        catch let error as NSError {print ("Could not fetch \(error), \(error.userInfo)")}        let entry = [HabitsEntry(date: Date(), habitsCount: habits)]        let timeline = Timeline(entries: entry, policy: .never)        completion(timeline)    }}struct HabitsEntry: TimelineEntry {    let date: Date    var habitsCount: Int}struct HabitsHomeWidgetEntryView : View {    var entry: Provider.Entry    var body: some View {      Text(entry.date, style: .timer)      Text("There are \(entry.habitsCount) daily habits")    }}@mainstruct HabitsHomeWidget: Widget {    let kind: String = "HabitsHomeWidget"    var body: some WidgetConfiguration {        StaticConfiguration(kind: kind, provider: Provider()) { entry in            HabitsHomeWidgetEntryView(entry: entry)        }        .configurationDisplayName("My Widget")        .description("This is an example widget.")    }}   

Here is the code for Core Data:

Code Block import UIKitimport CoreDataimport WidgetKitclass Storage {    static let shared = Storage()    static var persistentContainer: NSPersistentContainer {    return Storage.shared.persistentContainer  }    static var viewContext: NSManagedObjectContext {    return persistentContainer.viewContext  }    lazy var persistentContainer: NSPersistentContainer = {    let container: NSPersistentContainer        let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.appGroupName)!    let storeURL = containerURL.appendingPathComponent("NoRegrets.sqlite")    let description = NSPersistentStoreDescription(url: storeURL)    if isICloudContainerAvailable {      container = NSPersistentCloudKitContainer(name: Constants.persistentContainerName)    } else {      container = NSPersistentContainer(name: Constants.persistentContainerName)    }    description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)    description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)    description.shouldMigrateStoreAutomatically = true    description.shouldInferMappingModelAutomatically = true    let storeDescription = description        container.persistentStoreDescriptions = [storeDescription]        container.loadPersistentStores(completionHandler: { (storeDescription, error) in      if let error = error as NSError? {        return      }    })        container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy    container.viewContext.automaticallyMergesChangesFromParent = true        do {      try container.viewContext.setQueryGenerationFrom(.current)    } catch {      print("Failed to pin viewContext to the current generation: \(error)")    }    return container  }()    var isICloudContainerAvailable: Bool {    FileManager.default.ubiquityIdentityToken != nil  }    // MARK: - Core Data Saving support  func saveContext() {    let context = persistentContainer.viewContext    if context.hasChanges {      do {        try context.save()              } catch {        let nserror = error as NSError        print("Saving error: \(nserror)")      }    }  }}// MARK: - Helper methodsextension Storage {  func getCurrentUser() -> User? {    let fetchRequest = User.createFetchRequest()    fetchRequest.fetchLimit = 1    fetchRequest.sortDescriptors = [NSSortDescriptor(key: "objectID", ascending: false)]        let user = try? Storage.viewContext.fetch(fetchRequest).first    return user  }    class func getCurrentDatePredicate() -> NSPredicate {    var calendar = Calendar.current    calendar.timeZone = NSTimeZone.local    let dateFrom = calendar.startOfDay(for: Date())    let dateTo = calendar.date(byAdding: .day, value: 1, to: dateFrom)    let fromPredicate = NSPredicate(format: "date >= %@", dateFrom as NSDate)    let toPredicate = NSPredicate(format: "date < %@", dateTo! as NSDate)    return NSCompoundPredicate(andPredicateWithSubpredicates: [fromPredicate, toPredicate])  }}




Answered by Engineer in 650260022
A bug report with a sysdiagnose after reproducing the issue would be helpful.

Are the Widget and main app sharing exactly the same database file in a group container ? Or are you syncing the two as effectively separate apps through CloudKit ?
Accepted Answer
A bug report with a sysdiagnose after reproducing the issue would be helpful.

Are the Widget and main app sharing exactly the same database file in a group container ? Or are you syncing the two as effectively separate apps through CloudKit ?
Yep, same database for both Widget and the main app.

It turns out all I had to do was update Core Data's context to the current store generation (in WidgetKit file, getTimeline() function) before making a fetch:

Code Block try? Storage.viewContext.setQueryGenerationFrom(.current)Storage.viewContext.refreshAllObjects()

Found the solution here: https://developer.apple.com/documentation/coredata/accessing_data_when_the_store_has_changed

Thanks for providing the solution! I was struggling with this.

WidgetKit doesn't fetch updated data from Core Data when WidgetCenter.shared.reloadAllTimelines() gets called
 
 
Q