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 WidgetKit
import SwiftUI
import CoreData
struct 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")
}
}
@main
struct 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 UIKit
import CoreData
import WidgetKit
class 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 methods
extension 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