flutter_app_intents

Navigation App Intents Example

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.

Features Demonstrated

Key Concepts

Architecture

This example uses the hybrid approach with:

  1. Static Swift App Intents (ios/Runner/AppDelegate.swift)
  2. Flutter handlers (lib/main.dart)
  3. Bridge communication via the plugin
  4. Flutter navigation using named routes

Screenshots

App Interface

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

Siri Navigation

Screenshot of Siri responding to “Open my profile in navigation example”

iOS Shortcuts for Navigation

Screenshot of the Shortcuts app showing available navigation shortcuts

Deep Linking in Action

Screenshot showing a page opened via voice command with parameters (e.g., “Chat with Alice”)

Screenshots

App Interface iOS Shortcuts
Counter App Interface iOS Shortcuts
Navigation app iOS Shortcuts app

Quick Start

Prerequisites

Run the Example

cd navigation
flutter pub get
flutter run

Test the App Intents

  1. Manual Testing: Use the buttons on the home page

  2. iOS Shortcuts: Check the Shortcuts app for available navigation actions

  3. Enable Siri: ⚠️ IMPORTANT - In Shortcuts app, tap “Navigation Example Shortcuts” and toggle ON the Siri switch (it’s OFF by default)

  4. Siri Commands:
    • “Open my profile in navigation example”
    • “Chat with Alice using navigation example”
    • “Search for photos in navigation example”
    • “Open settings with navigation example”
  5. Settings: Go to Settings > Siri & Search > App Shortcuts

Implementation Details

Static Swift Intents

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)
    }
}

Flutter Navigation Handlers

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
  );
}

Route Configuration

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
  },
)

What You’ll Learn

This example focuses on navigation intents that:

For action intents that perform operations without navigation, see the counter example.

Next Steps