flutter_webview_communication 0.1.6 copy "flutter_webview_communication: ^0.1.6" to clipboard
flutter_webview_communication: ^0.1.6 copied to clipboard

A Flutter plugin for bi-directional communication between WebView and Flutter with HTML, CSS, and script support.

flutter_webview_communication #

A Flutter plugin for creating WebViews with seamless bi-directional communication between Flutter and JavaScript, built on top of the webview_flutter package (version 4.8.0).

Demo of flutter_webview_communication in action

Table of Contents #

Features #

  • Load custom HTML content or external URLs.
  • Inject custom CSS (for HTML content) and JavaScript (embedded for HTML, injected post-load for URLs).
  • Bi-directional JSON-based communication between Flutter and WebView with action-based message handling.
  • Manage WebView local storage: save, retrieve, remove, and clear key-value pairs.
  • Apply Content Security Policy (CSP) to custom HTML for enhanced security.
  • Set custom user agent strings for WebView requests.
  • Monitor loading states (started, progress, finished, error) with progress percentages and HTTP error details.
  • Retrieve the current scroll position of the WebView using JavaScript.
  • Clear cookies, cache, and local storage with a single method.
  • Reload the current WebView content (HTML or URL).
  • Comprehensive JavaScript error handling via a callback.
  • Cross-platform support with minimal dependencies.

Platform Support #

Platform Support Status Notes
Android Fully supported Requires minSdkVersion 19 and INTERNET permission.
iOS Fully supported No additional configuration required.
macOS Partially supported Background color not supported.
Web Not supported Throws Exception due to lack of native WebView.
Windows Not supported
Linux Not supported

Installation #

Add the following to your pubspec.yaml:

dependencies:
  flutter_webview_communication: ^0.1.6

Run flutter pub get to install the package.

Platform Setup #

  • Android: Ensure the minimum SDK version is set in android/app/build.gradle:
    android {
        defaultConfig {
            minSdkVersion 19
        }
    }
    
    Add the INTERNET permission in android/app/src/main/AndroidManifest.xml:
    <uses-permission android:name="android.permission.INTERNET" />
    
  • iOS: No additional configuration required.
  • macOS: Ensure macOS is enabled in your Flutter project (e.g., via flutter create --platforms=macos).

Usage #

Basic Example (HTML Content with Local Storage) #

This example loads HTML content, handles messages, manages local storage, retrieves scroll position, and monitors loading states.

import 'package:flutter/material.dart';
import 'package:flutter_webview_communication/flutter_webview_communication.dart';

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

  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  late WebViewPlugin webViewPlugin;
  String? errorMessage;
  String loadingState = 'idle';
  int? loadingProgress;

  @override
  void initState() {
    super.initState();
    try {
      webViewPlugin = WebViewPlugin(
        enableCommunication: true,
        actionHandlers: {
          'update': (payload) {
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('Received: ${payload['text']}')),
            );
          },
        },
        onJavaScriptError: (error) {
          setState(() {
            errorMessage = error;
          });
        },
      );
    } catch (e) {
      setState(() {
        errorMessage = e.toString();
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: errorMessage != null
          ? Center(child: Text(errorMessage!))
          : Column(
              children: [
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Text('Loading: $loadingState${loadingProgress != null ? ' ($loadingProgress%)' : ''}'),
                ),
                Expanded(
                  child: webViewPlugin.buildWebView(
                    content: '''
                      <h1>Hello</h1>
                      <button onclick="sendToFlutter('update', {text: 'Hi'})">Click</button>
                    ''',
                    cssContent: '<style>h1 { color: blue; }</style>',
                    scriptContent: '''
                      window.addEventListener('flutterData', (e) => {
                        document.querySelector('h1').innerText = e.detail.payload.text;
                      });
                    ''',
                    csp: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'",
                    userAgent: 'MyApp/1.0',
                    onLoadingStateChanged: (state, progress, error) {
                      setState(() {
                        loadingState = state;
                        loadingProgress = progress;
                        if (error != null) {
                          errorMessage = error;
                        }
                      });
                    },
                  ),
                ),
              ],
            ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: errorMessage != null
                ? null
                : () => webViewPlugin.sendToWebView(
                      action: 'update',
                      payload: {'text': 'Hello from Flutter'},
                    ),
            child: const Icon(Icons.send),
            tooltip: 'Send to WebView',
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            onPressed: errorMessage != null
                ? null
                : () => webViewPlugin.saveToLocalStorage(
                      key: 'userData',
                      value: 'Saved from Flutter',
                    ),
            child: const Icon(Icons.save),
            tooltip: 'Save to Local Storage',
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            onPressed: errorMessage != null
                ? null
                : () => webViewPlugin.removeFromLocalStorage(key: 'userData'),
            child: const Icon(Icons.delete),
            tooltip: 'Remove from Local Storage',
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            onPressed: errorMessage != null
                ? null
                : () async {
                    final storage = await webViewPlugin.getLocalStorage();
                    if (mounted) {
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(content: Text('Storage: $storage')),
                      );
                    }
                  },
            child: const Icon(Icons.storage),
            tooltip: 'Get Local Storage',
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            onPressed: errorMessage != null
                ? null
                : () async {
                    final position = await webViewPlugin.getScrollPosition();
                    if (mounted) {
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(content: Text('Scroll: x=${position['x']}, y=${position['y']}')),
                      );
                    }
                  },
            child: const Icon(Icons.arrow_downward),
            tooltip: 'Get Scroll Position',
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            onPressed: errorMessage != null
                ? null
                : () => webViewPlugin.clearWebViewData(),
            child: const Icon(Icons.clear),
            tooltip: 'Clear WebView Data',
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            onPressed: errorMessage != null
                ? null
                : () => webViewPlugin.reload(),
            child: const Icon(Icons.refresh),
            tooltip: 'Reload WebView',
          ),
        ],
      ),
    );
  }
}

