Bridging WebView to Flutter App Guidance
#Webview Bridging#Flutter#Firebase Analytics#JavaScript

Context
When customers interact with web content within a Flutter app, events should ideally be attributed to the app's analytics stream rather than a separate web stream. By implementing WebView Bridging, we can intercept events triggered within a web view and pass them directly to the Flutter Firebase Analytics SDK. This guide outlines how to configure the communication bridge so that web-based actions are recorded as Flutter app events, ensuring a unified user journey and consistent data attribution in Google Analytics 4.
Prerequisites
Before proceeding with the implementation methodology, ensure the following requirements are met:
- Core Technical Requirements:
- Firebase Analytics for Flutter: The
firebase_analyticspackage must be properly initialized in your Flutter project. - webview_flutter: The standard Flutter WebView plugin integrated into your project.
- JavaScript Channel Access: Ability to modify the web-side code (JavaScript) to send messages to the Flutter wrapper.
- Event Mapping Schema: A defined list of events (e.g., add_to_cart, view_item) that need to be bridged from the web content to the app tracker.
Setup
Part 1: Web Side: The Dispatcher (JavaScript/TypeScript)
Create a utility function that checks if the 'Bridge' (the Flutter JavaScript channel) exists. If it doesn't, it can fall back to standard web tracking (like GTM).
1. The Bridge Utility
/**
* Global Bridge Dispatcher to Flutter
* @param {string} eventName - The GA4 event name (e.g., 'purchase')
* @param {Object} params - Flat key-value pairs for event parameters
*/
export const logEventToFlutter = (eventName, params = {}) => {
if (typeof window === "undefined") return;
// 'FlutterGA' is the name of the channel we will define in Flutter
if (window.FlutterGA && window.FlutterGA.postMessage) {
const payload = JSON.stringify({
name: eventName,
parameters: params,
});
window.FlutterGA.postMessage(payload);
} else {
// Fallback: If not in the Flutter app, log to standard web analytics
console.log(`Flutter bridge not found. Logging ${eventName} to Web.`);
// Example: window.gtag('event', eventName, params);
}
};2. Usage in Web Components
// Example: Tracking a button click within the web asset
const handleButtonClick = () => {
logEventToFlutter('project_click', {
project_title: 'Asteriks',
category: 'portfolio',
target_url:'www.ilmuonedata.com',
});
};For example, when establishing the bridging logic for the view_item_list event, you can utilize the following configuration to ensure seamless data alignment between your platforms:
// For example to bridging view_item_list to the Flutter app layer
logEventToFlutter("view_item_list", {
// Keep standard GA4 parameter naming conventions
item_list_id: "1234",
item_list_name: "gelas minum",
ecommerce: {
item_list_id: "1234",
item_list_name: "gelas minum",
items: [
{
item_id: "12345",
item_name: "gelas espresso",
discount: 1000,
index: 1,
item_brand: "pokal",
item_category: "markethall",
item_category2: "cups",
item_variant: "art",
price: 9000,
quantity: 1,
},
{
item_id: "67890",
item_name: "cangkir cappuccino",
discount: 0,
index: 2,
item_brand: "pokal",
item_category: "markethall",
item_variant: "classic",
price: 12000,
quantity: 2,
},
],
},
}
);
};Part 2: Flutter Side: The Receiver
The Flutter app must set up a listener (JavaScript Channel) within its WebViewController to catch the messages sent by postMessage.
1. Add Dependencies
dependencies:
# ...
firebase_analytics: ^11.0.0
webview_flutter: ^4.0.72. The Web View Configuration
Create a Flutter component to process the incoming JSON string from the channel and pass it to the Firebase Analytics Flutter library.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
class WebViewPage extends StatefulWidget {
final String url;
const WebViewPage({super.key, required this.url});
@override
State<WebViewPage> createState() => _WebViewPageState();
}
class _WebViewPageState extends State<WebViewPage> {
late final WebViewController _controller;
@override
void initState() {
super.initState();
// Initialize controller with settings tailored for Flutter bridging
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel(
'FlutterGA', // Must exactly match the Javascript identifier
onMessageReceived: (JavaScriptMessage message) {
_handleWebEvent(message.message);
},
)
..setNavigationDelegate(
NavigationDelegate(
onWebResourceError: (error) =>
debugPrint("WebView Error: ${error.description}"),
),
)
..loadRequest(Uri.parse(widget.url));
}
Future<void> _handleWebEvent(String jsonString) async {
try {
final Map<String, dynamic> data = jsonDecode(jsonString);
final String? eventName = data['name'];
final Map<String, dynamic>? rawParams = data['parameters'];
if (eventName == null || rawParams == null) return;
// Handle Specific Flutter E-commerce Tracking: view_item_list
if (eventName == "view_item_list") {
await _logViewItemListEvent(rawParams);
return;
}
// Fallback: Handle standard flat events mapped to Flutter objects
final Map<String, Object>? serializableParams = rawParams
.cast<String, Object>();
await FirebaseAnalytics.instance.logEvent(
name: eventName,
parameters: serializableParams,
);
debugPrint("Flutter GA4 Logged Custom Event: $eventName");
} catch (e) {
debugPrint("Failed to process Web Event in Flutter: $e");
}
}
// Helper method to parse and log the view_item_list structure via Flutter objects
Future<void> _logViewItemListEvent(Map<String, dynamic> params) async {
List<AnalyticsEventItem> analyticsItems = [];
// Extract the nested ecommerce items array safely
if (params['ecommerce'] != null && params['ecommerce']['items'] != null) {
final List<dynamic> rawItems = params['ecommerce']['items'];
analyticsItems = rawItems.map((item) {
final Map<String, dynamic> itemMap = Map<String, dynamic>.from(item);
// Map web parameters directly to Flutter's AnalyticsEventItem class properties
return AnalyticsEventItem(
itemId: itemMap['item_id']?.toString(),
itemName: itemMap['item_name']?.toString(),
discount: (itemMap['discount'] as num?)?.toDouble(),
index: itemMap['index'] as int?,
itemBrand: itemMap['item_brand']?.toString(),
itemCategory: itemMap['item_category']?.toString(),
itemCategory2: itemMap['item_category2']?.toString(),
itemVariant: itemMap['item_variant']?.toString(),
price: (itemMap['price'] as num?)?.toDouble(),
quantity: itemMap['quantity'] as int?,
);
}).toList();
}
// Log using the dedicated Flutter Firebase Analytics E-commerce function
await FirebaseAnalytics.instance.logViewItemList(
itemListId: params['item_list_id']?.toString(),
itemListName: params['item_list_name']?.toString(),
items: analyticsItems,
);
debugPrint(
"Flutter GA4 Logged: view_item_list with ${analyticsItems.length} items",
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Web View")),
body: PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) async {
if (didPop) return;
if (await _controller.canGoBack()) {
await _controller.goBack();
} else {
if (context.mounted) Navigator.pop(context);
}
},
child: WebViewWidget(controller: _controller),
),
);
}
}3. How to use the WebView Component in Flutter
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const WebViewPage(
// Pass the URL your web tracking is hosted on
url: 'https://your-nextjs-website.com/home',
),
),
);
},
child: const Text("Open Shop WebView"),
);Result
- DebugView
- Confirm user interaction with specific items parameters forwarded by Flutter.
- The presence of your web-configured bridging events within the analytics platform confirms that the Flutter channel integration has been implemented correctly and data is flowing seamlessly as application actions.
- Report View Validation
- Confirm view_item_list dashboard metric visibility inside final console interface reports.




