Kaily Flutter SDK

pub package License: MIT

A Flutter SDK for integrating Kaily AI chatbot with dynamic tool registration and execution capabilities. This SDK provides a WebView-based implementation that works across all Flutter platforms.

✨ Features

  • 🌐 WebView-Based Integration: Pure Flutter implementation using WebView
  • πŸ› οΈ Dynamic Tool Registration: Register Dart functions that the AI can call in real-time
  • πŸ‘€ User Management: Complete user context and authentication support
  • 🎨 Customizable Appearance: Full theming and styling options
  • πŸ“‘ Real-time Events: Comprehensive event system for monitoring AI interactions
  • πŸ“± Cross-Platform: Works on iOS, Android, Web, and Desktop

πŸš€ Quick Start

1. Installation

Add this to your pubspec.yaml:

dependencies:
  kaily_flutter_sdk: ^1.0.0

Then run:

flutter pub get

2. Basic Setup

import 'package:kaily_flutter_sdk/kaily_flutter_sdk.dart';

void main() {
  runApp(const KailyExampleApp());
}

class KailyExampleApp extends StatelessWidget {
  const KailyExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Kaily Flutter SDK Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  bool _isInitialized = false;
  bool _isLoading = false;
  String? _error;
  final List<String> _logs = [];
  final List<Map<String, dynamic>> _cartItems = [];

