Forcing an AppIntent to run in the main app process

In my new app Breaks, I’m in the process of adding interactive widgets and hit a subtle issue right off the bat. To add an interactive widget, you can add a Button to trigger an AppIntent. But in what process does that AppIntent run? Well, it depends.

The best answer I found is from Michael Gorbach (on AppIntents team) who explains it on Mastodon, but I’ll give you the gist here:

  1. If your AppIntent is only in your widget target, it will run in the widget
  2. If your AppIntent is in both your app and widget target, it will run in the app process if the app is running and not suspended. Otherwise, it will run in the widget process.
  3. Same rules above apply whether it’s run from the widget or via Shortcuts
  4. You can force your AppIntent to run in your app process by conforming it to one of a few different protocols

Why does this matter? Well, it might not depending on your setup. For Breaks, this posed a problem since I have a database in my app and to keep things simple, this database isn’t shared with the widget 1. When the user creates a session, the app writes it to the database, as well as to shared UserDefaults, and triggers a reload to widget’s timeline. The widget can then read the current session from UserDefaults and then create the appropriate timeline from there.

Since the database isn’t shared, that means when the AppIntent is run in the widget, it can’t update the database. One option is to have the widget write to the shared UserDefaults and have the app read from there, but that is a bit more complex when we really just want the AppIntent to always run in the main app process, which luckily there are a few simple ways to do. The docs explicitly mention a few options:

If you adopt the LiveActivityIntent or AudioPlaybackIntent protocol, the system runs the app intent in the app’s process.

From the Mastodon thread linked above, it is also mentioned that conforming to ForegroundContinuableIntent works, which did the trick for me. The one caveat is that is not available in widgets, so you’ll have to conform it like so:

@available(iOSApplicationExtension, unavailable)
extension StartSessionIntent: ForegroundContinuableIntent { }

With that, you don’t have to change any other code, and the AppIntent will always run in your main app process. If you’re adopting interactive widgets, make sure to think about the context of that will execute and whether you have specific requirements that might require it to run in a particular process.

1 I could share the database between processes, but that involves a lot of extra complexity and possible issues (see https://swiftpackageindex.com/groue/grdb.swift/v6.20.1/documentation/grdb/databasesharing for an in-depth explanation) that I'd like to avoid.