# Flutter WebView

> Flutter WebView Integration Guide - Enable microphone access for Revrag Embed Agent in mobile apps

URL: /embed/web-view-integration/flutter
Markdown: /embed/web-view-integration/flutter.md



{/* 
  <img
  className="block dark:hidden"
  src="https://images.unsplash.com/photo-1555774698-0b77e0d5fac6?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1400&q=72"
  alt="Flutter WebView Development Light"
  />
  <img
  className="hidden dark:block"
  src="https://images.unsplash.com/photo-1517077304055-6e89abbf09b0?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1400&q=72"
  alt="Flutter WebView Development Dark"
  /> */}

# Flutter WebView Integration Guide [#flutter-webview-integration-guide]

## Overview [#overview]

This Flutter WebView integration provides a solution for the microphone access challenge faced by Revrag's embed agent when implemented in mobile apps using WebView. The embed agent requires microphone access to enable voice-based AI assistance for website navigation, but traditional WebView implementations often fail to properly delegate microphone permissions to the embedded website.

## Problem Statement [#problem-statement]

Revrag's embed agent is a JavaScript SDK that can be integrated into any website to provide AI-powered voice assistance. However, when mobile apps load websites containing the embed agent in a WebView, the microphone access required by the agent is often blocked or not properly delegated, preventing users from interacting with the AI assistant.

## Prerequisites [#prerequisites]

* Flutter 3.0+
* iOS 13+ / Android API 21+
* Microphone permissions on target device