  // Example user data
  final _user = const KailyUser(
    id: 'user_123',
    name: 'John Doe',
    email: 'john.doe@example.com',
    attributes: {'tier': 'premium', 'location': 'San Francisco'},
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Kaily Flutter SDK Example'),
        actions: [
          IconButton(icon: const Icon(Icons.info), onPressed: _showInfo),
        ],
      ),
      body: Column(
        children: [
          // Status Section
          Container(
            width: double.infinity,
            padding: const EdgeInsets.all(16),
            color: _getStatusColor(),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'Status: ${_getStatusText()}',
                  style: const TextStyle(
                    color: Colors.white,
                    fontSize: 16,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                if (_error != null) ...[
                  const SizedBox(height: 8),
                  Text(
                    'Error: $_error',
                    style: const TextStyle(color: Colors.white, fontSize: 12),
                  ),
                ],
              ],
            ),
          ),

          // Control Section
          Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              children: [
                Row(
                  children: [
                    Expanded(
                      child: ElevatedButton(
                        onPressed: _isLoading ? null : _initializeKaily,
                        child: _isLoading
                            ? const SizedBox(
                                height: 20,
                                width: 20,
                                child: CircularProgressIndicator(strokeWidth: 2),
                              )
                            : const Text('Initialize Kaily'),
                      ),
                    ),
                    const SizedBox(width: 8),
                    ElevatedButton(
                      onPressed: _isInitialized ? _showKailyWidget : null,
                      child: const Text('Open Chat'),
                    ),
                  ],
                ),
                const SizedBox(height: 16),

                // Cart Items Display
                Container(
                  width: double.infinity,
                  padding: const EdgeInsets.all(16),
                  decoration: BoxDecoration(
                    border: Border.all(color: Colors.grey),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'Shopping Cart (${_cartItems.length} items)',
                        style: Theme.of(context).textTheme.titleMedium,
                      ),
                      const SizedBox(height: 8),
                      if (_cartItems.isEmpty)
                        const Text('Cart is empty')
                      else
                        ..._cartItems.map(
                          (item) => Padding(
                            padding: const EdgeInsets.symmetric(vertical: 2),
                            child: Text(
                              'β€’ ${item['name']} (Qty: ${item['quantity']}) - \$${item['price']}',
                              style: const TextStyle(fontSize: 12),
                            ),
                          ),
                        ),
                    ],
                  ),
                ),
              ],
            ),
          ),

          // Logs Section
          Expanded(
            child: Container(
              margin: const EdgeInsets.all(16),
              padding: const EdgeInsets.all(16),
              decoration: BoxDecoration(
                border: Border.all(color: Colors.grey),
                borderRadius: BorderRadius.circular(8),
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    children: [
                      Text(
                        'Logs',
                        style: Theme.of(context).textTheme.titleMedium,
                      ),
                      const Spacer(),
                      TextButton(
                        onPressed: () => setState(() => _logs.clear()),
                        child: const Text('Clear'),
                      ),
                    ],
                  ),
                  const Divider(),
                  Expanded(
                    child: ListView.builder(
                      itemCount: _logs.length,
                      itemBuilder: (context, index) {
                        final log = _logs[index];
                        return Padding(
                          padding: const EdgeInsets.symmetric(vertical: 2),
                          child: Text(
                            log,
                            style: const TextStyle(
                              fontSize: 12,
                              fontFamily: 'monospace',
                            ),
                          ),
                        );
                      },
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  Color _getStatusColor() {
    if (_error != null) return Colors.red;
    if (_isInitialized) return Colors.green;
    return Colors.orange;
  }

  String _getStatusText() {
    if (_error != null) return 'Error';
    if (_isInitialized) return 'Initialized';
    if (_isLoading) return 'Initializing...';
    return 'Not Initialized';
  }

  Future<void> _initializeKaily() async {
    setState(() {
      _isLoading = true;
      _error = null;
    });

    _addLog('Initializing Kaily SDK...');

    try {
      // Initialize the SDK
      await KailySDK.instance.initialize(
        KailyConfig.withDefaults(
          token: 'your-kaily-token',
          user: _user,
        ).copyWith(debugMode: true),
      );

      _addLog('SDK initialized successfully');

      // Register example tools
      await _registerTools();

      // Set initial context
      await KailySDK.instance.setContext({
        'current_page': 'home',
        'cart_count': _cartItems.length,
        'user_tier': 'premium',
      });

      _addLog('Context set successfully');

      setState(() {
        _isInitialized = true;
        _isLoading = false;
      });

      _addLog('Kaily is ready to use!');
      _setupEventListening();

    } catch (e) {
      setState(() {
        _error = e.toString();
        _isLoading = false;
      });
      _addLog('Initialization failed: $e');
    }
  }

  Future<void> _registerTools() async {
    // Register all tools at once using the new registerTools method
    await KailySDK.instance.registerTools([
      KailyTool(
        name: 'add_to_cart',
        description: 'Add a product to the shopping cart',
        parameters: [
          const KailyToolParameter(
            name: 'product_id',
            type: 'string',
            description: 'The ID of the product to add',
            required: true,
          ),
          const KailyToolParameter(
            name: 'product_name',
            type: 'string',
            description: 'The name of the product',
            required: true,
          ),
          const KailyToolParameter(
            name: 'price',
            type: 'number',
            description: 'The price of the product',
            required: true,
          ),
          const KailyToolParameter(
            name: 'quantity',
            type: 'number',
            description: 'The quantity to add (default: 1)',
            defaultValue: 1,
          ),
        ],
        handler: _handleAddToCart,
      ),
      KailyTool(
        name: 'get_cart_items',
        description: 'Get all items currently in the shopping cart',
        handler: _handleGetCartItems,
      ),
      KailyTool(
        name: 'remove_from_cart',
        description: 'Remove a product from the shopping cart',
        parameters: [
          const KailyToolParameter(
            name: 'product_id',
            type: 'string',
            description: 'The ID of the product to remove',
            required: true,
          ),
        ],
        handler: _handleRemoveFromCart,
      ),
      KailyTool(
        name: 'get_user_info',
        description: 'Get information about the current user',
        handler: _handleGetUserInfo,
      ),
    ]);

    _addLog('Registered ${KailySDK.instance.getRegisteredTools().length} tools at once');
  }

  Future<KailyToolResult> _handleAddToCart(Map<String, dynamic> parameters) async {
    try {
      final productId = parameters['product_id'] as String;
    final productName = parameters['product_name'] as String;
      final price = (parameters['price'] as num).toDouble();
      final quantity = (parameters['quantity'] as num?)?.toInt() ?? 1;

      // Simulate adding to cart
      final cartItem = {
        'id': productId,
        'name': productName,
        'price': price,
        'quantity': quantity,
        'added_at': DateTime.now().toIso8601String(),
      };

      setState(() {
        _cartItems.add(cartItem);
      });

      // Update context
      await KailySDK.instance.setContext({
        'current_page': 'home',
        'cart_count': _cartItems.length,
        'user_tier': 'premium',
      });

      _addLog('Added to cart: $productName (x$quantity)');

    return KailyToolResult.success(
      data: {
        'success': true,
          'product_id': productId,
          'product_name': productName,
          'quantity': quantity,
          'cart_count': _cartItems.length,
        'message': 'Successfully added $productName to your cart!',
      },
    );
    } catch (e) {
      _addLog('Add to cart failed: $e');
      return KailyToolResult.failure(
        error: 'Failed to add product to cart: $e',
      );
    }
  }

  Future<KailyToolResult> _handleGetCartItems(Map<String, dynamic> parameters) async {
    try {
      _addLog('Retrieved cart items (${_cartItems.length} items)');

      return KailyToolResult.success(
        data: {
          'items': _cartItems,
          'count': _cartItems.length,
          'total_value': _cartItems.fold<double>(
            0,
            (sum, item) => sum + (item['price'] as double) * (item['quantity'] as int),
          ),
        },
      );
    } catch (e) {
      _addLog('Get cart items failed: $e');
      return KailyToolResult.failure(error: 'Failed to get cart items: $e');
    }
  }

  Future<KailyToolResult> _handleRemoveFromCart(Map<String, dynamic> parameters) async {
    try {
      final productId = parameters['product_id'] as String;

      final index = _cartItems.indexWhere((item) => item['id'] == productId);
      if (index == -1) {
        return KailyToolResult.failure(error: 'Product not found in cart');
      }

      final removedItem = _cartItems.removeAt(index);

      // Update context
      await KailySDK.instance.setContext({
        'current_page': 'home',
        'cart_count': _cartItems.length,
        'user_tier': 'premium',
      });

      setState(() {});

      _addLog('Removed from cart: ${removedItem['name']}');

      return KailyToolResult.success(
        data: {
          'success': true,
          'removed_product': removedItem,
          'cart_count': _cartItems.length,
          'message': 'Successfully removed ${removedItem['name']} from your cart!',
        },
      );
    } catch (e) {
      _addLog('Remove from cart failed: $e');
      return KailyToolResult.failure(
        error: 'Failed to remove product from cart: $e',
      );
    }
  }

  Future<KailyToolResult> _handleGetUserInfo(Map<String, dynamic> parameters) async {
    try {
      _addLog('Retrieved user info for: ${_user.name}');

      return KailyToolResult.success(
        data: {
          'user': _user.toJson(),
          'preferences': {
            'theme': 'light',
            'notifications': true,
            'language': 'en',
          },
          'stats': {
            'total_orders': 42,
            'favorite_category': 'electronics',
            'member_since': '2023-01-15',
          },
        },
      );
    } catch (e) {
      _addLog('Get user info failed: $e');
      return KailyToolResult.failure(error: 'Failed to get user info: $e');
    }
  }

  void _setupEventListening() {
    _addLog('Setting up event listening...');

    KailySDK.instance.eventStream.listen(
      (event) {
        _addLog('Event received: ${event.type.name}');
        _addLog('Event data: ${event.data}');
        _addLog('Event source: ${event.source}');

        switch (event.type) {
          case KailyEventType.conversationLoaded:
            _addLog('Conversation loaded successfully');
            break;
          case KailyEventType.conversationFailedToLoad:
            _addLog('Conversation failed to load: ${event.error}');
            break;
          case KailyEventType.toolCall:
            final toolName = event.data?['tool_name'];
            _addLog('Tool called: $toolName');
            break;
          case KailyEventType.toolResult:
            final toolName = event.data?['tool_name'];
            final success = event.data?['success'];
            _addLog('Tool result: $toolName (${success ? 'success' : 'failed'})');
            break;
          case KailyEventType.error:
            _addLog('Error: ${event.error}');
            break;
          default:
            _addLog('Other event: ${event.type.name}');
        }
      },
      onError: (error) {
        _addLog('Event stream error: $error');
      },
    );

    _addLog('Event listening setup complete');
  }

  void _showKailyWidget() {
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => Scaffold(
          appBar: AppBar(title: const Text('Kaily Chat')),
          body: KailyWidget(
            config: KailyConfig.withDefaults(
              token: 'your-kaily-token',
              user: _user,
            ).copyWith(debugMode: true),
            onConversationLoaded: () {
              _addLog('Widget: Conversation loaded');
            },
            onConversationFailedToLoad: (error) {
              _addLog('Widget: Conversation failed to load - $error');
            },
            onTelemetryEvent: (eventType, payload) {
              _addLog('Widget: Telemetry - $eventType');
            },
            onEvent: (event) {
              _addLog('Widget: Event - ${event.type.name}');
            },
          ),
        ),
      ),
    );
  }

  void _showInfo() {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Kaily Flutter SDK Example'),
        content: const Text(
          'This example demonstrates:\n\n'
          'β€’ SDK initialization\n'
          'β€’ Tool registration (single or multiple tools at once)\n'
          'β€’ Event handling\n'
          'β€’ WebView-based chat widget\n'
          'β€’ Real-time JavaScript-Dart communication\n\n'
          'New Feature: registerTools() method:\n'
          'β€’ Single tool: registerTools(KailyTool(...))\n'
          'β€’ Multiple tools: registerTools([KailyTool(...), ...])\n\n'
          'Try asking the AI to:\n'
          'β€’ "Add iPhone to my cart for \$999"\n'
          'β€’ "Show me what\'s in my cart"\n'
          'β€’ "Remove the iPhone from my cart"\n'
          'β€’ "What\'s my user information?"',
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: const Text('OK'),
          ),
        ],
      ),
    );
  }

  void _addLog(String message) {
    final timestamp = DateTime.now().toString().substring(11, 19);
    setState(() {
      _logs.add('[$timestamp] $message');
    });

    // Keep only last 100 logs
    if (_logs.length > 100) {
      _logs.removeAt(0);
    }
  }
}

