This example demonstrates navigation-based App Intents using a Flutter application with multiple pages. It shows how to perform deep linking and app navigation through Siri voice commands and iOS shortcuts.
needsToContinueInApp: trueThis example uses the hybrid approach with:
ios/Runner/AppDelegate.swift)lib/main.dart)Screenshot showing the home page with navigation buttons for Profile, Chat, Search, and Settings
Screenshots showing the different pages: Profile page, Chat page, Search page, and Settings page
Screenshot of Siri responding to “Open my profile in navigation example”
Screenshot of the Shortcuts app showing available navigation shortcuts
Screenshot showing a page opened via voice command with parameters (e.g., “Chat with Alice”)
| App Interface | iOS Shortcuts |
|---|---|
![]() |
![]() |
| Navigation app | iOS Shortcuts app |
cd navigation
flutter pub get
flutter run
Manual Testing: Use the buttons on the home page
iOS Shortcuts: Check the Shortcuts app for available navigation actions
Enable Siri: ⚠️ IMPORTANT - In Shortcuts app, tap “Navigation Example Shortcuts” and toggle ON the Siri switch (it’s OFF by default)
The iOS side defines static navigation intents in AppDelegate.swift:
struct OpenProfileIntent: AppIntent {
static var title: LocalizedStringResource = "Open Profile"
@Parameter(title: "User ID")
var userId: String?
func perform() async throws -> some IntentResult & OpensIntent {
// Bridge to Flutter handler with navigation
let result = await plugin.handleIntentInvocation(
identifier: "open_profile",
parameters: ["userId": userId ?? "current"]
)
return .result(opensIntent: true)
}
}
The Flutter side handles navigation and route parameters:
Future<AppIntentResult> _handleOpenProfileIntent(Map<String, dynamic> parameters) async {
final userId = parameters['userId'] as String? ?? 'current';
if (mounted) {
Navigator.of(context).pushNamed('/profile', arguments: {'userId': userId});
}
await _client.donateIntent('open_profile', parameters);
return AppIntentResult.successful(
value: 'Opening profile for user $userId',
needsToContinueInApp: true, // Critical for navigation intents
);
}
Named routes are configured in MaterialApp:
MaterialApp(
routes: {
'/profile': (context) {
final args = ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>?;
return ProfilePage(userId: args?['userId'] ?? 'current');
},
'/chat': (context) {
final args = ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>?;
return ChatPage(contactName: args?['contactName'] ?? 'Unknown');
},
// ... more routes
},
)
OpensIntent return type for navigationneedsToContinueInApp: true for proper app openingThis example focuses on navigation intents that:
OpensIntent return type in SwiftneedsToContinueInApp: true in FlutterFor action intents that perform operations without navigation, see the counter example.