flutter_app_intents

Weather App Intents Example

This example demonstrates query-based App Intents using a weather application that provides information without opening the app interface. It showcases how to create background data queries with voice responses optimized for Siri.

Features Demonstrated

Query Intents with Voice Responses

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. Background operation without UI requirements

Screenshots

App Interface iOS Shortcuts
Weather App Interface iOS Shortcuts

Quick Start

Screenshots

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

Prerequisites

Run the Example

cd weather
flutter pub get
flutter run

Test the App Intents

  1. Manual Testing: Use the buttons in the app interface to test queries

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

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

  4. Siri Commands:
    • “Get weather from Weather Example”
    • “Check temperature in San Francisco using Weather Example”
    • “What’s the forecast for tomorrow in Weather Example”
    • “Is it raining in New York with Weather Example”
  5. Settings: Go to Settings > Siri & Search > App Shortcuts

Implementation Details

Static Swift Intents

The iOS side defines static query intents in AppDelegate.swift:

@available(iOS 16.0, *)
struct GetCurrentWeatherIntent: AppIntent {
    static var title: LocalizedStringResource = "Get Current Weather"
    static var description = IntentDescription("Get current weather conditions for a location")
    static var isDiscoverable = true
    // Note: No openAppWhenRun - this works in background
    
    @Parameter(title: "Location")
    var location: String?
    
    func perform() async throws -> some IntentResult & ReturnsValue<String> & ProvidesDialog {
        let plugin = FlutterAppIntentsPlugin.shared
        let result = await plugin.handleIntentInvocation(
            identifier: "get_current_weather",
            parameters: ["location": location ?? "current location"]
        )
        
        if let success = result["success"] as? Bool, success {
            let value = result["value"] as? String ?? "Weather information retrieved"
            return .result(
                value: value,
                dialog: IntentDialog(stringLiteral: value)  // Ensures Siri speaks result
            )
        } else {
            let errorMessage = result["error"] as? String ?? "Failed to get weather"
            throw AppIntentError.executionFailed(errorMessage)
        }
    }
}

Flutter Query Handlers

The Flutter side handles data fetching and voice response formatting:

Future<AppIntentResult> _handleCurrentWeather(Map<String, dynamic> parameters) async {
  try {
    final location = parameters['location'] as String? ?? 'current location';
    
    // Simulate weather data fetching
    final weatherData = await _fetchWeatherData(location);
    
    // Log the query for demonstration
    setState(() {
      _queryLog.insert(0, 'Current weather for $location');
    });

    // Donate intent for Siri learning
    await _client.donateIntent('get_current_weather', parameters);

    // Format response optimized for Siri voice output
    final response = _formatCurrentWeatherResponse(weatherData, location);
    
    return AppIntentResult.successful(
      value: response,
      // Note: needsToContinueInApp = false for background queries
      needsToContinueInApp: false,
    );
  } catch (e) {
    return AppIntentResult.failed(
      error: 'Failed to get weather data: $e',
    );
  }
}

Voice Response Formatting

Responses are optimized for Siri speech:

String _formatCurrentWeatherResponse(Map<String, dynamic> data, String location) {
  final temp = data['temperature'];
  final condition = data['condition'];
  final humidity = data['humidity'];
  final wind = data['wind_speed'];
  
  return 'Current weather in $location: $temp degrees and $condition. '
         'Humidity is $humidity percent with winds at $wind miles per hour.';
}

App Shortcuts Provider

The static shortcuts are declared with an AppShortcutsProvider:

@available(iOS 16.0, *)
struct WeatherAppShortcuts: AppShortcutsProvider {
    static var appShortcuts: [AppShortcut] {
        // Current weather shortcut - comprehensive weather information
        AppShortcut(
            intent: GetCurrentWeatherIntent(),
            phrases: [
                "Get weather from \(.applicationName)",
                "Check weather using \(.applicationName)",
                "What's the weather in \(.applicationName)",
                "Current weather with \(.applicationName)"
            ],
            shortTitle: "Weather",
            systemImageName: "cloud.sun"
        )
        // ... other shortcuts
    }
}

Query vs Action/Navigation Intents

This example focuses on query intents that:

Key Differences from Other Examples:

What You’ll Learn

Voice Command Examples

Basic Weather Queries

"Get weather from Weather Example"
"Check weather using Weather Example"
"What's the weather in Weather Example"

Location-Specific Queries

"Get temperature in San Francisco using Weather Example"
"Check weather in New York with Weather Example"
"What's the forecast in Seattle from Weather Example"

Specific Information Queries

"Is it raining in Miami with Weather Example"
"Get forecast for tomorrow using Weather Example"
"Check temperature using Weather Example"

Advanced Features

Parameter Handling

Voice Optimization

Background Operation

Next Steps

This weather example demonstrates the query pattern that’s perfect for:

For action-based intents, see the counter example. For navigation intents, see the navigation example.