I have a Flutter application where I use GetX for state management. In the project, I want to integrate Siri. When I say "Open Homework MyApp" to Siri, I want the app to open and navigate to the homework page. To achieve this, I used intents and shortcuts on Apple's side, and I tried to handle navigation using MethodChannel.
When I follow the process through debugging, this is how the scenario unfolds: I open Siri and say the phrase, I can track the prints and breakpoints I've set, and I see the log as shown below. However, the app doesn't open and navigate immediately. But when I manually open the app, the navigation starts.
What I want to achieve is for the app to open and navigate to the relevant page as soon as the phrase is said. I'm including the intent, shortcut, app delegate, and MethodChannel code I've written on the Flutter side.
AppDelegate:
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
private let navigationChannel = "com.example.myapp/navigation"
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
setupSiriShortcuts(application: application)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func setupSiriShortcuts(application: UIApplication) {
let activity = NSUserActivity(activityType: "com.example.myapp.openhomework")
activity.title = "Open Homework Page"
activity.isEligibleForSearch = true
activity.isEligibleForPrediction = true
activity.isEligibleForPublicIndexing = true
activity.persistentIdentifier = NSUserActivityPersistentIdentifier("com.example.myapp.openhomework")
activity.userInfo = ["intentName": "openHomeworkPage"]
self.userActivity = activity
application.userActivity = activity
}
override func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if userActivity.activityType == "com.example.myapp.openhomework",
let viewController = window.rootViewController as? FlutterViewController {
let navigationChannel = FlutterMethodChannel(name: self.navigationChannel, binaryMessenger: viewController.binaryMessenger)
application.windows.first?.makeKeyAndVisible()
navigationChannel.invokeMethod("openHomeworkPage", arguments: nil)
}
return true
}
}
Intent:
import Foundation
import AppIntents
import Flutter
@available(iOS 16.0, *)
struct OpenHomeworkPageIntent: AppIntent {
static let title: LocalizedStringResource = "Open Homework MyApp"
func perform() async throws -> some IntentResult & ProvidesDialog {
print("Perform function started.")
guard let windowScene = await UIApplication.shared.connectedScenes.first as? UIWindowScene,
let viewController = await windowScene.windows.first?.rootViewController as? FlutterViewController else {
return .result(dialog: "Error: App not started.")
}
let navigationChannel = await FlutterMethodChannel(name: "com.example.myapp.openhomework", binaryMessenger: viewController.binaryMessenger)
// Dispatch the invokeMethod call on the main thread
DispatchQueue.main.async {
print("Invoking openHomeworkPage method.")
navigationChannel.invokeMethod("openHomeworkPage", arguments: nil)
}
print("Perform function completed.")
return .result(dialog: "Homework page open.")
}
}
Shortcut:
import Foundation
import AppIntents
@available(iOS 16.0, *)
struct OpenHomeworkPageShortcuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: OpenHomeworkPageIntent(),
phrases: [
"Open homework \(.applicationName)"
],
shortTitle: "Homework open"
)
}
}
MethodChannel in flutter:
static const platform = MethodChannel('com.example.myapp.openhomework');
@override
void initState() {
platform.setMethodCallHandler(_handleMethodCall);
}
Future<void> _handleMethodCall(MethodCall call) async {
print("methodChannel: ${call.method} ${call.arguments}");
switch (call.method) {
case 'openHomeworkPage':
try {
print("Navigating to HomeworkScreen...");
Get.offNamed(AppNavigation.HOMEWORK);
} catch (e) {
print("Error navigating to HomeworkScreen: $e");
}
break;
default:
print('Unknown method: ${call.method}');
}
}
Debug Log:
-[WFBackgroundShortcutRunner runWorkflowWithDescriptor:request:inEnvironment:runningContext:completion:] Disabling privacy prompts because this is an on-the-fly shortcut.
Starting shortcut run: <WFWorkflow: 0xa660a9180, name: Open homework Metodbox , actions: 1>, request: <WFSiriWorkflowRunRequest: 0xa66114780, runSource: siri, input: no, presentationMode: Siri, output behavior: Ignore, automationType: (null), allowsHandoff: no, allowsDialogNotifications: no>
Running action <WFLinkAction: 0x102a7caf0, identifier: com.bkmobil.metodboxxx.OpenHomeworkPageIntent>
Action finished running <WFLinkAction: 0x102a7caf0, identifier: com.bkmobil.metodboxxx.OpenHomeworkPageIntent>.
Shortcut <private> has finished running with output: (null).
-[WFBackgroundShortcutRunner callWorkflowRunningCompletionBlockWithResult:] Workflow Did Finish: Calling Completion Block
-[WFBackgroundShortcutRunner listener:shouldAcceptNewConnection:]_block_invoke XPC connection invalidated
-[WFBackgroundShortcutRunnerStateMachine invalidateWithReason:] connection invalidated/interrupted while finishing shortcut or exiting runner. Exiting should already be in process, not transitioning.
-[WFBackgroundShortcutRunner unaliveProcess]_block_invoke_2 Exiting process
flutter: methodChannel: openHomeworkPage null
flutter: Navigating to HomeworkScreen...
[GETX] REPLACE ROUTE /main
[GETX] NEW ROUTE /splash
I tried deeplink instead of methodChannel, I tried to do it directly with appIntents, but I finally decided against the shortcut. What I want to achieve is for the app to open and navigate to the relevant page as soon as the phrase is said.
Flutter’s engine your trying to access from a shortcut is only spawned for the occasion of the application launch. Shortucts, by default, doesn’t launch the application. You can add
static var openAppWhenRun: Bool = true
field to your AppIntent, which will make sure your app is started with the shortcut, but even then, the Flutter engine does need some time to setup before it can receive messages from Swift.
I’d suggest using a plugin like intelligence to handle shortcuts complexities for you.