Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Widget changes not syncing to main app #1

Open
codefruitio opened this issue Aug 30, 2023 · 23 comments
Open

Widget changes not syncing to main app #1

codefruitio opened this issue Aug 30, 2023 · 23 comments

Comments

@codefruitio
Copy link

Hello! Thanks for this demo project. I've noticed that when you modify a task through the widget, the change is not reflected in the main app until the app is restarted. I've seen the same behavior in my own apps using Interactive Widgets with SwiftData. Is this just expected behavior for the SwiftData beta currently?

@JuniperPhoton
Copy link
Owner

Hi. Do you fetch your data when the app is in the foreground like monitoring the ScenePhase and fetch data when its value changes to .foreground? I haven’t encountered this issue before in iOS 17 Beta 6.

@codefruitio
Copy link
Author

I'm not doing anything special in the app besides the @query command in the view to pull from the ModelContainer. When I used your app, I had the same behavior though. Where when I completed a task, the main app didnt reflect the changes when brought into the foreground unless I killed and reopened the entire app

@JuniperPhoton
Copy link
Owner

Oh I reproduce this issue and seems that fetching and saving data manually won't work. However today Apple released the new Beta 8, I haven't have time to try it yet because I am on my trip, can you share the result after upgrading Xcode 15 beta and try?

@codefruitio
Copy link
Author

codefruitio commented Aug 31, 2023 via email

@JuniperPhoton
Copy link
Owner

I think we should fire a feedback to Apple.
I tried Core Data and the issue exists and it seems that the changes won't be written to the disk until the app is killed. Also, I can only try on the simulator for now, but as far as I know the simulator has different behaviors regarding to saving UserDefaults to the disk.

I also tried changing the value in UserDefaults and the value is updated as soon as I tap the widget to bring the app to the foreground.

@codefruitio
Copy link
Author

Good idea. I've never filed feedback with them before. Is that something you'd like to do? I'd be happy to figure it out otherwise

@JuniperPhoton
Copy link
Owner

JuniperPhoton commented Sep 1, 2023

I would. But I find out that the last feedbacks I submitted are still in the “open” state. But it’s still better to file a feedback though.

By the way, did you test on real device or just use the simulator like me?

@codefruitio
Copy link
Author

codefruitio commented Sep 1, 2023 via email

@codefruitio
Copy link
Author

I have tested on a simulator and physical device with the same result. Feedback filed.. FB13107596

@JuniperPhoton
Copy link
Owner

Thanks. I will also report this issue later.

@JuniperPhoton
Copy link
Owner

I have tried a method to avoid this issue.

When fetching data: use a dedicated ModelContext to fetch data. By default the @Query Marco uses MainContext to fetch data.

let context = ModelContext(sharedModelContainer)
let items = (try? context.fetch(FetchDescriptor<Item>())) ?? []
self.items = items

And it's ok to update the data in an arbitrary context in Widget extension.

The reason I tried this method is that my app MyerList has adopted this interactive widget feature before I make this demo and article and it works as expected. Though it uses Core Data instead of SwiftData, but I always uses the background context to update data and update data using a subscription-notification style.

After all, I think it's still the issue of SwiftData (even in Beta 8). And I will still keep my eye on this issue.

@iandundas
Copy link

iandundas commented Sep 20, 2023

In my own app this has also been driving me bananas. Searched on GitHub for what others are doing and found this repo. On release version of Xcode 15, the issue remains unfortunately :(

(posting for others who are googling, because there's not much out there).

See video:

CleanShot.2023-09-20.at.07.50.14-converted.mp4

@iandundas
Copy link

I'm very curious what Sindre is doing, he has build basically this same app (apparently in SwiftData)

app

Here he has this behaviour working:

RPReplay_Final1695189425.MP4

@JuniperPhoton
Copy link
Owner

There is a workaround posted before(see my previous comment). It's fine by me for now because I won't use SwiftData for production because supporting only iOS 17 will have very few users.

@iandundas
Copy link

iandundas commented Sep 20, 2023

Indeed that workaround works - thanks for fast reply. It's a shame to abandon @Query - it seems kinda fundamental..!

Unfortunately, fetching Models from a background context means they can't be used in the UI :(

By default the @query Marco uses MainContext to fetch data.

I don't think this can be changed - that might actually be a workaround if it could. A shame.

And it's ok to update the data in an arbitrary context in Widget extension.

Correct, it doesn't seem to matter which context you use in the Widget. Also saving (or not) doesn't make a difference.

@iandundas
Copy link

Correction:

Unfortunately, fetching Models from a background context means they can't be used in the UI :(

Actually, this works well enough. The fetched models can still be used in the UI without issue.

CleanShot 2023-09-20 at 09 50 58@2x

@JuniperPhoton
Copy link
Owner

Actually you can use modelContext modifier to set the context to be used in @Query. Even if you are creating your own ModelContext there is no need to abandon the @Query Marco if it's off this case.

But the key of this workaround is to create a ModelContext each time you fetch items. If you reuse a ModelContext created before you still can't get the right results.

@iandundas
Copy link

Thanks so much, was banging my head on that throughout the betas & was amazed it released with that bug. Now I can progress! 🙌

@codefruitio
Copy link
Author

This workaround got my app working as well. Thanks @JuniperPhoton

@stephenfung98
Copy link

Can I get a code sample on how to use this workaround?

I put .modelContext(ModelContext(sharedModelContainer)) on the content view

and the following in onChange(of: scenePhase):

            guard let modelContainer = try? ModelContainer(for: Item.self) else { return }

            let descriptor = FetchDescriptor<Item>()
            let fetchedContributions = try? ModelContext(modelContainer).fetch(descriptor)
            
            for item in items {
                if fetchedContributions?.first(where: { $0.id == item.id })?.completedDate == nil && item.completedDate != nil {
                    toggleComplete(item: item)
                } else if fetchedContributions?.first(where: { $0.id == item.id })?.completedDate != nil && item.completedDate == nil {
                    toggleComplete(item: item)
                }
            }

but it seems to have multiple ModelContext as the items sometimes do not save.

thanks :)

@julianfbeck
Copy link

Thanks for the idea of a seperate context, that works for me when fetching data, but how would i then prevent another cloudkit instance from beeing created if i use cloudkkit?
CoreData: error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate resetAfterError:andKeepContainer:](585): <NSCloudKitMirroringDelegate: 0x600003b42ca0> - resetting internal state after error: Error Domain=NSCocoaErrorDomain Code=134410 "CloudKit setup failed because there is another instance of this persistent store actively syncing with CloudKit in this process."

@ramzesenok
Copy link

I did something nasty but it seems to work fine :)
In a nutshell – yes, SwiftData won't pull the changes from Widget when the app enters foreground but it will do so if you try to change the corresponding item in the app. So what I did is following:

@Environment(\.scenePhase) var scenePhase

var body: some View {
    ...
        .onChange(of: scenePhase) { _, newValue in
            if case .active = newValue {
                items.forEach { $0.title = $0.title }
            }
        }
}

Setting the same title won't change a thing but it will force SwiftData to pull the changes and apply them. That being said I've only a bunch of items at any given time, not sure how it's gonna work with hundreds or more but at least it should help those who have only a few at a given screen

@bradhowes
Copy link

@ramzesenok your solution worked for me! I'm considering SwiftData to manage app state that is shared with AUv3 app extension. In my demo using 2 apps that share the same app group, I was only seeing additions and deletions, but not property changes. The forEach hack did the trick to update the view. Wow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants