This repository contains a sample Android application designed to help developers and analysts understand how to implement WebView tracking using Firebase Analytics for Google Analytics 4 (GA4). It addresses common challenges associated with tracking user interactions within WebViews and demonstrates a recommended solution using a JavaScript interface to forward events to native code.
- Problem Statement
- Recommended Solution
- Project Structure
- Getting Started
- Implementation Details
- Testing the Implementation
- Additional Notes
- Contributing
- License
When integrating web content within an Android app using WebView, tracking user interactions can be problematic. Using standard Google Analytics tracking (e.g., gtag.js) within the WebView can lead to inflated user session counts and inaccurate analytics data. This happens because both the WebView's JavaScript (via GTAG) and the native app send events to the same Google Analytics 4 property (under seperate Web and App streams), resulting in artifically high user and session counts.
To avoid inflated session counts and duplicate analytics data, it's recommended to forward analytics events from the WebView's JavaScript context to the native Android code. This can be achieved by:
-
Implementing a JavaScript Interface: Create a bridge between the WebView's JavaScript and the native Android code using
addJavascriptInterface. -
Forwarding Events to Native Code: In the WebView's JavaScript, instead of using
gtag.jsto log events, call the methods exposed by the JavaScript interface to log events directly in the native code using Firebase Analytics.
By following this approach, all analytics events are logged from the native side, ensuring consistent and accurate data without duplication.
android-webviews/
βββ app/
β βββ build.gradle
β βββ src/
β β βββ main/
β β β βββ java/com/yourcompany/yourapp/
β β β β βββ AnalyticsWebInterface.java
β β β β βββ MainActivity.kt
β β β β βββ ui/
β β β β βββ dashboard/
β β β β β βββ DashboardFragment.kt
β β β β β βββ DashboardViewModel.kt
β β β β βββ home/
β β β β β βββ HomeFragment.kt
β β β β β βββ HomeViewModel.kt
β β β β βββ notifications/
β β β β βββ NotificationsFragment.kt
β β β β βββ NotificationsViewModel.kt
β β β βββ res/...
β β βββ androidTest/java/... (Test files)
β βββ ... (Other configurations)
βββ google-tag-manager/
β βββ event_utility_js_handler.js
β βββ logEvent_function.js
βββ build.gradle
βββ settings.gradle
βββ ... (Other project files)
To get a local copy up and running, follow these steps.
- Android Studio Bumblebee or later
- Android SDK
- Firebase account with a project set up for Android
-
Clone the repository:
git clone https://github.com/rb-jellyfish/android-webviews.git
-
Replace Package Name:
- The sample project uses the package name
com.example.demoshop. - Replace all instances of
com.example.demoshopwith your own package name in the project files.- Update the
applicationIdinapp/build.gradle. - Refactor the package name in your source code directories and files.
- Update the
namespaceinapp/build.gradle.
- Update the
Example:
android { namespace 'com.yourcompany.yourapp' defaultConfig { applicationId "com.yourcompany.yourapp" // ... } }
- The sample project uses the package name
-
Add Firebase to your project:
- Follow the Firebase setup guide to add Firebase to your Android app.
- Download the
google-services.jsonfile from the Firebase console and place it in theapp/directory. - Important: Ensure that the package name in your
google-services.jsonmatches your application's package name.
-
Sync Gradle files:
- Android Studio should prompt you to sync the Gradle files. Click on Sync Now.
-
Include JavaScript Files for Web Frontend:
- In the
google-tag-manager/directory, you will find two JavaScript files that need to be included in your web frontend:logEvent_function.jsevent_utility_js_handler.js
- Include these scripts in your web pages to enable communication between your web content and the native Android app.
- In the
This class defines the JavaScript interface that the WebView will use to communicate with the native code.
Key Points:
- Methods Exposed to JavaScript:
logEvent(String name, String jsonParams): Logs events to Firebase Analytics.setUserProperty(String name, String value): Sets user properties in Firebase Analytics.
- JSON Parsing:
- The
jsonToBundlemethod recursively converts JSON objects to AndroidBundleobjects, handling various data types, including nested objects and arrays.
- The
Usage:
webView.addJavascriptInterface(
new AnalyticsWebInterface(requireContext()), "AnalyticsWebInterface"
);This fragment sets up the WebView and adds the JavaScript interface.
Key Configurations:
-
WebView Settings:
webView.settings.apply { javaScriptEnabled = true domStorageEnabled = true mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW }
-
Adding JavaScript Interface:
webView.addJavascriptInterface( AnalyticsWebInterface(requireContext()), "AnalyticsWebInterface" ) -
Loading Your Web Content:
webView.loadUrl("https://yourwebsite.com")
Note: Replace "https://firebase.google.com/docs/analytics/webview" with the URL of your web content.
To enable your web pages to communicate with the native Android app, you'll need to include custom JavaScript functions. These scripts are responsible for forwarding analytics events from the web page to the native code.
Place the following JavaScript files in your web project's appropriate directories:
-
logEvent_function.jstry { var db = {{Debug Mode}} window.logEvent = function(name, params) { if (!name) { return; } if (window.AnalyticsWebInterface) { // Call Android interface window.AnalyticsWebInterface.logEvent(name, JSON.stringify(params)); } else if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.firebase) { // Call iOS interface var message = { command: 'logEvent', name: name, parameters: params }; window.webkit.messageHandlers.firebase.postMessage(message); } else { // No Android or iOS interface found console.log("No native APIs found."); } } window.setUserProperty = function(name, value) { if (!name || !value) { return; } if (window.AnalyticsWebInterface) { // Call Android interface window.AnalyticsWebInterface.setUserProperty(name, value); } else if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.firebase) { // Call iOS interface var message = { command: 'setUserProperty', name: name, value: value }; window.webkit.messageHandlers.firebase.postMessage(message); } else { // No Android or iOS interface found console.log("No native APIs found."); } } } catch (error) { if(db){console.log("Error in CHTML JS Handler Script:", error);} }
-
event_utility_js_handler.js(function() { try { var db = {{Debug Mode}}; var ecommerceData = {{DLV - ecommerce}}; var eventName = {{Event}}; if (!ecommerceData || !eventName) { if (db) { console.log('Ecommerce data or event name is missing'); } return; } // Prepare event parameters var eventParams = { currency: "AUD", value: 0, items: ecommerceData.items }; // Call logEvent with the event name and parameters logEvent(eventName, eventParams); if (db) { console.log('Pushed to JS Handler', eventName, eventParams); } } catch(err) { if (db) { console.log('Error in ecommerce tracking:', err); } } })();
-
Include the
logEvent_function.jsat the beginning of your web page:<script src="path/to/logEvent_function.js"></script>
-
Include the
event_utility_js_handler.jswhere you want to track specific events:<script src="path/to/event_utility_js_handler.js"></script>
-
Set Up Variables for Google Tag Manager (Optional):
- If you're using Google Tag Manager (GTM), you can set up variables and data layer events to pass the required data to these scripts.
- Replace
{{Debug Mode}},{{DLV - ecommerce}}, and{{Event}}with the appropriate GTM variables or hard-coded values.
-
logEvent_function.js- Defines the
logEventandsetUserPropertyfunctions, which check for the presence of the native interfaces. - Supports both Android (
AnalyticsWebInterface) and iOS (window.webkit.messageHandlers).
- Defines the
-
event_utility_js_handler.js- Wraps the event logging in an IIFE (Immediately Invoked Function Expression).
- Retrieves event data from variables and calls
logEventwith the appropriate parameters. - Handles errors gracefully and logs them if in debug mode.
-
Check for Interface Availability:
- The scripts check if the native interfaces are available before calling them. This prevents errors when the web page is loaded outside of the app.
-
Debug Mode:
- Use the
dbvariable to enable or disable console logging for debugging purposes.
- Use the
-
Currency and Value:
- Adjust the
currencyandvalueparameters as needed for your application.
- Adjust the
-
Event Names and Parameters:
- Ensure that the
eventNameandeventParamsmatch the events and parameters you want to track in Firebase Analytics.
- Ensure that the
-
Run the App:
- Build and run the app on an emulator or physical device.
-
Navigate to the WebView:
- Use the app's navigation to open the
DashboardFragment, which contains the WebView.
- Use the app's navigation to open the
-
Interact with the Web Content:
- Perform actions on the web page that trigger analytics events.
-
Verify in Firebase Analytics:
- Go to the Firebase console and navigate to the Analytics dashboard.
- Use the DebugView to verify that events are being logged correctly.
-
Security Considerations:
- Be cautious when adding JavaScript interfaces to WebViews.
- Only expose necessary methods to prevent potential security risks.
- Ensure the WebView loads content from trusted sources.
-
Sensitive Files:
google-services.json:- Contains sensitive information about your Firebase project.
- Do not commit this file to version control.
- Provide instructions for other developers to obtain their own
google-services.json.
-
Package Name Replacement:
- Replace all instances of
com.example.demoshopwith your own package name throughout the project.
- Replace all instances of
-
Debugging:
-
Enable WebView debugging during development:
WebView.setWebContentsDebuggingEnabled(true)
-
-
API Level Compatibility:
-
The
addJavascriptInterfacemethod is safe to use starting from API level 17 (Jelly Bean MR1). For devices running lower API levels, implement appropriate checks.if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { webView.addJavascriptInterface( AnalyticsWebInterface(requireContext()), "AnalyticsWebInterface" ) }
-
-
Event Parameter Limitations:
- Firebase Analytics has limitations on the size and number of parameters for events. Ensure that the data you send complies with Firebase's guidelines.
-
User Privacy:
- Always comply with user privacy regulations and policies.
- Provide options for users to opt-out of analytics tracking if required.