πŸ› οΈ Advanced Usage

Comprehensive Tool Registration

The example above demonstrates a complete e-commerce shopping cart implementation with multiple tools. Here's how to create your own tools:

await KailySDK.instance.registerTools([
  KailyTool(
    name: 'add_to_cart',
    description: 'Add a product to the shopping cart',
    parameters: [
      const KailyToolParameter(
        name: 'product_id',
        type: 'string',
        description: 'The ID of the product to add',
        required: true,
      ),
      const KailyToolParameter(
        name: 'product_name',
        type: 'string',
        description: 'The name of the product',
        required: true,
      ),
      const KailyToolParameter(
        name: 'price',
        type: 'number',
        description: 'The price of the product',
        required: true,
      ),
      const KailyToolParameter(
        name: 'quantity',
        type: 'number',
        description: 'The quantity to add (default: 1)',
        defaultValue: 1,
      ),
    ],
    handler: _handleAddToCart,
  ),
  KailyTool(
    name: 'get_cart_items',
    description: 'Get all items currently in the shopping cart',
    handler: _handleGetCartItems,
  ),
  KailyTool(
    name: 'remove_from_cart',
    description: 'Remove a product from the shopping cart',
    parameters: [
      const KailyToolParameter(
        name: 'product_id',
        type: 'string',
        description: 'The ID of the product to remove',
        required: true,
      ),
    ],
    handler: _handleRemoveFromCart,
  ),
  KailyTool(
    name: 'get_user_info',
    description: 'Get information about the current user',
    handler: _handleGetUserInfo,
  ),
]);