<Note>
  This integration requires proper setup of WebView microphone permissions and real-time audio communication capabilities. Check out the detailed documentation for [flutter\_inappwebview](https://inappwebview.dev/docs/intro/).
</Note>

## Installation [#installation]

Add the required packages to your `pubspec.yaml`:

```yaml
dependencies:
  flutter:
    sdk: flutter
  flutter_inappwebview: ^6.0.0
  permission_handler: ^11.3.1
```

Then run:

```bash
flutter pub get
```

## Solution Overview [#solution-overview]

This project implements a Flutter WebView solution using the `flutter_inappwebview` package with proper microphone permission handling.

The solution:

1. **Requests native microphone permissions** using the `permission_handler` package
2. **Delegates permissions to the WebView** when the embed agent requests microphone access
3. **Handles permission states** across both native and WebView contexts
4. **Provides a seamless user experience** for voice interactions

### Key Features [#key-features]

* ✅ **Native Microphone Permission Handling**: Properly requests and manages microphone permissions at the app level
* ✅ **WebView Permission Delegation**: Seamlessly delegates permissions to the embedded website
* ✅ **Cross-Platform Support**: Works on both Android and iOS
* ✅ **Error Handling**: Comprehensive error handling and user feedback
* ✅ **Configurable**: Easy configuration for different websites and use cases

## Android Configuration [#android-configuration]

### 1. Android Manifest Permissions [#1-android-manifest-permissions]

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

```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- Required permissions for WebView microphone access -->
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        android:label="@string/app_name"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher"
        android:usesCleartextTraffic="true">

        <!-- Your activities and other components -->

    </application>
</manifest>
```

## iOS Configuration [#ios-configuration]

### 1. iOS Permissions [#1-ios-permissions]

<Warning>
  **CRITICAL:** Add the following permissions to your `ios/Runner/Info.plist`. Missing `NSMicrophoneUsageDescription` will cause the app to crash when accessing the microphone.
</Warning>

```xml
<key>NSMicrophoneUsageDescription</key>
<string>This app needs access to microphone to enable voice functionality in the webview.</string>

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <false/>
    <key>NSAllowsLocalNetworking</key>
    <true/>
</dict>
```

## WebView Implementation [#webview-implementation]

### Permission Handling [#permission-handling]

The app uses a two-layer permission system:

```dart
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';

Future<PermissionResponse> _requestMicrophonePermission(
  List<PermissionResourceType> resources,
) async {
  try {
    // Check current permission status
    PermissionStatus status = await Permission.microphone.status;

    if (status.isDenied) {
      // Request permission
      status = await Permission.microphone.request();
      if (status.isGranted) {
        return PermissionResponse(
          resources: resources,
          action: PermissionResponseAction.GRANT,
        );
      }
    } else if (status.isGranted) {
      // Permission granted, allow WebView access
      return PermissionResponse(
        resources: resources,
        action: PermissionResponseAction.GRANT,
      );
    }
    
    // Permission denied
    return PermissionResponse(
      resources: resources,
      action: PermissionResponseAction.DENY,
    );
  } catch (e) {
    print('Permission request error: $e');
    return PermissionResponse(
      resources: resources,
      action: PermissionResponseAction.DENY,
    );
  }
}
```

### WebView Configuration [#webview-configuration]

The WebView is configured with specific settings to enable microphone access:

```dart
InAppWebView(
  initialUrlRequest: URLRequest(
    url: WebUri('https://your-website.com')
  ),
  initialSettings: InAppWebViewSettings(
    javaScriptEnabled: true,
    mediaPlaybackRequiresUserGesture: false,
    allowsInlineMediaPlayback: true,
    allowsAirPlayForMediaPlayback: true,
    allowsPictureInPictureMediaPlayback: true,
    useHybridComposition: true,
    mixedContentMode: MixedContentMode.MIXED_CONTENT_ALWAYS_ALLOW,
    allowFileAccess: true,
    allowContentAccess: true,
  ),
  onPermissionRequest: (controller, request) async {
    if (request.resources.contains(PermissionResourceType.MICROPHONE)) {
      return await _requestMicrophonePermission(request.resources);
    }
    return PermissionResponse(
      resources: request.resources,
      action: PermissionResponseAction.DENY,
    );
  },
  onLoadError: (controller, url, code, message) {
    print('WebView load error: $code - $message');
  },
  onConsoleMessage: (controller, consoleMessage) {
    print('Console: ${consoleMessage.message}');
  },
)
```

## Usage Examples [#usage-examples]

### Complete Implementation Example [#complete-implementation-example]

```dart
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:permission_handler/permission_handler.dart';

class MicrophoneWebView extends StatefulWidget {
  final String targetUrl;
  
  const MicrophoneWebView({
    Key? key,
    this.targetUrl = 'https://your-website.com',
  }) : super(key: key);

  @override
  State<MicrophoneWebView> createState() => _MicrophoneWebViewState();
}

class _MicrophoneWebViewState extends State<MicrophoneWebView> {
  InAppWebViewController? _webViewController;
  bool _isLoading = true;
  bool _micGranted = false;

  @override
  void initState() {
    super.initState();
    _requestMicrophonePermission();
  }

  Future<void> _requestMicrophonePermission() async {
    try {
      PermissionStatus status = await Permission.microphone.request();
      setState(() {
        _micGranted = status.isGranted;
      });
    } catch (e) {
      print('Permission request failed: $e');
      _showErrorDialog('Failed to request microphone permission');
    }
  }

  Future<PermissionResponse> _handlePermissionRequest(
    List<PermissionResourceType> resources,
  ) async {
    if (resources.contains(PermissionResourceType.MICROPHONE)) {
      if (_micGranted) {
        return PermissionResponse(
          resources: resources,
          action: PermissionResponseAction.GRANT,
        );
      } else {
        // Request permission again
        await _requestMicrophonePermission();
        return PermissionResponse(
          resources: resources,
          action: _micGranted 
            ? PermissionResponseAction.GRANT 
            : PermissionResponseAction.DENY,
        );
      }
    }
    
    return PermissionResponse(
      resources: resources,
      action: PermissionResponseAction.DENY,
    );
  }

  void _showErrorDialog(String message) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Error'),
        content: Text(message),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: const Text('OK'),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    if (!_micGranted) {
      return Scaffold(
        appBar: AppBar(title: const Text('WebView')),
        body: const Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Icon(Icons.mic_off, size: 64, color: Colors.red),
              SizedBox(height: 16),
              Text(
                'Microphone permission is required for voice features',
                textAlign: TextAlign.center,
                style: TextStyle(fontSize: 16),
              ),
            ],
          ),
        ),
      );
    }

    return Scaffold(
      appBar: AppBar(
        title: const Text('WebView'),
        actions: [
          if (_isLoading)
            const Center(
              child: Padding(
                padding: EdgeInsets.all(16.0),
                child: SizedBox(
                  width: 20,
                  height: 20,
                  child: CircularProgressIndicator(strokeWidth: 2),
                ),
              ),
            ),
        ],
      ),
      body: InAppWebView(
        initialUrlRequest: URLRequest(
          url: WebUri(widget.targetUrl),
        ),
        initialSettings: InAppWebViewSettings(
          javaScriptEnabled: true,
          mediaPlaybackRequiresUserGesture: false,
          allowsInlineMediaPlayback: true,
          useHybridComposition: true,
          mixedContentMode: MixedContentMode.MIXED_CONTENT_ALWAYS_ALLOW,
        ),
        onWebViewCreated: (controller) {
          _webViewController = controller;
        },
        onPermissionRequest: (controller, request) async {
          return await _handlePermissionRequest(request.resources);
        },
        onLoadStart: (controller, url) {
          setState(() {
            _isLoading = true;
          });
        },
        onLoadStop: (controller, url) {
          setState(() {
            _isLoading = false;
          });
        },
        onLoadError: (controller, url, code, message) {
          print('WebView load error: $code - $message');
          _showErrorDialog('Failed to load webpage: $message');
        },
        onConsoleMessage: (controller, consoleMessage) {
          print('Console: ${consoleMessage.message}');
        },
      ),
    );
  }
}
```

## Troubleshooting [#troubleshooting]

### Common Issues [#common-issues]

<AccordionGroup>
  <Accordion title="Microphone permission denied">
    Check the following requirements:

    1. ✅ App has microphone permissions in device settings
    2. ✅ Manifest permissions are declared correctly
    3. ✅ Permission handler is implemented properly
    4. ✅ WebView permission delegation is configured

    ```dart
    // Check permission status
    Future<void> checkPermissionStatus() async {
      PermissionStatus status = await Permission.microphone.status;
      print('Microphone permission status: $status');
      
      if (status.isPermanentlyDenied) {
        // Open app settings
        await openAppSettings();
      }
    }
    ```
  </Accordion>

  <Accordion title="WebView not loading">
    Verify connectivity and configuration:

    1. ✅ Check internet connectivity
    2. ✅ Verify URL accessibility in browser
    3. ✅ Ensure JavaScript is enabled
    4. ✅ Check mixed content settings

    ```dart
    // Add comprehensive error handling
    onLoadError: (controller, url, code, message) {
      print('Load error: $code - $message');
      print('Failed URL: $url');
      
      // Show user-friendly error
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Failed to load webpage')),
      );
    }
    ```
  </Accordion>

  <Accordion title="JavaScript errors in WebView">
    Debug JavaScript execution issues:

    1. ✅ Verify JavaScript is enabled in WebView settings
    2. ✅ Check console messages for errors
    3. ✅ Test website in mobile browser first
    4. ✅ Ensure all required permissions are granted

    ```dart
    // Monitor console messages
    onConsoleMessage: (controller, consoleMessage) {
      print('Console [${consoleMessage.messageLevel}]: ${consoleMessage.message}');
      
      if (consoleMessage.messageLevel == ConsoleMessageLevel.ERROR) {
        // Handle JavaScript errors
        print('JavaScript error detected');
      }
    }
    ```
  </Accordion>
</AccordionGroup>

### Best Practices [#best-practices]

<Tip>
  **Security & Permissions:**

  * Only grant necessary permissions to WebView
  * Restrict WebView to trusted domains
  * Always request user permission before accessing microphone
  * Handle permission denials gracefully with user-friendly messages
</Tip>

<Tip>
  **Performance Optimization:**

  * Use lazy loading for WebView when possible
  * Cache permission status to avoid repeated requests
  * Implement proper error handling and loading states
  * Monitor WebView memory usage and dispose properly
</Tip>

<Tip>
  **Configuration Management:**

  * Use environment variables for sensitive URLs
  * Create configurable settings for different environments
  * Test with various target websites during development
  * Implement logging for debugging and monitoring
</Tip>

## Support [#support]

For additional help:

* **Email Support:** [contact@revrag.ai](mailto:contact@revrag.ai)

***

**Last Updated:** June 2025
