Advanced Topics
App Level Ad Tracking Opt-out
With the TUNE SDK, an app can allow its users to "opt-out" of app-level ad tracking. If a user decides to opt-out, then the TUNE SDK uses the install or conversion event only for ad reporting and not for ad targeting (such as by device, country, or language). By default, ad tracking is enabled (opt-in).
Android SDK Opt-out
To opt-out, call the following setter method:
setAppAdTrackingEnabled(false);
To opt-in (default), call the following setter method:
setAppAdTrackingEnabled(true);
iOS SDK Opt-out
To opt-out, call the following setter method:
[Tune setAppAdMeasurement:NO];
To opt-in (default), call the following setter method:
[Tune setAppAdMeasurement:YES];
Unity Plugin Opt-out
To opt-out, call the following setter method:
TuneExterns.TuneSetAppAdTracking(NO);
To opt-in (default), call the following setter method:
TuneExterns.TuneSetAppAdTracking(YES);
Attributing with UIID for iOS Apps in APAC
In many countries (such as Japan) across the Asia Pacific (APAC) region, a significant portion of advertising partners (publishers) use the Unique Installation Identifier (UIID) for identifying unique and distinct app installs on iOS. For information about UIID, please visit https://github.com/akisute/UIApplication-UIID#readme.
Similar to the other unique device identifiers that Attribution Analytics (AA) uses for mobile app attribution, when UIID is passed into the TUNE link on click (and the TUNE SDK is set up to collect the value on install), AA performs attribution using unique identifier matching.
UIID Collected in SDK on Install
To set up the TUNE iOS SDK for UIID collection, use our "setUIID" method.
파라미터 | 설명 | Android | iOS |
---|---|---|---|
UIID | The Unique Installation Identifier (UIID) of the device. Generate this value using the official implementation according to: https://github.com/akisute/UIApplication-UIID | 해당 없음 | setUIID: |
UIID Collected in TUNE Link
Partners who collect UIID must pass this value on click into the advertiser TUNE link. Specifically, partners pass the UIID into the "device_id_md5" parameter of AA:
&device_id_md5={UIID-value-here}
In the example above, replace "UIID-value-here" with the real UIID value that is 32 characters long, as shown in the following example TUNE link:
https://12345.api-01.com/serve?action=click&publisher_id=9558&site_id=2962&offer_id=237906&device_id_md5={UIID-value-here}
Automatic Collection of Device Geo Location
User device location information can provide you with important insights by allowing you to segment users based on their geographic location. To facilitate this, the TUNE iOS SDK 3.10.0+ and TUNE Android SDK 4.0.0+ automatically collects the user device geo location when a measure call is fired (ex. "measureSession", "measureEvent").
When our SDK detects the presence of the CoreLocation framework on iOS, or access location permissions on Android, it auto-collects the device location (only if the end-user allows your app to access the device location). If you explicitly call "setLocation:", then the SDK uses the provided location during the app session and then stops auto-collecting the location.
This feature does not force you to include CoreLocation framework or Android location permissions, if your app does not need it otherwise.
Prompt for Device Location
Even when location is present, the TUNE SDK never prompts the end-user for permission to access location information. It is the responsibility of your app to prompt the user for permission to access location information.
If the user does not enable location, then there is no location for the SDK to collect. If the user enables location but does not allow your app to collect the device’s location, then again, the SDK cannot collect the location.
To help conserve battery, the SDK accesses the device location only once when an event measurement call is fired. The auto-collected location info includes latitude, longitude, and altitude. On iOS, it also collects horizontal accuracy, vertical accuracy, and timestamp — properties from CoreLocation.CLLocation.
You can call a setter to control the SDK device location auto-collection behavior:
Android SDK Location Access
void setShouldAutoCollectDeviceLocation(boolean autoCollect)
iOS SDK Location Access
+ (void)disableLocationAutoCollection:(BOOL)autoCollect;
Unity Plugin Location Access
Tune.SetShouldAutoCollectDeviceLocation(shouldAutoCollect);
Avoiding the Dalvik 65K Method Limit
Android APKs have a limit of 65K methods for a single DEX file. This limit can easily be reached when including the Google Play Services SDK required by the TUNE Android SDK, as the Google Play Services SDK alone holds over 20K methods.
There are several ways to reduce the amount of methods added to your app. When working with third-party libraries, you can include only the methods your app actually uses during the build process. The TUNE SDK only requires the Ads library in the Google Play Services SDK, in order to access the Google Advertising ID.
ProGuard
We recommend using ProGuard to strip any unused methods during compilation. To set up ProGuard for Google Play Services, add to your <project_directory>/proguard-project.txt
file:
-keep class * extends java.util.ListResourceBundle {
protected Object[][] getContents();
}
-keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable {
public static final *** NULL;
}
-keepnames @com.google.android.gms.common.annotation.KeepName class *
-keepclassmembernames class * {
@com.google.android.gms.common.annotation.KeepName *;
}
-keepnames class * implements android.os.Parcelable {
public static final ** CREATOR;
}
Please note that ProGuard will apply only for release builds.
Selective Gradle Compilation
For a Gradle build, you may also selectively include only the required Ads API from the SDK, starting from Google Play Services version 6.5+.
In your build.gradle
file, include only the "play-services-ads" library as a dependency:
compile 'com.google.android.gms:play-services-ads:7.5.0'
instead of
compile 'com.google.android.gms:play-services:7.5.0'
Multidex
You may consider configuring your app as a multidex build to circumvent the 65K limit if you are still above the limit.
Collecting Device, Brand, Model and OS
Android SDK
Device Brand
To collect these values from your SDK (so you can include them in TUNE links), use the following code snippets.
String deviceBrand = android.os.Build.MANUFACTURER;
Device Model
To collect these values from your SDK (so you can include them in TUNE links), use the following code snippets.
String deviceModel = android.os.Build.MODEL;
OS Version
To collect these values from your SDK (so you can include them in TUNE links), use the following code snippets.
String osVersion = android.os.Build.VERSION.RELEASE;
iOS SDK
Device Brand
To collect these values from your SDK (so you can include them in TUNE links), use the following code snippets.
NSString *deviceBrand = @"Apple";
Device Model
To collect these values from your SDK (so you can include them in TUNE links), use the following code snippets.
#import <sys/utsname.h>
struct utsname systemInfo;
uname(&systemInfo);
NSString *deviceModel = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
OS Version
To collect these values from your SDK (so you can include them in TUNE links), use the following code snippets.
NSString *osVersion = [[UIDevice currentDevice] systemVersion];
Creating Unique Package Names
If you have an Android app with a package name of "com.example" that you plan to release in multiple app stores (such as the Google Play store, the Amazon Appstore for Android, and the Samsung App Store/Samsung Galaxy Apps), then Attribution Analytics may not be able to properly attribute the install to the appropriate app because we attribute based on platform (iOS/Android) and package name (and all package names are the same in this case–"com.example").
All apps registered within Attribution Analytics MUST have unique package names (else we cannot distinguish between them). If your app code is the same as another version registered with Attribution Analytics, both versions can have the same actual package name but one or both MUST have an Attribution Analytics-specific package name that is different from the other’s package name.
You specify the package name when you add your mobile app in Attribution Analytics. Then the package name is listed on the app details page in Attribution Analytics.
If your app code is completely new (or similar but different from another version), then it is not necessary to use an Attribution Analytics-specific package name because there will be no conflict between package names.
To use an Attribution Analytics-specific package name for your app:
Note
An Attribution Analytics-specific package name and conditional can be in any of your apps and app versions registered with Attribution Analytics.
- Create an app in Attribution Analytics for one of the apps with a slightly different package name (for example, the "Samsung version" of your app may have the Attribution Analytics-specific package name "com.example.samsung").
- In your app source code:
- Before the first TUNE measurement call, add a conditional that if the app is a Samsung build, the conditional calls the Tune method
setPackageName
(String). For example:setPackageName("com.example.samsung")
- Use your app’s REAL/functional package name when defining your Android manifest.
- Register your app with the appropriate app store (for example, the Samsung Seller Store).
- Add your app and its Attribution Analytics-specific package name to Attribution Analytics.
- Before the first TUNE measurement call, add a conditional that if the app is a Samsung build, the conditional calls the Tune method
During operations, the Tune call overrides the functional package name in the Tune object with the Attribution Analytics-specific package name. So when your app makes a measurement call, Attribution Analytics matches the call to your app via its Attribution Analytics-specific package name.
Now your Google, Amazon, and Samsung apps will attribute correctly to their own apps in Attribution Analytics, without any actual package name change.
Android SDK Unique Package Name
Tune.setPackageName("package_name");
iOS SDK Unique Package Name
[Tune setPackageName:@"package_name"];
Javascript SDK Unique Package Name
MobileAppTracker.setPackageName("package_name");
Windows SDK Unique Package Name
mobileAppTracker.SetPackageName("package_name");
Adobe Air Plugin Unique Package Name
mobileAppTracker.setPackageName("package_name");
Cocos2dx Plugin Unique Package Name
PluginTune::setPackageName("package_name");
PhoneGap Plugin Unique Package Name
Tune.setPackageName(successCallback, failureCallback, "package_name");
Unity Plugin Unique Package Name
Tune.SetPackageName("package_name");
Xamarin Plugin Unique Package Name
Android
Tune.Instance.SetPackageName("package_name");
iOS
Tune.SetPackageName ("your.pagage.name");
Deep Linking to Your Mobile App from Your Website
To use a deep link URL from your own mobile website into your mobile app, simply include some JavaScript in the head of your HTML page that opens your deep link URL on page load. If the user already has the app installed on their device, then their browser will recognize the URL scheme for your app and open your app to the specified screen!
Formatting the Deep Link URL
It is very important to make sure that when you try to open a deep link URL with JavaScript that the URL is properly formatted for the device and browser. If you do not use the appropriate deep link URL for the browser/platform, then a user may be redirected to a "Page Not Found".
For information on how to create properly formatted Invoke URLs, reference the technical documentation provided by the native platforms themselves.
Android
On Android, reference how to set a URL data scheme in an intent filter:
https://developer.android.com/training/basics/intents/filters.html
Learn how to read the data from the intent when your app is launched:
https://developer.android.com/guide/topics/manifest/data-element.html
If your Application and Activities are instrumented according to our Android Quick Start guide, then the opened url should automatically be recorded as the referral source for the app launch.
However, note that Chrome on Android has a different URL format than the standard Android browser:
https://developer.chrome.com/docs/multidevice/android/intents/
iOS
For iOS, reference Defining a Custom URL Scheme for Your App for information on how to set up URL schemes and handle deep links in your app delegate.
Example Code
Below is example code that first tries to open the app for existing users (with the app already installed) and redirect new users to download the app via a TUNE link to the app store.
You should only run the Javascript code for your mobile users or only for the mobile platform that you have an app for.
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width,minimum-scale=1.0, maximum-scale=1.0" />
<title>Site Name</title>
<style>@media screen and (max-device-width:480px){body{-webkit-text-size-adjust:none}}</style>
<!-- implement javascript on web page that first first tries to open the deep link
1. if user has app installed, then they would be redirected to open the app to specified screen
2. if user doesn't have app installed, then their browser wouldn't recognize the URL scheme
and app wouldn't open since it's not installed. In 1 second (1000 milliseconds) user is redirected
to download app from app store.
-->
<script>
window.onload = function() {
<!-- Deep link URL for existing users with app already installed on their device -->
window.location = 'yourapp://app.com/?screen=xxxxx';
<!-- Download URL (TUNE link) for new users to download the app -->
setTimeout("window.location = 'http://hastrk.com/serve?action=click&publisher_id=1&site_id=2';", 1000);
}
</script>
</head>
<body>
<!-- button to Download App for new app users -->
<form action="http://hastrk.com/serve?action=click&publisher_id=1&site_id=2" target="_blank">
<input type="submit" value="Download" />
</form>
<!-- button to Open App to specific screen for existing app users -->
<form action="yourapp://app.com/?screen=xxxxx" target="_blank">
<input type="submit" value="Open App" />
</form>
</body>
</html>
Detecting iOS Jailbroken Devices
iOS SDK version 2.3+ has enhanced functionality to detect jailbroken devices. When a device is jailbroken, it's a good indication that purchases made by these devices may not be valid.
Reject Install / Events from Jailbroken Devices
Since a jailbroken device is a good indicator for an invalid user (for example, a user who did not actually purchase your app, but uses a pirated or cracked version), our Support team can enable a setting on your account that rejects installs and events from jailbroken devices. Once enabled, these rejected installs and events from jailbroken devices are logged as rejected in the install and event logs (and the aggregated mobile app and publisher reports do not include the information from these rejected installs and events).
Determining if Device is Jailbroken
The TUNE iOS SDK uses three methods to determine if a device is jailbroken. When the SDK communicates with the TUNE platform, it includes information about the jailbreak status of an iOS-based device. The following methods to detect jailbroken devices:
-
Presence of file paths of some commonly used hacks
If there are presence of any of the following file paths, it indicates a jailbroken device:
- /Applications/Cydia.app
- /Applications/blackra1n.app
- /Applications/FakeCarrier.app
- /Applications/Icy.app
- /Applications/IntelliScreen.app
- /Applications/MxTube.app
- /Applications/RockApp.app
- /Applications/SBSettings.app
- /Applications/WinterBoard.app
- /Library/MobileSubstrate/DynamicLibraries/LiveClock.plist
- /Library/MobileSubstrate/DynamicLibraries/Veency.plist
- /private/var/lib/apt
- /private/var/lib/cydia
- /private/var/mobile/Library/SBSettings/Themes
- /private/var/stash
- /private/var/tmp/cydia.log
- /System/Library/LaunchDaemons/com.ikey.bbot.plist
- /System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist
- /usr/bin/sshd
- /usr/libexec/sftp-server
- /usr/sbin/sshd
-
Presence of shell access
Since non-jailbroken iOS devices do not have shell access, the presence of shell access indicates a jailbroken device.
-
Non-existence of standard framework at the expected file path
If the standard Foundation framework does not exist at the expected file path (
/System/Library/Frameworks/Foundation.framework/Foundation
), then this absence indicates a jailbroken device.
OS Jailbroken in Reporting
In reporting, you can group, view, and filter by the jailbreak status of a device. In the Edit options on most reports, use the OS Jailbroke parameter as shown in the following screenshot (the report returns Yes for Jailbroken devices, and No for non-jailbroken devices):
In the Actuals and Cohort Report, you can group, view, and filter on the OS Jailbroke parameter. The following shows installs and events from devices that are jailbroken. In general, the installs, events, and revenue associated with jailbroken devices is a good indication of fraud.
In the Installs, Updates, and Event Log Reports, you can also view and filter on the OS Jailbroke parameter.
Finding Your Advertiser ID and Conversion Key
The SDK requires passing in your TUNE Advertiser ID and app Conversion Key as initialization parameters. You can find these values in the Branch dashboard.
-
Log in to the Branch Attribution Analytics dashboard.
-
In the navigation panel (on the left side), click Mobile Apps link to open the list of Mobile Apps.
-
Get the advertiser id and conversion key using one of the following two ways:
- Click a mobile app title from the list (on the right side) to open the app’s Details page. Under the Details section, you will find the Advertiser ID and Conversion Key fields.
- Click the Download SDK button placed above the Mobile Apps list (on the right hand side). When the Download SDK popup shows up select the target app using the drop-down list. Under the Tracking Info section, you will find the Advertiser ID and Conversion Key fields.
-
Pass the above Advertiser ID and Conversion Key values to the Tune constructor in your SDK implementation.
Handling Existing Non-TUNE SDK Implementations
If you're ready to implement the software development kit (SDK) for TUNE but have existing implementations (other publisher SDKs) configured for conversion measurement, then implement the TUNE SDK first before you remove the other publisher SDKs. While you can implement the TUNE SDK alongside other SDKs without conflict, after you begin using TUNE links in your campaigns with other publishers, TUNE attributes those installs and logs them accordingly so you no longer need to manage the other publisher SDKs.
To implement the TUNE SDK in parallel with your other publisher SDKs:
- Implement the TUNE SDK and also keep your existing publisher SDKs implemented.
- Release your mobile app using the TUNE SDK while preserving your existing publisher SDK implementations.
- Start working with your new publishing partners.
As you get comfortable using TUNE, feel free to remove unnecessary publisher SDKs for which you've created server postbacks for conversion measurement.
To remove other publisher SDKs but keep the TUNE SDK:
- Update your campaigns to use the publisher-specific TUNE links generated in your Branch account.
- Set up server postback to notify publishers of installs and other events.
- Confirm that the TUNE SDK is configured and functioning appropriately: the TUNE links generate the proper events and your own internal system receives the server postback notifications successfully.
- Remove the appropriate publisher SDK from your mobile app.
- Release your mobile app with the TUNE SDK only (after you confirm that you no longer need the other publisher SDKs).
Handling Existing Users Prior to SDK Implementation
Any time the TUNE SDK sees a user we've never seen before, TUNE logs a new install record for said user. While this works perfectly for new apps being introduced to the app stores, it presents a problem for existing apps in the app store, but new to TUNE. If your mobile app already has an established user base before you implement the TUNE SDK, you will encounter issues with false positives – i.e. "new installs" that are in fact not new to your app; only new to TUNE.
To avoid unnecessary discrepancies and not attribute existing users as new installs when the open your updated app with the TUNE SDK, we recommend you select one of the following methods to inform TUNE about your pre-existing users.
Provide an "Onboarding Date"
The TUNE SDK (Android v4.5+ & iOS v4.9+) can determine when your app was first installed on a given device using the creation datetime (insdate) of the app's install directory for Android and the "original_install_date" field from the Apple install receipt for iOS. By providing TUNE with a preset "onboarding date", we can inform the TUNE Measurement Engine such that any install record with an "insdate" value before the "onboarding_date" value will automatically be marked as an "existing_user" and made "Organic".
We recommend that you set the Onboarding Date to be the day when the app will first be published to the app store with the TUNE SDK included.
Install Data Import Tool
If you know the Advertising Identifiers of your app's pre-existing users that you want to exclude from attribution, you can use our data import tool to register those attributions in the system directly. This import tool will accept a CSV file with the following information on each row:
- CSV file per mobile app (site), per identifier type:
- google_aid
- ios_ifa
- android_id
- publisher_id (optional)
For more detailed information on using the Install Data Import Tool, please see Importing Install Data to TUNE.
setExistingUser SDK Flag
Important
The
setExistingFlag
is only an indicator to tell TUNE that the user is pre-existing. The TUNE SDK does not handle the logic for determining your app's pre-existing users itself; this logic must come from your own internal system.
To flag users as pre-existing in Attribution Analytics, first call setExistingUser(true)
for Android before your first Activity resumes or call setExistingUser:YES
for iOS before calling measureSession
.
For any install where this flag has been set, the new install will be recorded as "Organic" (i.e. not attributed to an advertising partner) in reports and flagged in our database as a pre-existing user. This will give us an install date that we will use for cohorts and other future reports.
Calling setExistingUser before session is measured
When implementing the SDK, you need to know if a user is installing the app for the first time (or if the user is updating the app). Typically, app developers set a preference on the user’s device at the initial install, thereby making the preference present when the user updates their app. When the app developer knows that the user has already installed their app, they can call setExistingUser(true)
or setExistingUser:YES
.
setExistingUser
with Android SDK
setExistingUser
with Android SDKBoolean isExistingUser = ... // true or false
Tune.getInstance().setExistingUser(isExistingUser);
setExistingUser
with iOS SDK
setExistingUser
with iOS SDKBOOL isExistingUser = ... // YES or NO
[Tune setExistingUser:isExistingUser];
[Tune measureSession];
setExistingUser
with Javascript SDK
setExistingUser
with Javascript SDKmobileAppTracker.setExistingUser(true);
mobileAppTracker.measureSession();
setExistingUser
with Windows SDK
setExistingUser
with Windows SDKmobileAppTracker.SetExistingUser(true);
mobileAppTracker.MeasureSession();
setExistingUser
with Cocos2dx Plugin
setExistingUser
with Cocos2dx PluginPluginParam pExistingUser(true);
_pluginMAT-&callFuncWithParam("setExistingUser", &pExistingUser, NULL);
_pluginMAT-&callFuncWithParam("measureSession", NULL);
setExistingUser
with PhoneGap Plugin
setExistingUser
with PhoneGap PluginmobileAppTracker.setExistingUser(successCallback, failureCallback, true);
mobileAppTracker.measureSession(successCallback, failureCallback);
setExistingUser
with Unity Plugin
setExistingUser
with Unity PluginTune.SetExistingUser(true);
Tune.MeasureSession();
setExistingUser
with Xamarin Plugin
setExistingUser
with Xamarin PluginAndroid
MobileAppTracker.Instance.SetExistingUser(true);
MobileAppTracker.Instance.MeasureSession();
iOS
Tune.SetExistingUser (true);
Tune.MeasureSession ();
Viewing Pre-Existing Users in the Logs Report
To verify that your pre-existing users have been imported correctly:
- Go to the Logs report.
- Click on the Installs tab.
- Click the Configure Report button.
- Check off the Pre-Existing User attribute.
- Click Apply.
Disabling/Enabling Install Attribution for Pre-Existing Users
By default, install attribution is disabled for any user that has been flagged as pre-existing. You can enable install attribution for pre-existing users by clicking on Data Sharing under the Partners tab, toggling the setting to "On" and clicking Update. You can see if the setting is On or Off as the option is green as shown in the following screenshot (which shows the setting is currently Off).
Implementing a Deferred Deep Link
While standard deep links work great for redirecting your existing users (who already installed your app) to a specific screen or page within your app, they do not work for new users because new users get redirected to an app store to download the advertised app (then on first app open, these users see the app's default splash screen instead of the specified deep link).
With deferred deep linking, TUNE persists the deep link in an internal deep link database/registry and makes it available for app open events. So whether users are existing or new, each app open event initiates a lookup to retrieve a deferred deep link (if available). This persistence and retrieval service enables new users to reach a particular deep link after app install.
There are two ways to implement deferred deep linking:
- Through the TUNE SDK (as described on this page)
- Through server-to-server API calls (as described at Retrieving Deferred Deep Links from API)
To enable deferred deep linking, the following are required:
- Your account is enabled for deferred deep linking.
- Configure deep link URLs for each page in your app (where ad clicks will link to).
- Create links that include a configured deep link URL.
For more information about response methods, see Reading Server Responses.
Android SDK Deek Links
Tune.getInstance().registerDeeplinkListener(new TuneDeeplinkListener() {
@Override
public void didReceiveDeeplink(String deeplink) {
// Handle the deferred deeplink here
// e.g. open the link
if (!"".equals(deeplink)) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(deeplink)));
}
}
@Override
public void didFailDeeplink(String error) {
// Error was encountered
}
});
iOS SDK Deep Links
- Your app must be integrated with our iOS SDK 4.12.0+.
- Your app must implement a standard URL scheme or Universal Links for specific pages or screens in your app.
- The call to registerDeeplinkListener: must include an instance of a class that conforms to TuneDelegate protocol and implements the tuneDidReceiveDeeplink: and tuneDidFailDeeplinkWithError: methods
[Tune registerDeeplinkListener:self];
/// Deferred Deep Linking
- (void)tuneDidReceiveDeeplink:(nullable NSString *)deeplink {
NSLog(@"Tune deferred deeplink = %@", deeplink);
if(deeplink) {
//When the app is opened due to a deep link, call the Tune deep link setter
[Tune handleOpenURL:[NSURL URLWithString:deeplink] sourceApplication:nil];
}
//Handle opening deep link
}
- (void)tuneDidFailDeeplinkWithError:(nullable NSError *)error {
NSLog(@"Tune failed to receive deferred deep link: error = %@", error);
}
Adobe Air Plugin Deep Links
-
Your app must be integrated with our Adobe AIR plugin 4.1.0+.
-
The Tune Adobe AIR plugin does not auto-open the deferred deep link url.
-
In the same script file, add your code to handle the deep link in the following callbacks:
private function tuneDeeplinkCallback(event:StatusEvent):void {
trace(event.code + "::" + event.level);
// Pass deferred deep link value to MAT
mat.setDeepLink(event.level);
// Handle deferred deep link here
}
private function tuneDeeplinkFailedCallback(event:StatusEvent):void {
trace(event.code + "::" + event.level);
}
PhoneGap Plugin Deep Links
- Your app must be integrated with our Cordova plugin 4.1.0+.
- Your app code must include the following call made after initializing the MobileAppTracker:
window.plugins.matPlugin.checkForDeferredDeeplink(function(deeplink) {
console.log('deferred deeplink success: ' + deeplink);
// TODO: add your code to handle the deep link url as appropriate
},
function(error) {
console.log('deferred deeplink failed: ' + error);
}
);
Unity Plugin Deep Links
- Your app must be integrated with our Unity plugin 3.11.0+.
- Your app code must include the following call made after initializing the TUNE SDK:
mat.checkForDeferredDeeplink(function(result) {
var deepLink = result['deeplink'];
var error = result['error'];
console.log('deep link is ' + deepLink);
console.log('deep link error is ' + error);
if(deepLink != null) {
setDeepLink(deepLink);
}
});
- Your Unity project must have a GameObject called TuneListener with TuneListener.cs attached. To add this, you may choose TUNE → Setup → Create TuneListener GameObject from your Unity menu.
- In TuneListener.cs, add your code to handle the deep link in the following callbacks:
public void trackerDidReceiveDeeplink (string url)
{
print ("TuneListener trackerDidReceiveDeeplink: " + url);
// Let Unity open the deep link
Application.OpenUrl(url);
// TODO: You may alternatively add your custom code to handle the deferred deep link url directly here
// If so, call the following line to inform TUNE of the deep link that was opened
// Tune.SetDeepLink(url);
}
public void trackerDidFailDeeplink (string error)
{
print ("TuneListener trackerDidFailDeeplink: " + error);
}
If you're not using the Unity Application.OpenUrl() method to handle the deep link, you can directly call the setter method provided by the TUNE plugin:
Tune.SetDeepLink (deep_link_url);
After calling this method to set the deep link, TUNE includes the deep link information in all measurement requests in the current session.
Example: Setting Deferred Deep Link when using the Facebook Unity Plugin
If you're implementing deferred deep linking through the Facebook Unity plugin, you can use the following sample code to retrieve deferred deep links:
FB.Mobile.FetchDeferredAppLinkData(DeepLinkCallback);
Sample implementation of Facebook Unity plugin deferred deep link callback:
void DeepLinkCallback(IAppLinkResult result) {
if (!String.IsNullOrEmpty(result.Url)) {
Debug.Log(result.Url);
Tune.SetDeepLink (result.Url);
}
}
For more information about deferred deep linking with the Facebook Unity plugin, visit https://developers.facebook.com/docs/unity/reference/current/FB.Mobile.FetchDeferredAppLinkData.
Xamarin Plugin Deep Links
Android
- Your app must be integrated with our Xamarin binding 4.0.0+.
- Your app code must include the following calls made after initializing Tune:
using TuneSDK;
public class TuneDeeplinkListener : Java.Lang.Object, ITuneDeeplinkListener
{
public TuneDeeplinkListener ()
{
}
public void DidReceiveDeeplink(string deeplink)
{
Console.WriteLine ("TUNE DidReceiveDeeplink: " + deeplink);
// Pass deferred deeplink value to TUNE
Tune.Instance.ReferralUrl = deeplink;
// TODO: handle deeplink redirection
}
public void DidFailDeeplink(string error)
{
Console.WriteLine ("TUNE DidFailDeeplink: error = " + error);
}
}
TuneDeeplinkListener listener = new TuneDeeplinkListener();
- In the listener callbacks, handle opening the deferred deep link returned. After TUNE has been init, invoke the call to get a deferred deep link:
// Check for deferred deep link
Tune.Instance.checkForDeferredDeeplink(listener);
iOS
- Your app must be integrated with our Xamarin binding 5.1.0+.
- Create a subclass of TuneDelegate:
public class SampleTuneListener : TuneDelegate
{
public override void TuneDidReceiveDeeplink (string deeplink)
{
Console.WriteLine ("TUNE DidReceiveDeeplink: deeplink = " + deeplink);
// handle opening link
}
public override void TuneDidFailDeeplinkWithError (NSError error)
{
Console.WriteLine ("TUNE DidFailDeeplinkWithError: error = " + error.Code + ", " + error.LocalizedDescription);
}
}
- In the listener callbacks, handle opening the deferred deep link returned. After TUNE has been init, invoke the call to get a deferred deep link:
SampleTuneListener listener = new SampleTuneListener();
Tune.RegisterDeeplinkListener(listener);
Implementing iOS Universal Links
This section provides instructions for configuring your app and web server to support Universal Links.
For additional information about implementing Universal Links, you can visit Support Universal Links on the Apple Developer web site.
On the Apple Developer Tools Site
Visit developer.apple.com and register your app to establish an App ID. Then enable Associated Domains so that your Xcode project can support Universal Links.
- Register your app with Apple to establish an App ID:
- On the Apple Developer Member Center home page, click Certificates, Identifiers & Profiles.
- On the Certificates, Identifiers & Profiles page, under the iOS Apps column, click Identifiers.
- On the iOS App IDs page, click Register your App ID.
- On the Registering an App ID page, enter an App ID Description/Name, enter your app's Bundle ID, and then click Continue.
- On the Confirm your App ID page, click Submit.
- On the Registration complete page, click Done.
- Enable Associated Domains for your App ID:
- On the iOS App IDs page, select your App ID, and then click Edit.
- On the iOS App ID Settings page, check the box for Associated Domains.
- In the confirmation dialog box, click OK.
- On the iOS App ID Settings page, click Done.
The result of completing these steps is that your app/Xcode project now supports Universal Links. In the next section, you'll associate your Xcode project with a TUNE web domain.
In Your Xcode Project
Add a "com.apple.developer.associated-domains" entitlement to register your app with Apple as being capable of handling Universal Links; the entitlement lists all the domains that your app can handle as Universal Links.
- Enable Associated Domains:
- Load your app project in Xcode.
- In the left pane treeview, click the project.
- In the right pane, click the Capabilities tab.
- On the Capabilities tab, enable Associated Domains by clicking the toggle to On (blue color).
- Add a Domain Entitlement:
- Find your subdomain in your Attribution Analytics account:
- In the left hand navigation menu, in the Applications section, click on Mobile Apps.
- Find and click on the name of the appropriate mobile app.
- In the Details box, click Edit.
- On the Edit App page, in the Routing section, under the Universal Links section, copy and paste your Domain.
- In the Domains box, add all the domains that your app wants to handle as Universal Links. Prefix each domain with "applinks:.
- Include the entitlement file in your build: in the project browser, select the entitlement file for membership to the appropriate targets.
- Find your subdomain in your Attribution Analytics account:
The result of completing these steps is that your domain now supports Universal Links. In the next section, you'll configure your web server to support Universal Links.
In Your AppDelegate
Include logic for handling Universal Links: this code only runs if users have your app installed (else they get the mobile web version). So provide a mapping that correlates Universal Links with specific app screens.
For example, the sample code below handles receiving a Universal Link:
// Capturing Universal Links
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * _Nullable))restorationHandler {
// Check if Tune will handle the Universal Link. If so, the deeplink will be handled in your tuneDidReceiveDeeplink: or tuneDidFailDeeplinkWithError: callback
BOOL handledByTune = [Tune handleContinueUserActivity:userActivity restorationHandler:restorationHandler];
// If handledByTune is false, handle the routing yourself
if (!handledByTune) {
...
}
return YES;
}
After setting up the Universal Link call, implement registerDeeplinkListener: with a TuneDelegate implementation after your TUNE SDK init call. The implementation must implement the methods tuneDidReceiveDeeplink: and tuneDidFailDeeplinkWithError:
For example, you can have your app delegate implement the following TuneDelegate callbacks:
- (void)tuneDidReceiveDeeplink:(nullable NSString *)deeplink {
NSLog(@"TUNE.deeplink: %@", deeplink);
// Handle deep link redirect here...
}
- (void)tuneDidFailDeeplinkWithError:(nullable NSError *)error {
NSLog(@"TUNE.deeplink failed: %@", [error localizedDescription]);
}
Then register itself as a listener:
[Tune registerDeeplinkListener:self];
Creating a Universal Link in Attribution Analytics
Once your iOS app is set up to handle universal links, you'll need to enable the Universal Link setting in Attribution Analytics, then follow the standard process for creating a TUNE link in Attribution Analytics.
TUNE links support iOS Universal Link functionality as long as the above setting is enabled and routing is set up properly in your app. When your TUNE link (with Universal Link functionality enabled) is successfully implemented, users who click it are directed to the appropriate destination at the OS level instead of at the app level. No browser app is launched to perform app detection and routing logic, as routing is handled by iOS itself.
Importing Install Data to TUNE
Any time the SDK sees a user we've never seen before, TUNE logs a new install record for that user. While this works optimally for new apps being introduced to the app stores, it presents a problem for existing apps in the app store, but new to TUNE. If your app already has an established user base before you implement the TUNE SDK, you will encounter issues with false positives (new installs) that are actually not new to your app, only new to TUNE.
To avoid unnecessary discrepancies and not attribute existing users as new installs when opening your updated app with the TUNE SDK, use the Install Data Import Tool to inform TUNE about your pre-existing users. To learn about other methods TUNE employs to handle your existing users, please see Handling Existing Users Prior to SDK Implementation.
Important
Please send Branch the data exports from your former attribution solution or your in-house database immediately and before going live with the TUNE SDK in your app(s). It is imperative to have the data import completed before installs start being sent to TUNE via the SDK.
If at any point you run into a snag, please email [email protected].
- Sign up for a Branch account with Attribution Analytics.
- Log in to the Branch dashboard.
- Set up any mobile apps for which you wish to import past data.
- For an iOS app, provide your app’s package name (iOS Bundle Identifier) and the store app ID.
Note
Your mobile app's Store App ID is provided by Apple and is completely different from the "App ID" in TUNE (after you add the mobile app).
- For an Android app, provide your app's package name and indicate if you have the Google Analytics SDK installed and you would like your TUNE links to include the Google Analytics campaign measurement parameters
- After completing the fields, click Create App & Continue
- App Name - Type the name of your App
- App Type - Select the platform that your app runs on (you can only select one at a time)
Note
You will need to add the same app more than once if it runs on multiple platforms.
-
Destination URL - Type the URL to where you want users direct (for downloading your app).
-
In the Create App dialog box, complete the following fields.
-
If you are only importing Device IDs skip to step 5. If you would like to import your data with partner source data as well, continue here:
- Before you will be able to create your CSV file for import, you will need to enable all of your partner integrations in TUNE to obtain the corresponding Publisher IDs
- Partner integrations can be enabled in the Branch dashboard by searching for the Partner and clicking Enable.
- If you want to add a Partner that is not integrated with TUNE, you may add them as a partner in the Branch dashboard.
- Once this process is finished, you will need to use the ID listed next to each partner to populate the publisher_id column in the CSV file to be imported.
-
Create a CSV file per app following these guidelines:
- One CSV file per app, per identifier type (one of the following identifiers per file):
google_aid
ios_ifa
android_id
publisher_id
(optional; if included, organic must use '0' or the row will be skipped during import)
The list of valid fields is below:
- One CSV file per app, per identifier type (one of the following identifiers per file):
Field | 설명 | Required? |
---|---|---|
google_aid | Google Advertising Identifier (GAID). Formatted as a GUUID with dashes, as returned by the device (38400000-8cf0-11bd-b23e-10b96e40000d). | one ID required |
ios_ifa | Apple Advertising Identifier (IDFA). Formatted as a GUUID with dashes, as returned by the device (3F2504E0-4F89-41D3-9A0C-0305E82C3301). | one ID required |
android_id | Android ID. A 64-bit number (as a hex string) that is randomly generated on the first boot of a device and typically remains constant for the lifetime of the device. | one ID required |
publisher_id | TUNE Publisher ID for the install’s attributed partner. If included, use ‘0’ for Organics. | 아니오 |
- Go to the list of Mobile apps in your Branch dashboard.
- Rename your CSV import file to include the "ID" listed next to the correct mobile app in the TUNE dashboard ("TUNEAppId.csv")
- Email [email protected] with your account manager and Sales Engineer CC'd with the file(s) attached or a link to your file(s) for download.
- Our support team will update you once the data has been imported.
Timestamp Options
You can also provide one UNIX timestamp in the support request for ALL of the imported data. When providing support with the UNIX timestamp, the created timestamp must be within 20 days prior to current timestamp. If the created timestamp is not set, the time of the import is used.
In-App Purchase Verification
The revenue reporting features contain advanced receipt validation mechanisms that independently verify every in-app purchase. This feature eliminates revenue from fraudulent purchases and ensures that the revenue number you see in our dashboard is the most accurate.
In-app purchases via Apple iTunes or Google Play will be measured as events in TUNE. The verification process for in-app purchases works by our SDK collecting the receipt data (iOS)/receipt IDs (Android) for the in-app purchases, after which the SDK notifies our platform of these in-app purchases with the purchase validation status set to pending. The platform then queries the store API to confirm if the in-app purchase was verified. Only verified in-app purchases are measured as approved events and revenue is subsequently incremented.
Verification Process
The verification process is a separate and independent process that happens after the event is measured and attributed. Due to this sequence, the verification process will never lag / delay the measuring and attribution processes.
Since our SDKs are set up to support the collection of the receipt data (iOS)/receipt IDs (Android) from Apple iTunes and Google Play, when implemented, these values are included when measuring an in-app purchase event. When a request to measure an event for a purchase includes a receipt (iOS)/receipt ID (Android), the MAT platform automatically tries to verify it with Apple iTunes or Google Play.
When TUNE measures the event with a receipt ID, the event's purchase validation status is set to "Pending." Then 60 minutes after the event is measured, it is queued in the verification process, which takes the receipt ID (Android) / receipt (iOS) and queries the Apple iTunes API or Google Play API to see if the purchase was successful or not.
If the purchase was successful, TUNE sets the event's purchase validation status to "Verified" and increments revenue accordingly. If the purchase was not successful, then MAT sets the event's purchase validation status to "Failed" and no revenue is incremented. This process also checks for and blocks duplicate purchases being sent based on receipt ID.
Handling Subscriptions
If you offer auto-renewable digital subscriptions in your iOS app, you need to provide your App Store Connect Shared Secret to enable TUNE to use for validation. Auto-renewable subscriptions allow users to purchases in-app content, and the purchase is automatically renewed at the end of the period unless the user chooses to cancel the subscription.
Android Setup
Google Play In-App Purchases with Android SDK
To measure in-app purchase events with Google Play, use our Android SDK 2.6+. To tie your events in Google Play’s In-App Billing system, implement a service that is bound to their billing system via these instructions.
The following code snippet shows a sample measureEvent call with a receipt id (purchase data). In your onActivityResult method, call our measureEvent function after receiving the "BUY_INTENT" intent, parsing the purchaseData and dataSignature values:
// Measure the purchase event with TUNE
// Note: Obtain the price and currency code from SkuDetails
TuneEvent purchaseEvent = new TuneEvent(TuneEvent.PURCHASE)
.withRevenue(revenue)
.withCurrencyCode(currencyCode)
.withContentId(purchase.getSku())
.withAdvertiserRefId(purchase.getOrderId())
.withDate1(new Date(purchase.getPurchaseTime()))
.withReceipt(purchase.getOriginalJson(), purchase.getSignature());
Tune.getInstance().measureEvent(purchaseEvent);
Since this sample code includes the receipt data returned from Google Play for the in-app purchase, TUNE automatically performs purchase verification on the server-side.
Important
You will need to set your Google Public Key in your Branch account in order for in-app purchases to be validated using the IAP receipt data. Once you have your Google Public Key, paste this value into your mobile app's detail page (Mobile Apps → Your Mobile App → Details Edit → Google Public Key).
By passing us the IAP receipt data, TUNE will perform purchase verification on the event.
Android Purchase Validation Codes
When we do the validation on the in-app purchase for Android, verified purchases have a purchase validation code of 0. Any other status code for Android there can be a few different errors that occur.
안드로이드 코드 | Purchase Validation Status | Explanation | Verified Revenue | Conversion Status |
---|---|---|---|---|
-3 | OpenSSL Error | Error with the OpenSSL call. | 아니오 | rejected |
*-2 | Unknown Error | Some error with our attempt occurred. | 아니오 | rejected |
-1 | No Receipt | No receipt to validate. | 아니오 | approved |
0 | Validated | We've validated this receipt successfully. | 예 | approved |
1 | Failed Validation | The data or the signature didn't validate. | 아니오 | rejected |
2 | Invalid Secret Key | The secret key is malformed. | 아니오 | rejected |
iOS Setup
iTunes In-App Purchases with the iOS SDK
Starting with v3.9, the TUNE iOS SDK supports automatic purchase event measurement for successful Apple iTunes in-app purchase (IAP) events. Once this feature is enabled, no other code is required to measure IAP purchase events. You can enable this feature by calling the following setter method after SDK initialization:
[Tune automateIapEventMeasurement:YES];
Automatic Measurement of IAP Events
The TUNE SDK already implements the SKPaymentTransactionObserver
protocol. So when automatic IAP event measurement feature is enabled, the SDK automatically calls measureEvent
for each individual successful payment transaction as follows:
TuneEvent *event = [TuneEvent eventWithName:@"purchase"];
event.currencyCode = currencyCode;
event.receipt = receipt;
event.eventItems = @[eventItem];
event.transactionState = transaction.transactionState;
[Tune measureEvent:event];
Once IAP event measurement is enabled/automated, you should not add explicit measureEvent
calls for successful IAP transactions, so as to avoid duplicate event measurement. If you are upgrading to 3.9+ from an older SDK version and want to automate IAP event measurement, then make sure you remove the measureEvent
calls that correspond to successful IAP transactions.
Manual Measurement of IAP Events
If you instead wish to manually measure the IAP purchase event, then you can create a TuneEvent instance, set its properties that relate to the IAP transaction, and then explicitly call measureEvent
after the purchase is successful.
The following code snippet shows a sample manual measureEvent
call where an In-App Purchase (IAP) transaction receipt is included:
SKProduct *product = ...; // product being purchased with the current transaction
SKPaymentTransaction *transaction = ...; // current in-app-purchase transaction
TuneEvent *event = [TuneEvent eventWithName:@"purchase"];
event.refId = transaction.transactionIdentifier;
event.revenue = revenue;
event.currencyCode = currencyCode;
event.receipt = receiptData;
event.transactionState = transaction.transactionState;
TuneEventItem *item = [TuneEventItem eventItemWithName:@"apple" unitPrice:1.5 quantity:2];
event.eventItems = @[item];
[Tune measureEvent:event];
When automatic IAP event measurement is not available (using older SDK versions 2.5-3.8.x) or disabled, you can explicitly measure "purchase" events for iTunes transactions by adding the measureEvent
methods to the paymentQueue:updatedTransactions:
delegate callback method provided by SKPaymentTransactionObserver
in the StoreKit framework. The following code shows a sample implementation.
- Logs success and failure transaction events. Does not log restore transaction event.
- Uses "purchase" as the default logging event name, but you can modify this name as necessary.
- Finds the currency identifier for the transaction and sends it with the logged event.
- Each event item includes the details (for example, product name, quantity, unit price, revenue, and optional attributes 1 through 5).
- Includes the transaction receipt for server-side verification with Apple.
- Assumes an automatic reference counting (ARC) iPhone project.
#pragma mark SKProductsRequestDelegate Methods
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
int countEventsMeasured = 0;
// handle each transaction
for (SKPaymentTransaction *transaction in transactions)
{
// default measurement event name
NSString *eventName = @"purchase";
// self.products is the dictionary (NString, SKProduct) to be created by the user
NSDictionary *dictProducts = self.products;
// get the product associated with this transaction
SKProduct *product = (SKProduct *)([dictProducts objectForKey:transaction.payment.productIdentifier]);
// assign the currency code extracted from the transaction
NSString *currencyCode = [product.priceLocale objectForKey:NSLocaleCurrencyCode];
if(nil != product)
{
// extract transaction product quantity
int quantity = transaction.payment.quantity;
// extract unit price of the product
float unitPrice = [product.price floatValue];
// assign revenue generated from the current product
float revenue = unitPrice * quantity;
// create MAT measurement event item
MATEventItem *eventItem = [MATEventItem eventItemWithName:product.localizedTitle unitPrice:unitPrice quantity:quantity revenue:revenue attribute1:@"attr1" attribute2:@"attr2" attribute3:@"attr3" attribute4:@"attr4" attribute5:@"attr5"];
NSArray *arrEventItems = @[ eventItem ];
BOOL shouldTrackEvent = false;
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
{
// purchase successful
NSLog(@"Purchase Transaction successful:");
shouldTrackEvent = true;
// mark the transaction as completed
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
case SKPaymentTransactionStateFailed:
{
// purchase failed
NSLog(@"Purchase Transaction Failed: error = %@", transaction.error);
Since this sample code includes the receipt data returned from iTunes for the in-app purchase, TUNE automatically performs purchase verification on the server-side.
iOS Purchase Validation Codes
When we do the validation on the in-app purchase for iOS, verified purchases have a purchase validation code of 0. If the purchase is not validated, however, there can be many reasons as to why as shown in the table below.
iOS 코드 | Purchase Validation Status | Explanation | Verified Revenue | Conversion Status |
---|---|---|---|---|
-3 | Invalid Bundle | Valid receipt but contains bundle id of a different site. | 아니오 | rejected |
-2 | Unknown Error | Some error with our attempt occurred. | 아니오 | rejected |
-1 | No Receipt | No receipt to validate. | 아니오 | approved |
0 | Validated | We've validated this receipt successfully. | 예 | approved |
21000 | Unreadable JSON object | The App Store could not read the JSON object you provided. | 아니오 | rejected |
21002 | Malformed receipt data | The data in the receipt-data property was malformed. | 아니오 | rejected |
21003 | Failed Validation | The receipt could not be authenticated. | 아니오 | rejected |
21004 | Invalid Shared Secret | The shared secret you provided does not match the shared secret on file for your account. | 아니오 | rejected |
21005 | Receipt Server Unavailable | The receipt server is not currently available | 아니오 | rejected |
21006 | IAP subscription expired | This receipt is valid but the subscription has expired. When this status code is returned to your server, the receipt data is also decoded and returned as part of the response. | 아니오 | rejected |
21007 | Sandbox receipt to Production | This receipt is a sandbox receipt, but it was sent to the production service for verification. | 아니오 | rejected |
21008 | Production receipt to Sandbox | This receipt is a production receipt, but it was sent to the sandbox service for verification. | 아니오 | rejected |
Including Tune iOS Objective-C SDK in a Swift project
If you’re creating an iOS project using the Swift programming language in Xcode 6.4+, you can use the existing Tune SDK 4.x.x for iOS (which is based on the Objective-C SDK framework).
To include the Tune SDK for iOS, drag-and-drop the Tune.framework on the project name in the Xcode Project Navigator panel. You can import Tune
module and other required frameworks in AppDelegate
and in any other class where you want to measure events.
import Tune
import AdSupport
import CoreTelephony
import iAd
import MobileCoreServices
import Security
import StoreKit
import SystemConfiguration
import UIKit
Add the following flags to "Other Linker Flags" in the Xcode project Build Settings.
-ObjC -lz
In AppDelegate.swift file, add the following:
#pragma mark SKProductsRequestDelegate Methods
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
int countEventsMeasured = 0;
// handle each transaction
for (SKPaymentTransaction *transaction in transactions)
{
// default measurement event name
NSString *eventName = @"purchase";
// self.products is the dictionary (NString, SKProduct) to be created by the user
NSDictionary *dictProducts = self.products;
// get the product associated with this transaction
SKProduct *product = (SKProduct *)([dictProducts objectForKey:transaction.payment.productIdentifier]);
// assign the currency code extracted from the transaction
NSString *currencyCode = [product.priceLocale objectForKey:NSLocaleCurrencyCode];
if(nil != product)
{
// extract transaction product quantity
int quantity = transaction.payment.quantity;
// extract unit price of the product
float unitPrice = [product.price floatValue];
// assign revenue generated from the current product
float revenue = unitPrice * quantity;
// create MAT measurement event item
MATEventItem *eventItem = [MATEventItem eventItemWithName:product.localizedTitle unitPrice:unitPrice quantity:quantity revenue:revenue attribute1:@"attr1" attribute2:@"attr2" attribute3:@"attr3" attribute4:@"attr4" attribute5:@"attr5"];
NSArray *arrEventItems = @[ eventItem ];
BOOL shouldTrackEvent = false;
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
{
// purchase successful
NSLog(@"Purchase Transaction successful:");
shouldTrackEvent = true;
// mark the transaction as completed
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
case SKPaymentTransactionStateFailed:
{
// purchase failed
NSLog(@"Purchase Transaction Failed: error = %@", transaction.error);
shouldTrackEvent = true;
// mark the transaction as completed
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
case SKPaymentTransactionStateRestored:
{
// purchase restored
NSLog(@"Purchase Transaction Restored:");
// mark the transaction as completed
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
case SKPaymentTransactionStatePurchasing:
default:
break;
}
NSLog(@"Event Item = %@", arrEventItems);
if(shouldTrackEvent)
{
// measure the purchase transaction event
// when overrideRevenue = 0, total revenue = sum of event item revenues
// when overrideRevenue != 0, total revenue = overrideRevenue
float overrideRevenue = 0; // default to zero
MATEvent *event = [MATEvent eventWithName:eventName];
event.eventItems = arrEventItems;
event.refId = transaction.transactionIdentifier;
event.revenue = overrideRevenue;
event.currencyCode = currencyCode;
event.transactionState = transaction.transactionState;
event.receipt = transaction.transactionReceipt;
[MobileAppTracker measureEvent:event];
NSLog(@"Transaction event measured: %@", eventName);
// increment the measured events count
++countEventsMeasured;
}
}
}
if(0 < countEventsMeasured)
{
NSString *alertMessage = [NSString stringWithFormat:@"%d in-app purchase transaction events measured.", countEventsMeasured];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"MobileAppTracker"
message:alertMessage
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[ alert show ];
}
}
let Tune_Advertiser_Id = "your_tune_advertiser_id"
let Tune_Conversion_Key = "your_tune_conversion_key"
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any] ? ) - > Bool {
// Override point for customization after application launch.
// Note: to set self as the delegate, this class needs to implement the TuneDelegate protocol
Tune.setDelegate(self)
// call one of the Tune init methods
Tune.initialize(withTuneAdvertiserId: Tune_Advertiser_Id, tuneConversionKey: Tune_Conversion_Key)
// Check if a deferred deeplink is available and handle opening of the deeplink as appropriate in the success tuneDidReceiveDeeplink: callback.
// Uncomment this line if your TUNE account has enabled deferred deeplinks
//Tune.registerDeeplinkListener(self)
// Uncomment this line to enable auto-measurement of successful in-app-purchase (IAP) transactions as "purchase" events
//Tune.automateIapEventMeasurement(true)
// If your app already has a pre-existing user base before you implement the Tune SDK, then
// identify the pre-existing users with this code snippet.
// Otherwise, TUNE counts your pre-existing users as new installs the first time they run your app.
// Omit this section if you're upgrading to a newer version of the Tune SDK.
// This section only applies to NEW implementations of the Tune SDK.
//var isExistingUser:Bool = ...
//if (isExistingUser) {
// Tune.setExistingUser(true)
//}
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
Tune.measureSession()
}
func application(_ application: UIApplication, open url: URL, sourceApplication: String ? , annotation : Any) - > Bool {
// when the app is opened due to a deep link, call the Tune deep link setter
Tune.handleOpen(url, sourceApplication: sourceApplication)
return true;
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
To measure an event, the measureEvent
method can be used, e.g.
event without event items
func loginComplete() {
Tune.measureEventName("login")
}
event with event items
func purchaseComplete() {
let item1 = TuneEventItem(name: "ball", attribute1: "red", attribute2: "medium", attribute3: "", attribute4: "", attribute5: "")
let item2 = TuneEventItem(name: "ball", attribute1: "blue", attribute2: "large", attribute3: "", attribute4: "", attribute5: "")
let event = TuneEvent(name: "purchase")
event.eventItems = [item1, item2]
event.refId = "1234567890"
Tune.measure(event!)
}
Using TUNE iOS SDK in Hybrid with Swift/Objective-C App
To use Tune to measure session from Swift code, (if your app is mostly in Objective-C with few Swift classes) you can create a bridging header and import Tune SDK to measure an event from the Swift code.
For more information on this topic, see Apple Developer's article, Importing Objective-C into Swift.
Manually Installing the TUNE Android SDK via AAR
Downloading the SDK
- Log in to the Branch dashboard to add your app to the Attribution Analytics platform.
- Once you've added your app, click Download SDK.
Installing the SDK
To manually install the latest TUNE SDK via the AAR distribution:
-
In Android Studio, go to File → New → New Module → Import .JAR/.AAR Package.
-
Select the TuneMarketingConsoleSDK-X.X.X.aar file and create the subproject.
-
Now that the
TuneMarketingConsoleSDK-X.X.X
subproject has been created in your project, include it in your app's build.gradle:
api project (':TuneMarketingConsoleSDK-X.X.X')
-
Include the Play Install Referrer library and import it into your project.
implementation 'com.android.installreferrer:installreferrer:1.0'
If you receive an error after adding this dependency, check that your top-level build.gradle contains a reference to:
maven { url "https://maven.google.com" }
-
Install the Android Support V4 library and import it into your project.
implementation 'com.android.support:support-v4:25+'
Manually Installing the TUNE iOS SDK
To install manually, follow the steps below:
- Unzip and copy the
Tune.framework
folder into your source tree. - Add the
Tune.framework
to your Xcode project:- At the top of the Project Navigator, click the project name.
- On the General tab, scroll down to the bottom to see (Linked Frameworks and Libraries).
- To add a new framework, click the "
+
" sign, and then click Add Other…. - Browse to and select the
Tune.framework
folder, and then click Open.

-
Add the following additional frameworks to your project:
AdSupport.framework
CoreLocation.framework
CoreSpotlight.framework
CoreTelephony.framework
iAd.framework
libz.tbd
MobileCoreServices.framework
QuartzCore.framework
Security.framework
StoreKit.framework
SystemConfiguration.framework
UserNotifications.framework
(only on Xcode 8 to support iOS 10+)WebKit.framework
-
Add the following flags to Other Linker Flags in the Xcode project Build Settings.
-ObjC -lz
-
Add the
Tune.framework
to the Embed Frameworks build phases. -
Add the following script to the Run Script build phases to strip out simulator slices from the
Tune.framework
:
echo "Target architectures: $ARCHS"
APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"
find "$APP_PATH" -name '*.framework' -type d | while read -r FRAMEWORK
do
FRAMEWORK_EXECUTABLE_NAME=$(defaults read "$FRAMEWORK/Info.plist" CFBundleExecutable)
FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME"
echo "Executable is $FRAMEWORK_EXECUTABLE_PATH"
echo $(lipo -info "$FRAMEWORK_EXECUTABLE_PATH")
FRAMEWORK_TMP_PATH="$FRAMEWORK_EXECUTABLE_PATH-tmp"
# remove simulator's archs if location is not simulator's directory
case "${TARGET_BUILD_DIR}" in
*"iphonesimulator")
echo "No need to remove archs"
;;
*)
if $(lipo "$FRAMEWORK_EXECUTABLE_PATH" -verify_arch "i386") ; then
lipo -output "$FRAMEWORK_TMP_PATH" -remove "i386" "$FRAMEWORK_EXECUTABLE_PATH"
echo "i386 architecture removed"
rm "$FRAMEWORK_EXECUTABLE_PATH"
mv "$FRAMEWORK_TMP_PATH" "$FRAMEWORK_EXECUTABLE_PATH"
fi
if $(lipo "$FRAMEWORK_EXECUTABLE_PATH" -verify_arch "x86_64") ; then
lipo -output "$FRAMEWORK_TMP_PATH" -remove "x86_64" "$FRAMEWORK_EXECUTABLE_PATH"
echo "x86_64 architecture removed"
rm "$FRAMEWORK_EXECUTABLE_PATH"
mv "$FRAMEWORK_TMP_PATH" "$FRAMEWORK_EXECUTABLE_PATH"
fi
;;
esac
echo "Completed for executable $FRAMEWORK_EXECUTABLE_PATH"
echo $(lipo -info "$FRAMEWORK_EXECUTABLE_PATH")
done
Measuring Apple Watch Events
Because users can launch Apple Watch apps independently of the "main" iOS app (running on the iOS device to which the Apple Watch is paired), it is necessary to integrate MobileAppTracker (MAT) in both AppDelegate
and WKInterfaceController
.
The watch app sessions and events are measured as part of the parent iOS app, so the same param values for — advertiser id, conversion key and package name — are to be used for the watch app MAT integration.
Initialize MobileAppTracker
To configure session and event measurement with your Apple Watch app extension, first initialize MobileAppTracker in AppDelegate
applicationDidFinishLaunching:
and WKInterfaceController
awakeWithContext:
, as shown in the following code sample.
// sample code to initialize MobileAppTracker
[MobileAppTracker initializeWithMATAdvertiserId:MAT_ADVERTISER_ID
MATConversionKey:MAT_CONVERSION_KEY];
// [MobileAppTracker setDebugMode:YES];
[MobileAppTracker setDelegate:self];
The last call to MobileAppTracker setDelegate
method is the one to receive the delegate responses moving forward. For more info please refer to Reading Server Responses.
Measure Sessions
Each time a user launches an Apple Watch app, call "measureSession
" in AppDelegate
applicationDidBecomeActive:
andWKInterfaceController
willActivate
, as shown in the following code sample.
// measure app session
[MobileAppTracker measureSession];
Measure Events
To measure a specific event of interest, you can optionally call measureEvent
as shown in the following code sample. Notice the inclusion of event.attribute5 = @"watch"
to indicate this event is watch-specific.
// measure event, e.g. "purchase", "levelChanged"
MATEvent *event = [MATEvent eventWithName:@"anEventOnWatch"];
event.attribute5 = @"watch";
[MobileAppTracker measureEvent:event];
Measuring Facebook App Campaigns
Discontinued
TUNE is no longer using deep linking to measure Facebook. Should you have any further queries, please don’t hesitate to reach out to Support.
Facebook recently changed their deep link measurement policy to no longer allow the use of deep links for measurement or tracking purposes.
To make absolutely certain that TUNE adheres to the terms and policies required by Facebook, and to remove any potential liability for our customers, TUNE is removing this portion of Facebook measurement that relies on deep links, both through access in the platform as well as through Facebook’s API endpoint for deep links. Any campaign previously set up with deep link measurement will no longer return device level attribution.
Any measurement that uses a standard TUNE link will continue, but measuring Mobile Apps via deep linking, for Facebook, has been discontinued at this time.
As the Facebook Terms and Conditions are retroactive, they apply to both existing and new campaigns. Therefore, to be compliant with Facebook’s terms, we have disabled deep linking measurement for existing campaigns.
This means that TUNE will no longer provide device level data typically provided by a Facebook recognized Mobile Measurement Partner (MMP), removing the ability to de-duplicate installs for Facebook against other ad partners or calculate LTV of users that came from Facebook outside of Facebook’s own reporting. If you need device level data for these reasons, we are happy to work with you, and an MMP, to ensure you get access to the device level insights you need.
You will still continue to see aggregate level measurement of Facebook through the Ad Partner ROAS report in Multiverse; showing you install, clicks, and impression counts along with the cost and ROAS associated with your Facebook app campaigns. We are also currently working on bringing this data into a report for consumption directly within your Attribution Analytics account.
Finally, if you are already sharing all of your app installs and events with Facebook for overall reporting and optimization, we will continue to do so to ensure the continuous optimization or performance of any current campaigns. Of course you will still see aggregate level measurement through Facebook Reporting.
Measuring the Referrer Application
By default, the TUNE Measurement API attributes events to the partner/publisher that generated the app install. It also attributes future events to the partner who is responsible for the same user installing the mobile app originally. This approach works well for marketing teams focused on user acquisition and user re-engagement within the app.
Re-engagement allows you to create campaigns that attribute post-install events to the last partner who interacted with the user prior to the event. Re-engagement is useful when a user installed the app several months ago, and a partner wants to be compensated for getting that user to re-open the mobile app (and complete an action or event, such as an in-app purchase).
This section describes how to implement measurement of the referrer application.
Android SDK
Some app ads can open your app if it’s already installed on the device. When another app initiates the opening of your app (by an Intent with startActivityForResult
), Tune can measure the referrer app package name and deep link URL.
The Android SDK automatically collects the package name of the referring app (if your app was called from an Intent withstartActivityForResult
from another app) and deep link URL when your Activity resumes.
iOS SDK
Some app ads can open your app if it’s already installed on the device. When another app initiates the opening of your app (for example, in Safari you receive a callback in the UIApplicationDelegate
). To measure re-engagement, you can measure the referrer app package name and URL by calling the following SDK method:
+ (BOOL)handleOpenURL:(nonnull NSURL *)url sourceApplication:(nullable NSString *)sourceApplication;
The following code shows an example implementation in AppDelegate.m.
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
[Tune handleOpenURL:[url absoluteString] sourceApplication:sourceApplication];
return YES;
}
The handleOpenURL:sourceApplication:
method is independent of the "open" event and may be called multiple times on the same date.
Unity Plugin
For standard deep links, you do not have to modify any app code to support standard deep linking with the TUNE plugin for Unity because it already handles the Unity AppDelegateListener onOpenURL:
callback (and automatically records the "referral_url
" and "referral_source
" when the app is opened from a standard deep link).
For deferred deep links, please follow the instructions at Implementing a Deferred Deep Link.
Passing Deep Links to Javascript
If you’re measuring re-engagement with the Javascript SDK, then you’ll need to access the deep link URL that initiated the app open event (which is only accessible via native code). Making the deep link URL accessible to Javascript allows TUNE to properly attribute the open event as a re-engagement event. Else, TUNE cannot identify the event as re-engagement in the Actuals report (where the is_reengagement
value = Yes
) and in the event log(s).
Here are some code samples for passing the native deep link URL from Android or iOS into your TUNE instance in JavaScript.
Android SDK
First, create a custom class containing the methods that you want to call from Javascript. The following code sample shows a class that gets the referral source (app that initiated the open event) and referral URL (the deep link URL):
public class CustomNativeAccess {
private String referralSource;
private String referralUrl;
public CustomNativeAccess() {
}
public void setReferralSources(Activity activity) {
referralSource = activity.getCallingPackage();
// Set source url query
Intent intent = activity.getIntent();
if (intent != null) {
Uri uri = intent.getData();
if (uri != null) {
referralUrl = uri.toString();
}
}
}
@JavascriptInterface
public String getReferralSource() {
return referralSource;
}
@JavascriptInterface
public String getReferralUrl() {
return referralUrl;
}
}
In your main Activity, instantiate the class and add it to the WebView with "addJavascriptInterface
" before rendering your html code, passing it the name of the interface you used to access it in the JavaScript:
public class NativeToJSExampleActivity extends Activity {
private CustomNativeAccess cna;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
cna = new CustomNativeAccess();
// Create a WebView or grab your existing one before this step
yourWebView.addJavascriptInterface(cna, "android");
}
@Override
public void onResume() {
super.onResume();
// On app resume, set referral values if resume was triggered by a deep link
cna.setReferralSources(this);
}
}
Now that you’ve passed the interface as "android", you can access it in your Javascript.
iOS SDK
For iOS, you can use the "stringByEvaluatingJavaScriptFromString
" method of UIWebView to pass native code by calling a JavaScript function: UIWebView Class Reference
The following native code sample shows retrieval of the openURL (the deep link URL) and sourceApplication from UIApplicationDelegate, and passing them to a "setIdentifiers
" function:
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
NSString *javascript = [NSString stringWithFormat:@"setReferralSources('%@', '%@'", sourceApplication, [url absoluteString]];
// Create a UIWebView or grab your existing one
[yourWebView stringByEvaluatingJavaScriptFromString:javascript];
return YES;
}
Your JavaScript code should also have the same function that simply sets these values for the TUNE instance:
function setReferralSources(referralSource, referralUrl) {
MobileAppTracker.setReferralSource(referralSource);
MobileAppTracker.setReferralUrl(referralUrl);
}
Passing Native Device Identifiers to JavaScript
The Javascript SDK works best with device identifier information, which is only accessible via native code. Here are some examples of how to pass the native identifiers into your TUNE instance in JavaScript.
Android SDK
First, create a custom class that contains the methods you want to call from JavaScript. The following code example shows a class that gets the Google AID and isLimitAdTrackingEnabled setting:
import com.google.android.gms.ads.identifier.AdvertisingIdClient;
import com.google.android.gms.ads.identifier.AdvertisingIdClient.Info;
public class CustomNativeAccess {
private final Context mContext;
private String gaid;
private boolean isLAT;
public CustomNativeAccess(Context context) {
mContext = context;
new Thread(new Runnable() {
@Override
public void run() {
Info adInfo = null;
try {
adInfo = AdvertisingIdClient.getAdvertisingIdInfo(mContext);
gaid = adInfo.getId();
isLAT = adInfo.isLimitAdTrackingEnabled();
} catch (Exception e) {
}
}
}).start();
}
@JavascriptInterface
public String getGaid() {
return gaid;
}
@JavascriptInterface
public boolean getIsLAT() {
return isLAT;
}
}
In your main Activity, instantiate the class and add it to the WebView with addJavascriptInterface
before displaying your html, passing it the name of the interface that you use to access it in the Javascript:
public class NativeToJSExampleActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create a WebView or grab your existing one before this step
yourWebView.addJavascriptInterface(new CustomNativeAccess(this), "android");
}
}
Now that you’ve passed the interface as "android", you can access it in your Javascript:
var gaid = window.android.getGaid();
var isLAT = window.android.getIsLAT();
MobileAppTracker.setGoogleAdvertisingId(gaid, isLAT);
iOS SDK
For iOS, you can use the stringByEvaluatingJavaScriptFromString
method of UIWebView to pass native code by calling a JavaScript function: UIWebView Class Reference
The following native code example shows retrieval of the IFA/IFV identifiers and passing them to a "setIdentifiers
" function:
ASIdentifierManager *adMgr = [ASIdentifierManager sharedManager];
NSString *appleAdId = [[adMgr advertisingIdentifier] UUIDString];
NSString *adTrackingEnabled = [NSString stringWithFormat:@"%d", adMgr.advertisingTrackingEnabled];
NSString *appleVendorId = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
NSString *javascript = [NSString stringWithFormat:@"setIdentifiers('%@', '%@', '%@')",
appleAdId, adTrackingEnabled, appleVendorId];
// Create a UIWebView or grab your existing one
[yourWebView stringByEvaluatingJavaScriptFromString:javascript];
Your Javascript code should also have the same function that simply sets these identifiers for the MAT instance:
function setIdentifiers(ifa, trackingEnabled, ifv) {
MobileAppTracker.setAppleAdvertisingIdentifier(ifa, trackingEnabled);
MobileAppTracker.setAppleVendorIdentifier(ifv);
}
Request Queueing and Retries
The TUNE SDKs include extensive logic for request queueing and retries. This logic ensures that requests are processed once, in order, by our Measurement API, and they are retried in case the device goes offline unexpectedly.
- App calls SDK’s measureAction() method.
- SDK creates a unique transaction ID and constructs the URL.
- URL is enqueued and queue is saved to disk.
- First queued request is sent to server.
- Server response received:
- HTTP 2XX?
- Remove request from queue.
- Update queue on disk.
- Send next item from queue.
- HTTP 400 with an X-MAT-Responder header?
- Bad request, remove from queue.
- Update queue on disk.
- Any other response:
- Leave request in queue.
- Update retry_attempt value.
- Update request’s send date with exponential backoff based on retry_attempt count.
- Update queue on disk.
- HTTP 2XX?
Tune.a File (iOS only)
If you’re using a third party SDKs (e.g. Corona) with TUNE, then you may not be able to include the Tune.framework
in your project. In such cases, you need to directly include the Tune.a
file.
The Tune.framework
is a folder structure that wraps the Tune.a
and Tune.h
files. To access the Tune.a
file:
- Open Finder and navigate to the
Tune.framework/Versions/A
folder. - Copy out the Tune file
Using Multiple Android Install Referrers
Android SDK
If your Android app has multiple receivers for INSTALL_REFERRER for Google Play, then you need to write a custom receiver that calls these receivers.
The following code shows an example of creating a new receiver class.
import com.tune.TuneTracker;
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
TuneTracker tracker = new TuneTracker();
tracker.onReceive(context, intent);
// Initialize and call onReceive for other receivers.
// These might include Google Analytics and your own handler.
}
}
Then place the receiver in your manifest file as shown in the following code example (but change the receiver name to the one you created above).
<receiver android:name="com.mypackage.MyReceiver" android:exported="true">
<intent-filter>
<action android:name="com.android.vending.INSTALL_REFERRER"/>
</intent-filter>
</receiver>
If an Install Referrer is not working/tracking properly, then please see Testing the Google Play Install Referrer.
Plugins
If you’re using one of the supported plugins, you still need to write a custom receiver in Java, but then export it into a .jar file to be included in the manifest file in your plugin of choice. Please refer to that plugin’s documentation as to the location of the manifest file.
Why is the Conversion Key the same for Multiple Apps
As of December 2012, the conversion key is the same for all new mobile apps created in Attribution Analytics. This consistency allows your engineers to implement the same SDK in all their apps. The Android package name / Apple bundle Id is then used to associate the SDK implementation with the mobile app created in TUNE.
Therefore, your engineers only need one key. If you have two apps with the same Android package name / Apple bundle Id, you can set the site_id (mobile app id) which overrides the lookup by Android package name / Apple bundle Id.
See Creating Unique Package Names for more information.
Now you don’t need to setup the mobile app in MAT for each new mobile app (that you implement the SDK into). If the SDK sends data to MAT, then before you create the mobile app in MAT, MAT automatically creates the mobile app using the Android package name / Apple bundle Id. When MAT creates a mobile app dynamically, you need to create a Campaign for the mobile app so you can generate tracking links and promote it.
Updated 8 months ago