URL Example with JavaScript Injection #

This example loads a URL and injects JavaScript.

import 'package:flutter/material.dart';
import 'package:flutter_webview_communication/flutter_webview_communication.dart';

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

  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  late WebViewPlugin webViewPlugin;
  String? errorMessage;
  String loadingState = 'idle';
  int? loadingProgress;

  @override
  void initState() {
    super.initState();
    try {
      webViewPlugin = WebViewPlugin(
        enableCommunication: true,
        actionHandlers: {
          'update': (payload) {
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('Received: ${payload['text']}')),
            );
          },
        },
        onJavaScriptError: (error) {
          setState(() {
            errorMessage = error;
          });
        },
      );
    } catch (e) {
      setState(() {
        errorMessage = e.toString();
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: errorMessage != null
          ? Center(child: Text(errorMessage!))
          : Column(
              children: [
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Text('Loading: $loadingState${loadingProgress != null ? ' ($loadingProgress%)' : ''}'),
                ),
                Expanded(
                  child: webViewPlugin.buildWebView(
                    content: 'https://example.com',
                    isUrl: true,
                    scriptContent: '''
                      window.addEventListener('flutterData', (e) => {
                        const p = document.createElement('p');
                        p.textContent = e.detail.payload.text;
                        document.body.appendChild(p);
                      });
                      document.addEventListener('DOMContentLoaded', () => {
                        const btn = document.createElement('button');
                        btn.textContent = 'Send to Flutter';
                        btn.onclick = () => sendToFlutter('update', {text: 'From Web'});
                        document.body.appendChild(btn);
                      });
                    ''',
                    userAgent: 'MyApp/1.0',
                    onLoadingStateChanged: (state, progress, error) {
                      setState(() {
                        loadingState = state;
                        loadingProgress = progress;
                        if (error != null) {
                          errorMessage = error;
                        }
                      });
                    },
                  ),
                ),
              ],
            ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: errorMessage != null
                ? null
                : () => webViewPlugin.sendToWebView(
                      action: 'update',
                      payload: {'text': 'Hello from Flutter'},
                    ),
            child: const Icon(Icons.send),
            tooltip: 'Send to WebView',
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            onPressed: errorMessage != null
                ? null
                : () => webViewPlugin.saveToLocalStorage(
                      key: 'userData',
                      value: 'Saved at ${DateTime.now()}',
                    ),
            child: const Icon(Icons.save),
            tooltip: 'Save to Local Storage',
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            onPressed: errorMessage != null
                ? null
                : () => webViewPlugin.removeFromLocalStorage(key: 'userData'),
            child: const Icon(Icons.delete),
            tooltip: 'Remove from Local Storage',
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            onPressed: errorMessage != null
                ? null
                : () async {
                    final storage = await webViewPlugin.getLocalStorage();
                    if (mounted) {
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(content: Text('Storage: $storage')),
                      );
                    }
                  },
            child: const Icon(Icons.storage),
            tooltip: 'Get Local Storage',
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            onPressed: errorMessage != null
                ? null
                : () async {
                    final position = await webViewPlugin.getScrollPosition();
                    if (mounted) {
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(content: Text('Scroll: x=${position['x']}, y=${position['y']}')),
                      );
                    }
                  },
            child: const Icon(Icons.arrow_downward),
            tooltip: 'Get Scroll Position',
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            onPressed: errorMessage != null
                ? null
                : () => webViewPlugin.clearWebViewData(),
            child: const Icon(Icons.clear),
            tooltip: 'Clear WebView Data',
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            onPressed: errorMessage != null
                ? null
                : () => webViewPlugin.reload(),
            child: const Icon(Icons.refresh),
            tooltip: 'Reload WebView',
          ),
        ],
      ),
    );
  }
}

Simple Example #

A minimal setup for quick integration.

import 'package:flutter_webview_communication/flutter_webview_communication.dart';

final plugin = WebViewPlugin(
  enableCommunication: true,
  actionHandlers: {
    'update': (payload) => debugPrint('Received: $payload'),
  },
  onJavaScriptError: (error) => debugPrint('JS Error: $error'),
);

Widget webView = plugin.buildWebView(
  content: '<h1>Hello</h1>',
  cssContent: '<style>h1 { color: blue; }</style>',
  scriptContent: 'console.log("Loaded");',
  csp: "default-src 'self'; script-src 'self' 'unsafe-inline'",
  userAgent: 'MyApp/1.0',
  onLoadingStateChanged: (state, progress, error) => debugPrint('$state: $error'),
);