Tool Handler Implementation

Each tool handler should return a KailyToolResult:

Future<KailyToolResult> _handleAddToCart(Map<String, dynamic> parameters) async {
  try {
    final productId = parameters['product_id'] as String;
    final productName = parameters['product_name'] as String;
    final price = (parameters['price'] as num).toDouble();
    final quantity = (parameters['quantity'] as num?)?.toInt() ?? 1;

    // Your business logic here
    // Update UI state, database, etc.

    return KailyToolResult.success(
      data: {
        'success': true,
        'product_id': productId,
        'product_name': productName,
        'quantity': quantity,
        'message': 'Successfully added $productName to your cart!',
      },
    );
  } catch (e) {
    return KailyToolResult.failure(
      error: 'Failed to add product to cart: $e',
    );
  }
}

Event Handling

The example demonstrates comprehensive event handling with logging and UI updates:

void _setupEventListening() {
  KailySDK.instance.eventStream.listen(
    (event) {
      _addLog('Event received: ${event.type.name}');
      _addLog('Event data: ${event.data}');
      _addLog('Event source: ${event.source}');

      switch (event.type) {
        case KailyEventType.conversationLoaded:
          _addLog('Conversation loaded successfully');
          break;
        case KailyEventType.conversationFailedToLoad:
          _addLog('Conversation failed to load: ${event.error}');
          break;
        case KailyEventType.toolCall:
          final toolName = event.data?['tool_name'];
          _addLog('Tool called: $toolName');
          break;
        case KailyEventType.toolResult:
          final toolName = event.data?['tool_name'];
          final success = event.data?['success'];
          _addLog('Tool result: $toolName (${success ? 'success' : 'failed'})');
          break;
        case KailyEventType.error:
          _addLog('Error: ${event.error}');
          break;
        default:
          _addLog('Other event: ${event.type.name}');
      }
    },
    onError: (error) {
      _addLog('Event stream error: $error');
    },
  );
}

