The following document demonstrates how to call methods and interact with the Kochava SDK from within a native WebView. While this interaction can be accomplish a number of ways, provided below is an example which can be modified based upon your needs.
WebView Javascript (HTML)
Define any desired functions within your WebView’s HTML. In the following example, the functions which mirror the most common SDK usage are defined. Place this Javascript code within your WebView HTML.
function sendEventString(nameString, infoString) { postUrl('KochavaTracker://sendEventString?nameString='+encodeURIComponent(nameString)+'&infoString='+encodeURIComponent(infoString)); } function sendEventMapObject(nameString, mapObject) { postUrl('KochavaTracker://sendEventMapObject?nameString='+encodeURIComponent(nameString)+'&mapObject='+encodeURIComponent(JSON.stringify(mapObject))); } function sendEventGooglePlayReceipt(nameString, mapObject, receiptData, receiptDataSignature) { postUrl('KochavaTracker://sendEventGooglePlayReceipt?nameString='+encodeURIComponent(nameString)+'&mapObject='+encodeURIComponent(JSON.stringify(mapObject))+'&receiptData='+encodeURIComponent(receiptData)+'&receiptDataSignature='+encodeURIComponent(receiptDataSignature)); } function sendDeepLink(openURLString, sourceApplicationString) { postUrl('KochavaTracker://sendDeepLink?openURLString='+encodeURIComponent(openURLString)+'&sourceApplicationString='+encodeURIComponent(sourceApplicationString)); } function setAppLimitAdTracking(appLimitAdTrackingBool) { postUrl('KochavaTracker://setAppLimitAdTracking?appLimitAdTrackingBool='+encodeURIComponent(appLimitAdTrackingBool)); } function sendIdentityLink(mapObject) { postUrl('KochavaTracker://sendIdentityLink?mapObject='+encodeURIComponent(JSON.stringify(mapObject))); } function postUrl(url) { var i = document.createElement('iframe'); i.style.display = 'none'; i.onload = function() { i.parentNode.removeChild(i); }; i.src = url; document.body.appendChild(i); }
Android Implementation
Add the native code which interacts with the WebView. In the code of your app attach the custom WebViewClient to your WebView instance in order to intercept and process the URLs generated by the Javascript functions.
Sample Java Code:
public final class JavaWebViewClient extends WebViewClient { /** * Override the URL handler and see if we are consuming the url load. */ @Override public final boolean shouldOverrideUrlLoading(@NonNull final WebView webView, @NonNull final String urlString) { if (kochavaUrlHandler(urlString)) { return true; } // ... return false; } /** * Checks if the given url is a Kochava command and performs its action as required. * <p> * Returns true if Kochava handles the command and false for all other urls. */ private boolean kochavaUrlHandler(@NonNull final String urlString) { final String urlStringLower = urlString.toLowerCase(Locale.US); if (urlStringLower.startsWith("KochavaTracker://sendEventString".toLowerCase(Locale.US))) { final Uri url = Uri.parse(urlString); final String nameString = url.getQueryParameter("nameString"); final String infoString = url.getQueryParameter("infoString"); Tracker.sendEvent(nameString, infoString); return true; } else if (urlStringLower.startsWith("KochavaTracker://sendEventMapObject".toLowerCase(Locale.US))) { final Uri url = Uri.parse(urlString); final String nameString = url.getQueryParameter("nameString"); final String mapObject = url.getQueryParameter("mapObject"); Tracker.sendEvent(nameString, mapObject); return true; } else if (urlStringLower.startsWith("KochavaTracker://sendEventGooglePlayReceipt".toLowerCase(Locale.US))) { final Uri url = Uri.parse(urlString); final String nameString = url.getQueryParameter("nameString"); final String mapObject = url.getQueryParameter("mapObject"); final String receiptData = url.getQueryParameter("receiptData"); final String receiptDataSignature = url.getQueryParameter("receiptDataSignature"); final Event event = new Event(nameString); event.setGooglePlayReceipt(receiptData, receiptDataSignature); try { event.addCustom(new JSONObject(mapObject)); } catch (@NonNull final Exception e) { // Do something with this. } Tracker.sendEvent(event); return true; } else if (urlStringLower.startsWith("KochavaTracker://sendDeepLink".toLowerCase(Locale.US))) { final Uri url = Uri.parse(urlString); final String openURLString = url.getQueryParameter("openURLString"); final String sourceApplicationString = url.getQueryParameter("sourceApplicationString"); // sourceApplicationString is ignored on Android. Tracker.sendEventDeepLink(openURLString); return true; } else if (urlStringLower.startsWith("KochavaTracker://setAppLimitAdTracking".toLowerCase(Locale.US))) { final Uri url = Uri.parse(urlString); final boolean appLimitAdTrackingBool = url.getBooleanQueryParameter("appLimitAdTrackingBool", false); Tracker.setAppLimitAdTracking(appLimitAdTrackingBool); return true; } else if (urlStringLower.startsWith("KochavaTracker://sendIdentityLink".toLowerCase(Locale.US))) { final Uri url = Uri.parse(urlString); final String mapObject = url.getQueryParameter("mapObject"); final IdentityLink identityLink = new IdentityLink(); try { final JSONObject idLinkJson = new JSONObject(mapObject); final Iterator<String> iterator = idLinkJson.keys(); while (iterator.hasNext()) { final String key = iterator.next(); identityLink.add(key, idLinkJson.optString(key)); } } catch (@NonNull final Exception e) { // Do something with this. } Tracker.setIdentityLink(identityLink); return true; } else if (urlStringLower.startsWith("KochavaTracker://".toLowerCase(Locale.US))) { // Unrecognized KochavaTracker command. return true; } // Not a KochavaTracker command. return false; } }
Sample Kotlin Code:
class KotlinWebViewClient : WebViewClient() { /** * Override the URL handler and see if we are consuming the url load. */ override fun shouldOverrideUrlLoading(view: WebView, urlString: String): Boolean { if (kochavaUrlHandler(urlString)) { return true } // ... return false } /** * Checks if the given url is a Kochava command and performs its action as required. * * Returns true if Kochava handles the command and false for all other urls. */ private fun kochavaUrlHandler(urlString: String): Boolean { when { urlString.startsWith("KochavaTracker://sendEventString", true) -> { val url = Uri.parse(urlString) val nameString = url.getQueryParameter("nameString") val infoString = url.getQueryParameter("infoString") Tracker.sendEvent(nameString, infoString) return true } urlString.startsWith("KochavaTracker://sendEventMapObject", true) -> { val url = Uri.parse(urlString) val nameString = url.getQueryParameter("nameString") val mapObject = url.getQueryParameter("mapObject") Tracker.sendEvent(nameString, mapObject) return true } urlString.startsWith("KochavaTracker://sendEventGooglePlayReceipt", true) -> { val url = Uri.parse(urlString) val nameString = url.getQueryParameter("nameString") val mapObject = url.getQueryParameter("mapObject") val receiptData = url.getQueryParameter("receiptData") val receiptDataSignature = url.getQueryParameter("receiptDataSignature") val event = Tracker.Event(nameString) event.setGooglePlayReceipt(receiptData, receiptDataSignature) try { event.addCustom(JSONObject(mapObject)) } catch (e: Exception) { // Do something with this. } Tracker.sendEvent(event) return true } urlString.startsWith("KochavaTracker://sendDeepLink", true) -> { val url = Uri.parse(urlString) val openURLString = url.getQueryParameter("openURLString") val sourceApplicationString = url.getQueryParameter("sourceApplicationString") //sourceApplicationString is ignored on Android. Tracker.sendEventDeepLink(openURLString) return true } urlString.startsWith("KochavaTracker://setAppLimitAdTracking", true) -> { val url = Uri.parse(urlString) val appLimitAdTrackingBool = url.getBooleanQueryParameter("appLimitAdTrackingBool", false) Tracker.setAppLimitAdTracking(appLimitAdTrackingBool) return true } urlString.startsWith("KochavaTracker://sendIdentityLink", true) -> { val url = Uri.parse(urlString) val mapObject = url.getQueryParameter("mapObject") val identityLink = Tracker.IdentityLink() try { val idLinkJson = JSONObject(mapObject) for (key in idLinkJson.keys()) { identityLink.add(key, idLinkJson.optString(key)) } } catch (e: Exception) { // Do something with this. } Tracker.setIdentityLink(identityLink) return true } urlString.startsWith("KochavaTracker://", true) -> { // Unrecognized KochavaTracker command. return true } // Not a KochavaTracker command. else -> return false } } }
Usage
Now that the javascript functions have been defined and the native code within has been added within the app, call the javascript functions from within the WebView which will in turn call the corresponding methods from the SDK.
Example Code:
//Send an event with a string name and string parameters. sendEventString("Event String", "{\"param1\":\"value1\",\"param2\":true}"); //Send an event with a string name and a map object parameter. var mapObject = {}; mapObject["item"] = "value"; mapObject["item2"] = 123; mapObject["item3"] = 333.456; sendEventMapObject("Event Map Object", mapObject); //Send an event that contains an Apple Store Receipt. var mapObject = {}; mapObject["item"] = "value"; mapObject["item2"] = 123; mapObject["item3"] = 333.456; sendEventAppleAppStoreReceipt("Event Apple Receipt", mapObject, "base64encodedreceipt"); //Send an event that contains a Google Play Receipt. var mapObject = {}; mapObject["item"] = "value"; mapObject["item2"] = 123; mapObject["item3"] = 333.456; sendEventGooglePlayReceipt("Event Google Play Receipt", mapObject, "receipt json", "receipt data signature"); //Send a deeplink event. sendDeepLink("open url", "source app"); //Set app limit ad tracking to true or false. setAppLimitAdTracking(true); //Set identity link key/value pairs. var mapObject = {}; mapObject["id_link_1"] = "123456789"; mapObject["id_link_2"] = "aaaa-bbbb"; sendIdentityLink(mapObject);
WebView Javascript (HTML)
Define any desired functions within your WebView’s HTML. In the following example, the functions which mirror the most common SDK usage are defined. Place this Javascript code within your WebView HTML.
function sendEventString(nameString, infoString) { postUrl('KochavaTracker://sendEventString?nameString='+encodeURIComponent(nameString)+'&infoString='+encodeURIComponent(infoString)); } function sendEventMapObject(nameString, mapObject) { postUrl('KochavaTracker://sendEventMapObject?nameString='+encodeURIComponent(nameString)+'&mapObject='+encodeURIComponent(JSON.stringify(mapObject))); } function sendEventAppleAppStoreReceipt(nameString, mapObject, appStoreReceiptBase64EncodedString) { postUrl('KochavaTracker://sendEventAppleAppStoreReceipt?nameString='+encodeURIComponent(nameString)+'&mapObject='+encodeURIComponent(JSON.stringify(mapObject))+'&appStoreReceiptBase64EncodedString='+encodeURIComponent(appStoreReceiptBase64EncodedString)); } function sendDeepLink(openURLString, sourceApplicationString) { postUrl('KochavaTracker://sendDeepLink?openURLString='+encodeURIComponent(openURLString)+'&sourceApplicationString='+encodeURIComponent(sourceApplicationString)); } function setAppLimitAdTracking(appLimitAdTrackingBool) { postUrl('KochavaTracker://setAppLimitAdTracking?appLimitAdTrackingBool='+encodeURIComponent(appLimitAdTrackingBool)); } function sendIdentityLink(mapObject) { postUrl('KochavaTracker://sendIdentityLink?mapObject='+encodeURIComponent(JSON.stringify(mapObject))); } function postUrl(url) { var i = document.createElement('iframe'); i.style.display = 'none'; i.onload = function() { i.parentNode.removeChild(i); }; i.src = url; document.body.appendChild(i); }
Apple iOS, tvOS Implementation
Add the native code which interacts with the WebView. In order to intercept and process actions from the WebView, use the following example approach. In your UIViewController, conform to protocol WKNavigationDelegate, and implement the callback for the WKNavigationDelegate instance method webView(_: WKWebView, decidePolicyFor: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) as follows:
Sample Swift Code:
class ViewController: UIViewController, WKNavigationDelegate { // MARK: - DELEGATE CALLBACKS // MARK: WKNavigationDelegate func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { if !self.webView(webView, kochavaDidDecidePolicyFor: navigationAction, decisionHandler: decisionHandler) { // ... additional logic may be placed here decisionHandler(.allow) } } func webView(_ webView: WKWebView, kochavaDidDecidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) -> Bool { // VALIDATION ELSE RETURN // url guard let url = navigationAction.request.url else { return false } // lowercasedURLString let lowercasedURLString = url.absoluteString.lowercased() guard lowercasedURLString.hasPrefix("kochavatracker://") else { return false } // urlComponents guard let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return false } // MAIN // nameString let nameString = urlComponents.queryItems?.first(where: { $0.name == "nameString" })?.value // infoString let infoString = urlComponents.queryItems?.first(where: { $0.name == "infoString" })?.value // infoDictionaryString let infoDictionaryString = urlComponents.queryItems?.first(where: { $0.name == "mapObject" })?.value // infoDictionaryData let infoDictionaryData = infoDictionaryString?.data(using: .utf8, allowLossyConversion: false) // infoDictionary var infoDictionary: [AnyHashable: Any]? = nil if infoDictionaryData != nil { do { try infoDictionary = JSONSerialization.jsonObject(with: infoDictionaryData!, options: JSONSerialization.ReadingOptions.mutableContainers) as? [AnyHashable: Any] } catch { print("Error: infoDictionaryData could not be serialized") } } // appStoreReceiptBase64EncodedString let appStoreReceiptBase64EncodedString = urlComponents.queryItems?.first(where: { $0.name == "appStoreReceiptBase64EncodedString" })?.value // openURLString let openURLString = urlComponents.queryItems?.first(where: { $0.name == "openURLString" })?.value // openURL var openURL: URL? = nil if (openURLString != nil) { openURL = URL(string: openURLString!) } // appLimitAdTrackingBoolString let appLimitAdTrackingBoolString = urlComponents.queryItems?.first(where: { $0.name == "appLimitAdTrackingBool" })?.value // appLimitAdTrackingBool let appLimitAdTrackingBool = ( (appLimitAdTrackingBoolString != nil) && (appLimitAdTrackingBoolString != "false") && (appLimitAdTrackingBoolString != "0") ) // KVATracker // ⓘ Perform action. if lowercasedURLString.hasPrefix("kochavatracker://sendevent") { let event = KVAEvent(type: .custom) event.customEventNameString = nameString event.infoString = infoString event.infoDictionary = infoDictionary event.appStoreReceiptBase64EncodedString = appStoreReceiptBase64EncodedString event.send() } else if lowercasedURLString.hasPrefix("kochavatracker://senddeeplink") { KVADeeplink.process(withURL: openURL) { deeplink in // ... do something with the processed deeplink } } else if lowercasedURLString.hasPrefix("kochavatracker://setapplimitadtracking") { KVATracker.shared.appLimitAdTrackingBool = appLimitAdTrackingBool } else if lowercasedURLString.hasPrefix("kochavatracker://sendidentitylink") { if let infoDictionary = infoDictionary { for (key, value) in infoDictionary { if let nameString = key as? String, let identifierString = value as? String { KVATracker.shared.identityLink.register( withNameString: nameString, identifierString: identifierString ) } } } } // decisionHandler // ⓘ Call passing .cancel. A value of .cancel indicates to the block that the navigationAction should not be alllowed, because it was intended to send a message to Kochava. decisionHandler(.cancel) // return // ⓘ A value of true indicates to the caller that Kochava did decide the policy, and the decisionHandler should not be called again. return true } }
Usage
Now that the javascript functions have been defined and the native code within has been added within the app, call the javascript functions from within the WebView which will in turn call the corresponding methods from the SDK.
Example Code:
//Send an event with a string name and string parameters. sendEventString("Event String", "{\"param1\":\"value1\",\"param2\":true}"); //Send an event with a string name and a map object parameter. var mapObject = {}; mapObject["item"] = "value"; mapObject["item2"] = 123; mapObject["item3"] = 333.456; sendEventMapObject("Event Map Object", mapObject); //Send an event that contains an Apple Store Receipt. var mapObject = {}; mapObject["item"] = "value"; mapObject["item2"] = 123; mapObject["item3"] = 333.456; sendEventAppleAppStoreReceipt("Event Apple Receipt", mapObject, "appStoreReceiptBase64EncodedString"); //Send a deeplink event. sendDeepLink("open url", "source app"); //Set app limit ad tracking to true or false. setAppLimitAdTracking(true); //Set identity link key/value pairs. var mapObject = {}; mapObject["id_link_1"] = "123456789"; mapObject["id_link_2"] = "aaaa-bbbb"; sendIdentityLink(mapObject);