plugin.sendToWebView(action: 'update', payload: {'text': 'Hello'});
plugin.saveToLocalStorage(key: 'test', value: 'Simple data');
plugin.getLocalStorage().then((data) => debugPrint('Storage: $data'));
plugin.removeFromLocalStorage(key: 'test');
plugin.clearWebViewData();
plugin.getScrollPosition().then((pos) => debugPrint('Scroll: x=${pos['x']}, y=${pos['y']}'));
plugin.reload();

API #

WebViewPlugin #

  • WebViewPlugin({Map<String, Function(Map<String, dynamic>)?>? actionHandlers, bool enableCommunication = false, Function(String)? onJavaScriptError})
    • Initializes the plugin with optional action handlers, communication script injection, and JavaScript error callback. Throws Exception on unsupported platforms (e.g., Web, Windows, Linux).
    • Parameters:
      • actionHandlers: Map of action names to handler functions for WebView messages.
      • enableCommunication: Enables injection of sendToFlutter and receiveFromFlutter scripts (default: false).
      • onJavaScriptError: Callback for JavaScript errors.
  • buildWebView({required String content, bool isUrl = false, String? cssContent, String? scriptContent, double? height, double? width, Color? backgroundColor, bool? enableCommunication, String? userAgent, String? csp, Function(String state, int? progress, String? error)? onLoadingStateChanged})
    • Creates a WebView widget with the specified content (HTML or URL).
    • Parameters:
      • content: HTML string or URL.
      • isUrl: Treat content as a URL if true (default: false).
      • cssContent: CSS to include in <head> (HTML only).
      • scriptContent: JavaScript to embed (HTML) or inject post-load (URL).
      • height, width: Optional dimensions for the WebView.
      • backgroundColor: Background color (not supported on macOS).
      • enableCommunication: Overrides constructor’s setting for communication scripts.
      • userAgent: Custom user agent string.
      • csp: Content Security Policy for HTML content.
      • onLoadingStateChanged: Callback for loading states (started, progress, finished, error) with progress and error details.
  • sendToWebView({required dynamic payload, String? action})
    • Sends JSON-serializable data to the WebView with an optional action identifier.
  • saveToLocalStorage({required String key, required dynamic value})
    • Saves a key-value pair to the WebView’s local storage. The value is JSON-stringified.
  • removeFromLocalStorage({required String key})
    • Removes a key from the WebView’s local storage.
  • getLocalStorage()
    • Retrieves all key-value pairs from local storage, with JSON-decoded values where possible.
  • clearWebViewData()
    • Clears cookies, cache, and local storage.
  • reload()
    • Reloads the current WebView content (HTML or URL).
  • getScrollPosition()
    • Returns the current scroll position as a Map<String, double> with x and y coordinates, using JavaScript.

JavaScript API #

  • sendToFlutter(action, payload)
    • Sends a message to Flutter with an action string and payload object.
    • Example:
      sendToFlutter('update', {text: 'Hello from Web'});
      
  • receiveFromFlutter(data)
    • Handles data from Flutter, dispatched as a flutterData event.
    • Example:
      window.addEventListener('flutterData', (e) => {
        console.log(e.detail); // {action: 'update', payload: {text: 'Hello'}}
      });
      

Platform-Specific Features #

For advanced features like auto-playing media, add platform-specific dependencies to pubspec.yaml:

dependencies:
  webview_flutter: ^4.8.0
  webview_flutter_android: ^3.16.4
  webview_flutter_wkwebview: ^3.14.0

Configure platform-specific settings:

import 'package:webview_flutter_android/webview_flutter_android.dart';
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';

void configureWebView(WebViewPlugin plugin) {
  final controller = plugin.buildWebView(content: 'https://example.com', isUrl: true);
  if (controller.platform is AndroidWebViewController) {
    (controller.platform as AndroidWebViewController).setMediaPlaybackRequiresUserGesture(false);
  } else if (controller.platform is WebKitWebViewController) {
    (controller.platform as WebKitWebViewController).setAllowsInlineMediaPlayback(true);
  }
}

Debugging Tips #

  • Local Storage: Use WebView developer tools (if available) to inspect localStorage and verify saved, retrieved, or cleared data.
  • Loading States: Monitor onLoadingStateChanged outputs to track progress percentages and errors (e.g., HTTP or resource errors).
  • Scroll Position: Verify getScrollPosition returns accurate x/y coordinates after scrolling.
  • JavaScript Errors: Check onJavaScriptError logs for issues in injected scripts or communication.
  • Logs: Review debugPrint outputs in the Flutter console for operation success or errors.
  • Platform Issues: Ensure Android INTERNET permission is set and minSdkVersion is 19 or higher.

Contributing #

Contributions are welcome! Please submit issues or pull requests to the GitHub repository.

4
likes
120
points
127
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for bi-directional communication between WebView and Flutter with HTML, CSS, and script support.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, webview_flutter, webview_flutter_android, webview_flutter_wkwebview

More

Packages that depend on flutter_webview_communication