Widget Event Callbacks

The KailyWidget also supports direct event callbacks:

KailyWidget(
  config: config,
  onConversationLoaded: () {
    print('Widget: Conversation loaded');
  },
  onConversationFailedToLoad: (error) {
    print('Widget: Conversation failed to load - $error');
  },
  onTelemetryEvent: (eventType, payload) {
    print('Widget: Telemetry - $eventType');
  },
  onEvent: (event) {
    print('Widget: Event - ${event.type.name}');
  },
)

Context Management

The example shows dynamic context updates that keep the AI informed about your app's current state:

// Initial context setup
await KailySDK.instance.setContext({
  'current_page': 'home',
  'cart_count': _cartItems.length,
  'user_tier': 'premium',
});

// Update context when cart changes
await KailySDK.instance.setContext({
  'current_page': 'home',
  'cart_count': _cartItems.length,
  'user_tier': 'premium',
});

Context is automatically updated in the example when:

  • Items are added to the cart
  • Items are removed from the cart
  • The user navigates to different pages

This ensures the AI always has current information about your app's state.

UI Features and Debugging

The example includes several UI features that make development and debugging easier:

Status Indicators

  • Color-coded status bar: Green (initialized), Orange (loading), Red (error)
  • Real-time status updates: Shows current SDK state
  • Error display: Clear error messages when initialization fails

Comprehensive Logging

  • Timestamped logs: Each log entry includes a timestamp
  • Real-time updates: Logs appear immediately as events occur
  • Log management: Automatic cleanup (keeps last 100 logs)
  • Clear functionality: Users can clear logs manually

Interactive Elements

  • Loading states: Buttons show loading indicators during initialization
  • Disabled states: Buttons are disabled when appropriate
  • Info dialog: Helpful information about the example features

Live Data Display

  • Shopping cart: Real-time display of cart items
  • Dynamic updates: UI updates immediately when tools are called
  • State management: Proper Flutter state management throughout

Custom Appearance

Customize the chat widget appearance:

KailyWidget(
  config: KailyConfig.withDefaults(
    token: 'your-token',
    user: user,
  ).copyWith(
    appearance: KailyAppearance(
      primaryColor: Colors.blue,
      backgroundColor: Colors.white,
      title: 'My Assistant',
      showHeader: true,
    ),
  ),
)

🎯 Example App Features

The comprehensive example app demonstrates all major SDK features:

What You Can Try

Once the SDK is initialized, you can ask the AI to:

  • "Add iPhone to my cart for $999" - Demonstrates tool calling with parameters
  • "Show me what's in my cart" - Shows data retrieval tools
  • "Remove the iPhone from my cart" - Demonstrates item removal
  • "What's my user information?" - Shows user data access
  • "Add 2 laptops to my cart for $1500 each" - Demonstrates quantity handling

Key Features Demonstrated

  • βœ… SDK Initialization with error handling and loading states
  • βœ… Multiple Tool Registration with comprehensive parameters
  • βœ… Real-time Event Handling with detailed logging
  • βœ… Dynamic Context Updates that keep AI informed
  • βœ… UI State Management with live updates
  • βœ… Error Handling with user-friendly messages
  • βœ… Debug Mode with comprehensive logging
  • βœ… Shopping Cart Logic as a practical business use case

Example App Structure

HomePage
β”œβ”€β”€ Status Bar (color-coded status)
β”œβ”€β”€ Control Buttons (Initialize/Open Chat)
β”œβ”€β”€ Shopping Cart Display (live data)
└── Logs Section (real-time debugging)

πŸ“± Platform Setup

Required Permissions

The Kaily Flutter SDK requires specific permissions for WebView functionality and voice features. No manual plugin registration is needed - Flutter automatically handles this when you add the SDK dependency.

πŸ”§ Plugin Registration

Automatic Setup: The SDK automatically includes and registers the following plugins:

  • flutter_inappwebview - For WebView functionality
  • permission_handler - For microphone permissions

Flutter will automatically generate the required plugin registration files when you run flutter pub get.

πŸ“± Android Setup

Add the following permissions to android/app/src/main/AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Required permissions for WebView functionality -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

    <!-- Required for voice features (microphone access) -->
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

    <!-- WebView hardware acceleration -->
    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />

    <application
        android:label="your_app_name"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher"
        android:networkSecurityConfig="@xml/network_security_config"
        android:enableOnBackInvokedCallback="true">
        <!-- Your app configuration -->
    </application>
</manifest>

Important Notes for Android:

  • The INTERNET permission is required for WebView to load web content
  • RECORD_AUDIO and MODIFY_AUDIO_SETTINGS are required for voice features
  • The SDK will automatically request microphone permission when needed

🍎 iOS Setup

Add the following permissions to ios/Runner/Info.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <!-- Your existing app configuration -->

    <!-- Required for WebView network access -->
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
        <key>NSAllowsArbitraryLoadsInWebContent</key>
        <true/>
    </dict>

    <!-- Required for microphone access (voice features) -->
    <key>NSMicrophoneUsageDescription</key>
    <string>This app uses the microphone for voice interactions with Kaily AI assistant.</string>

    <!-- Optional: For speech recognition -->
    <key>NSSpeechRecognitionUsageDescription</key>
    <string>This app uses speech recognition for voice commands with Kaily AI assistant.</string>
</dict>
</plist>

Important Notes for iOS:

  • NSAppTransportSecurity allows WebView to load HTTP content (required for some web services)
  • NSMicrophoneUsageDescription is required for microphone access - customize the description for your app
  • NSSpeechRecognitionUsageDescription is optional but recommended for speech features
  • The SDK will automatically request microphone permission when needed

🌐 Web Setup

For Flutter Web, no additional permissions are required. The browser will handle microphone permissions automatically when the user interacts with voice features.

πŸ–₯️ Desktop Setup

For Flutter Desktop (Windows, macOS, Linux), no additional permissions are required. The system will handle microphone permissions automatically.

🎀 Voice Features Integration

The SDK automatically handles microphone permission requests when users interact with voice features in the WebView. Here's how it works:

  1. Automatic Permission Request: When a user clicks the microphone button in the WebView, the SDK automatically requests microphone permission
  2. User-Friendly Dialogs: If permission is denied, the SDK shows helpful dialogs to guide users to app settings
  3. Web Widget Notification: The web widget is automatically notified of permission status changes

No additional code is required - the SDK handles all permission logic internally.

πŸ” Troubleshooting Permissions

If voice features aren't working:

  1. Check Permissions: Ensure the required permissions are added to your platform files
  2. Test on Device: Test on a physical device (permissions don't work in simulators)
  3. Check App Settings: Verify permissions are granted in device settings
  4. Debug Mode: Enable debug mode in your KailyConfig to see permission request logs
KailyConfig.withDefaults(
  token: 'your-token',
  user: user,
  debugMode: true, // Enable debug logging
)

πŸ§ͺ Testing

import 'package:flutter_test/flutter_test.dart';
import 'package:kaily_flutter_sdk/kaily_flutter_sdk.dart';

void main() {
  test('should initialize with valid config', () async {
    final config = KailyConfig.withDefaults(
      token: 'test-token',
      user: KailyUser(id: 'test', name: 'Test User'),
    );

    await KailySDK.instance.initialize(config);
    expect(KailySDK.instance.isInitialized, isTrue);
  });
}

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.


Made with ❀️ by the Kaily team

Libraries

kaily_flutter_sdk
Kaily Flutter Client