Android SDK
Overview
An Android SDK for user tracking enables developers to capture, analyze, and understand user interactions within their applications. This type of SDK collects valuable data points on user behavior, which can help improve user experience, increase engagement, and drive better business outcomes.
Features
- Autotrack events
- Track custom events
- Display server-side experiments (if an experiment is created)
- Display server-side personalization campaigns (if a personalization is created)
Minimum Requirements for using the Android SDK
-
Android SDK Version
- Minimum SDK Version: 31 (Android 12): The consumer appβs
minSdkVersion
must be set to 31 or higher to be compatible with the SDK library. - Target SDK Version:Itβs recommended that the consumer app targets a version close to API level 35 to ensure compatibility.
- Minimum SDK Version: 31 (Android 12): The consumer appβs
-
Java and Kotlin Compatibility
- Java Compatibility:The consumer app should support Java 8 (
sourceCompatibility
andtargetCompatibility
set to 1.8) - Kotlin Compatibility: The consumer app should set the Kotlin
jvmTarget
to 1.8 for alignment with the SDK.
- Java Compatibility:The consumer app should support Java 8 (
-
The following permissions are required only if the SDK uses features that need them:Required Permissions- Internet Permission: If the SDK makes network requests, the consumer app must declare the INTERNET permission in AndroidManifest.xml.
-
<uses-permission android:name="android.permission.INTERNET" />
-
Namespace Compatibility
- Ensure that namespace =
com.intempt.core
does not conflict with existing namespaces in the consumer app.
- Ensure that namespace =
Limitations
- If the UI element isn't part of the main view tree, auto-capturing interactions from that element will be ignored.
- The Android SDK does not currently support automatically capturing interactions from UI elements built with Jetpack Compose.
Installation
Create a source
Under the "Sources" page, select the "Create source" button and then the "Android SDK" option.
Create intempt-config.json
in src/main/assets
directory and insert the snippet.
intempt-config.json
in src/main/assets
directory and insert the snippet.When using this snippet, remember replace "YOUR_API_KEY" with the API key of the project you want to send data.
{
"auth": {
"INTEMPT_API_KEY": "YOUR_API_KEY",
"INTEMPT_SOURCE_ID": "YOUR_SOURCE_ID",
"INTEMPT_ORGANIZATION_ID": "YOUR_ORGANIZATION_ID",
"INTEMPT_PROJECT_ID": "YOUR_PROJECT_ID"
},
"options": {...your options }
}
Don't miss out!
Remember to replace "YOUR_API_KEY" with the API key you generated via API keys section.
Specify your options
WIthout specifying options
will be used default values
"options": {
"isLoggingEnabled": false,
"isTouchEnabled": true,
"isTextCaptureEnabled": true,
"isQueueEnabled": true,
"isAutoCaptureEnabled": true,
"itemsInQueue": 5,
"timeBuffer": 5000
}
Create the API key
Refer to API keys to create unique API keys that you can use in your project to authenticate SDK access.
Installing the SDK
The SDK is published as a library to our Maven Central repository, which includes all necessary classes, resources, and configurations.
Below are examples of how to include repositories in your project:
Option 1: Specify Repositories in settings.gradle or settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}
Option 2: Specify Repositories in build.gradle or build.gradle.kts
buildscript {
repositories {
google()
mavenCentral()
}
}
repositories {
google()
mavenCentral()
}
buildscript {
repositories {
google()
mavenCentral()
}
}
repositories {
google()
mavenCentral()
}
After specifying the repositories declare the library as a dependency in the app/build.gradle
file.
dependencies {
implementation("com.intempt.sdk:intempt-android:$version")
}
dependencies {
implementation "com.intempt.sdk:intempt-android:$version"
}
Initialize the SDK
Use an instance of Intempt
in your appβs main entry point to initialize the SDK with the context of your mobile app.
import com.intempt.core.Intempt
override fun onCreate() {
super.onCreate()
Intempt.initialize(this)
}
import com.intempt.core.Intempt
@Override
public void onCreate() {
Intempt.INSTANCE.initialize(this);
}
Supported event types
After the SDK is initialized, you can access two types of events tracked by the SDK.
Auto-tracked events
Event name | Definition |
---|---|
App instalation/upgrade | Tracks app installations or upgrades, capturing version and build details, key for understanding adoption rates. |
Change | This event occurs whenever a user makes a change to an input field or COmpund button component in a form, such as selecting an option from a dropdown or toggling a switch. It helps track user preferences and input patterns. |
Touch | Recorded when a user taps or touches a UI control, such as buttons, sliders, or any interactive elements. |
Session start | Logs the start of a new user session, essential for analyzing session frequency and engagement. |
Session end | Recorded after 30 minutes of inactivity, whether in background or foreground, for session length analysis. |
Fragment transition | Is captured every time a user navigates to a new screen or view within the app. It's useful for tracking user navigation patterns and screen engagement. |
View screen | Captured every time a user views a screen within the app. Useful for tracking navigation and screen engagement. |
Leave screen | Logged when a user exits a screen, useful for understanding user flows and potential interface issues. |
Custom events
Custom events are specific actions you define in your code that you want to track. Compared to autotrack events, which only require a tracking snippet to be added, custom events require advanced planning (refer to Complete guide to event tracking) and developer involvement to add the code to your website or application. After the custom event is tracked, it will automatically appear in the Events list.
Use the SDK methods below to perform custom event tracking
Initialize
The initialize
function is used to initialize Intempt instance.
Example
package com.example.testapp
import android.app.Application
import com.intempt.core.Intempt
class App: Application(){
override fun onCreate() {
super.onCreate()
Intempt.initialize(this);
}
}
package com.example.testapp;
import android.app.Application;
import com.intempt.core.Intempt;
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
Intempt.INSTANCE.initialize(this);
}
}
Decalaration
import android.content.Context
fun initialize(context:Context):Unit
import android.content.Context;
public void initialize(Context context)
Log out
The logOut
function is used to clear all stored data from SharedPreferences
storage, except profileId
. The user can remove it by manually clearing the SharedPreferences
storage.
Example
Intempt.logOut()
Intempt.INSTANCE.logOut()
In this example, we are clearing all stored data related to the user, except
Decalration
fun logOut():Unit
public void logOut()
Do not capture text
The doNotCapture
Text method will assign a custom tag intemptDoNotCapture to the provided View, which will replace all text values with "********". This method is useful for hiding any sensitive data in text fields.
Example
val element:TextView = view.findViewById(R.id.textElement)
Intempt.doNotCaptureText(view = element)
TextView element = view.findViewById(R.id.textElement);
Intempt.INSTANCE.doNotCaptureText(element);
In this example, we are hiding sensitive data provided by textElement
.
Decalration
fun doNotCaptureText(view: View):Unit
public void doNotCaptureText(View view)
Identify
The identify
function allows you to tie a user to their actions and record attributes about them. It includes a unique user ID and any optional attributes you know about the user, like their email, name, and more.
Identifying users helps us understand who they are and what they are doing. Once identified, Intempt connects events related to formerly anonymous IDs with the unique set IDs.
Example
Intempt.identify(userId = "[email protected]" )
Map<String, Object> data = null;
Map<String, Object> userAttributes = null;
String eventTitle = null;
String userId = "[email protected]";
Intempt.INSTANCE.identify(userId, eventTitle,userAttributes, data )
In this example, the identify
method logs user identification information, specifying the user's unique ID, such as an email address, to associate future events with this user
Declaration
fun identify(
userId: String,
eventTitle: String? = null,
userAttributes: Map<String, String>? = null,
data: Map<String, String>?= null,
): Unit
public void record(
String userId,
String eventTitle,// If 'eventTitle' is not specified, "Identify" title will be used
Map<String, Object> userAttributes,// 'eventTitle' must be specified to use 'userAttributes'
Map<String, Object> data
)
Track
The track
method is the simplest way to track events. It records event attributes present when the event was performed, including timestamps, IDs, amounts, and other event-specific information. Note that the track method does not allow tracking user attributes. Instead, use the identify
or record
methods.
Example
Intempt.track(
eventTitle= "Test track",
data = mapOf(
"itemId" to "SKU12345",
"price" to 29.99,
"quantity" to 1
),
)
Map<String, Object> data = new HashMap<>();
data.put("itemId", "SKU12345");
data.put("price", 29.99);
data.put("quantity", 1);
String eventTitle = "Test track";
Intempt.INSTANCE.track(eventTitle, data)
In this example, the track
method logs a product view event with additional data about the product, such as its ID, name, and price.
Declaration
fun track(eventTitle: String, data: Map<String, String>): Unit
public void track(String eventTitle, Map<String, Object> data)
Record
The record
method allows you to track everything about the event - including user or account attributes and identifying the user. We recommend using the record
if you want to use a single method to track the event data.
Example
Intempt.record(
eventTitle ="login",
userId = "[email protected]",
userId = "[email protected]",
data = mapOf(
"ipAddress" to "192.168.1.1",
),
userAttributes = mapOf(
"loginMethod" to "OAuth",
"attemptCount" to 3,
),
)
String accountId = null;
Map<String, Object> accountAttributes = null;
Map<String, Object> data = new HashMap<>();
data.put("ipAddress", "192.168.1.1");
Map<String, Object> userAttributes = new HashMap<>();
userAttributes.put("loginMethod", "OAuth");
userAttributes.put("attemptCount", "3");
String eventTitle = "login";
String userId = "[email protected]";
Intempt.INSTANCE.record(eventTitle,accountId, userId,accountAttributes, userAttributes, data)
In the example above, the record method tracks login event and identifies the user with [email protected]
, track user's attributes (loginMethod, attemptCount) and event attributes (ipAddress).
Declaration
fun record(
eventTitle: String,
accountId: String? = null,
userId: String? = null,
accountAttributes: Map<String, String>? = null,
userAttributes: Map<String, String>? = null,
data: Map<String, String>? = null
): Unit
public void record(
String eventTitle,
String accountId,
String userId,
Map<String, Object> accountAttributes,
Map<String, Object> userAttributes,
Map<String, Object> data
)
Group
The group method associates an individual user with a group, such as a company, organization, account, project, or team.
The Group method enables you to identify what account or organization your users are part of. Two IDs are relevant in a Group call: the userId, which belongs and refers to the user, and the accountId, which belongs and refers to the specific group. A user can be in more than one group, which would mean different group IDs, but the user will only have one user ID that is associated with each of the different groups.
In addition to the accountId, which is how youβd identify the specific group or company, the group method receives attributes specific to the group, like industry or number of employees that belong to that specific account. Like the attributes of an identify method, you can update these when you call the same attribute with a different value.
Example
Intempt.group(
accountId ="12345",
eventTitle="New User Group",
accountAttributes = mapOf(
"leader" to "John Doe",
"size" to 10
),
)
String accountId = "12345";
String eventTitle = "New User Group";
Map<String, Object> accountAttributes = new HashMap<>();
accountAttributes.put("leader", "John Doe");
accountAttributes.put("size", 10);
Intempt.INSTANCE.identify(accountId, eventTitle,accountAttributes)
In this example, the group
method is used to log information about a new user group, including details about the group leader and size.
Decalration
fun group(
accountId: String,
eventTitle: String? = null,
accountAttributes: Map<String, String>? = null
): Unit
public void group(
String accountId,
String eventTitle,// If 'eventTitle' is not specified, "Identify" title will be used
Map<String, Object> accountAttributes// 'eventTitle' must be specified to use 'accountAttributes'
)
Alias
The alias method associates two user IDs together, typically when you want to merge multiple profiles or sessions under a unified identity. This method is crucial for maintaining continuity when a user has changed their identification method or has multiple accounts.
Intempt.alias(
userId ="user123",
anotherUserId="user456",
)
String userId = "12345";
String anotherUserId = "user456";
Intempt.INSTANCE.identify(userId, anotherUserId)
In this example, the alias
method associates the user ID 'user123' with another user ID 'user456'. This can help in cases where the same individual has used different login methods or has multiple accounts that need to be treated as a single entity.
Declaration
fun alias(
userId: String,
anotherUserId: String
): Unit
public void alias(
String userId,
String anotherUserId
)
Consent
You can use consent
method to track if the user has provided consent to use his data for consent purposes.This functionality is critical that you only use the data complying with privacy regulations like GDPR and CCPA.
Example
Intempt.consent(
action = "agree",
validUntil = System.currentTimeMillis() + 100000000,
email = "[email protected]",
message = "User agreed to marketing emails.",
category = "marketing"
)
String action = "agree";
Long validUntil = System.currentTimeMillis() + 100000000;
String email = "[email protected]";
String message = "User agreed to marketing emails.";
String category = "marketing";
Intempt.INSTANCE.consent(action, validUntil, email, message, category)
In this example, the consent
method logs a user's agreement to receive marketing emails, specifying the duration of the consent and additional details about the consent given.
Declaration
fun consent(
action: String,
validUntil: Long,
email: String? = null,
message: String? = null,
category: String? = null
): Unit
public void alias(
String action,
Long validUntil,
String email,
String message,
String category
)
Product add
The productAdd
function is used to emit an event when a product is added to the cart.
Example
Intempt.productAdd(
productId = "SKU12345",
quantity= 1
)
String productId = "SKU12345";
int quantity = 1;
Intempt.INSTANCE.productAdd(productId,quantity)
In this example, the productAdd method logs the addition of a product to the shopping cart, specifying the product ID and quantity added. This information is then emitted as an "Added to cart" event for tracking purposes.
Declaration
fun productAdd(
productId: String,
quantity: Int
): Unit
public void productAdd(
String productId,
int quantity,
)
Product ordered
The productOrdered
function emits an event when a list of products is ordered, ensuring all products have valid details.
Example
Intempt.productOrdered(
listOf(
mapOf(
"productId" to "SKU12345",
"quantity" to 2,
),
mapOf(
"productId" to "SKU16347",
"quantity" to 4,
),
)
)
List<Map<String, Object>> products = new ArrayList<>();
Map<String, Object> product1 = new HashMap<>();
product1.put("productId", "SKU12345");
product1.put("quantity", 2);
products.add(product1);
Map<String, Object> product2 = new HashMap<>();
product2.put("productId", "SKU16347");
product2.put("quantity", 4);
products.add(product2);
Intempt.INSTANCE.productOrdered(products);
Declaration
fun productOrdered(products: List<Map<String, Any>>): Unit
public void productOrdered(List<Map<String, Object>> products)
Product View
The productView
function is used to emit an event when a product is viewed by the user.
Example
Intempt.productView(productId = "SKU12345")
String productId = "SKU12345";
Intempt.INSTANCE.productView(productId)
In this example, the productView method is used to emit information about a product view event, including details about the specific product being viewed by the user.
Declaration
fun productView(String productId): Unit
public void productOrdered(String productId)
Logging
The Logging
object provides access to several methods that control SDK logging. By default, loggin is disabled
Example
Intempt.Logging.start() // Enable logging if it's disabled
Intempt.Logging.stop()// Disable logging if it's enabled
Intempt.Logging.isLoggingEnabled() // Check logging status
Intempt.Logging.INSTANCE.start() // Enable logging if it's disabled
Intempt.Logging.INSTANCE.stop()// Disable logging if it's enabled
Intempt.Logging.INSTANCE.isLoggingEnabled() // Check logging status
Declaration
object Logging {
fun start():Unit
fun stop():Unit
fun isLoggingEnabled(): Boolean
}
public class Logging {
public static void start()
public static void stop()
public static boolean isLoggingEnabled()
}
Tracking
The Tracking
object provides access to several methods that control SDK tracking state. By default, tracking is enabled.
Example
Intempt.Tracking.start() // Enable tracking if it's disabled
Intempt.Tracking.stop()// Disable tracking if it's enabled
Intempt.Tracking.isTrackingEnabled() // Check tracking status
Intempt.Tracking.INSTANCE.start() // Enable tracking if it's disabled
Intempt.Tracking.INSTANCE.stop()// Disable tracking if it's enabled
Intempt.Tracking.INSTANCE.isTrackingEnabled() // Check tracking status
Declaration
object Tracking {
fun start():Unit
fun stop():Unit
fun isTrackingEnabled(): Boolean
}
public class Tracking {
public static void start()
public static void stop()
public static boolean isTrackingEnabled()
}
Optimization choose methods
The SDK provides methods to select and manage experiments and personalized experiences based on lists of groups and names. This functionality enables you to organize and conduct A/B tests or other experimental comparisons, while also tailoring user experiences to specific segments, groups, or individuals. By targeting groups and names directly, you can streamline the process of running experiments, delivering customized content, and analyzing results. These capabilities help optimize your product or service for improved user engagement, enhanced personalization, and overall performance.
Availabe methods:
experiment
andpersonalization
:getByName
getByGroup
Experiments
Example
coroutineScope.launch {
val res = Intempt.experiment.getByName(
listOf("Experiment Name")
)
withContext(Dispatchers.Main) {
printLn(" GetByName Result: $res ")
}
}
coroutineScope.launch {
val res = Intempt.experiment.getByGroup(
listOf("Experiment Group")
)
withContext(Dispatchers.Main) {
printLn(" GetByGroup Result: $res ")
}
}
List<String> names = Arrays.asList("Experiment Name");
CompletableFuture<JsonElement> futureNameResponse = Intempt.INSTANCE.experiment.getByNameAsync(names);
futureNameResponse.thenAccept(response -> {
System.out.println("Name Response: " + response);
}).exceptionally(ex -> {
ex.printStackTrace();
return null;
});
List<String> groups = Arrays.asList("Experiment Group");
CompletableFuture<JsonElement> futureResponse = Intempt.INSTANCE.experiment.getByGroupAsync(groups);
futureResponse.thenAccept(response -> {
System.out.println("Response: " + response);
}).exceptionally(ex -> {
ex.printStackTrace();
return null;
});
In this example, we are sending a POST request and log the response data for an experiment.
Declaration
interface ModificationProvider {
suspend fun getByGroup(data: List<String>): JsonElement?
suspend fun getByName(data: List<String>): JsonElement?
}
public interface ModificationProvider {
CompletableFuture<JsonElement> getByGroupAsync(List<String> data);
CompletableFuture<JsonElement> getByNameAsync(List<String> data);
}
Personalization
Example
coroutineScope.launch {
val res = Intempt.personalization.getByName(
listOf("Experiment Name")
)
withContext(Dispatchers.Main) {
printLn(" GetByName Result: $res ")
}
}
coroutineScope.launch {
val res = Intempt.personalization.getByGroup(
listOf("Personalization Group")
)
withContext(Dispatchers.Main) {
printLn(" GetByGroup Result: $res ")
}
}
List<String> names = Arrays.asList("Experiment Name");
CompletableFuture<JsonElement> futureNameResponse = Intempt.INSTANCE.personalization.getByNameAsync(names);
futureNameResponse.thenAccept(response -> {
System.out.println("Name Response: " + response);
}).exceptionally(ex -> {
ex.printStackTrace();
return null;
});
List<String> groups = Arrays.asList("Experiment Group");
CompletableFuture<JsonElement> futureResponse = Intempt.INSTANCE.personalization.getByGroupAsync(groups);
futureResponse.thenAccept(response -> {
System.out.println("Response: " + response);
}).exceptionally(ex -> {
ex.printStackTrace();
return null;
});
Declaration
interface ModificationProvider {
suspend fun getByGroup(data: List<String>): JsonElement?
suspend fun getByName(data: List<String>): JsonElement?
}
public interface ModificationProvider {
CompletableFuture<JsonElement> getByGroupAsync(List<String> data);
CompletableFuture<JsonElement> getByNameAsync(List<String> data);
}
Field Type Validation
- When sending custom events, itβs important that each field matches the data type defined in your event schema. If a fieldβs type changes (e.g., from a string to an object), the event will be rejected. This ensures data consistency and prevents errors.
Example:
If the field name is defined as a string in the schema:
const event = { name: βJohn Doeβ }; // β
Accepted
const event = { name: { first: βJohnβ, last: βDoeβ } }; // β Rejected
What to do?
Always ensure that event fields retain the same type as defined in your schema.
- Adding New Fields to Custom Events
You can now safely add new fields to your custom event schema without impacting existing fields. This allows you to extend your event tracking capabilities while keeping previous fields intact and functional.
Example:
Original event - After adding a new field
const event = { name: βJohn Doeβ }; // β
Accepted
const event = { name: { first: βJohnβ, last: βDoeβ } }; // β Rejected
Existing fields will remain usable even after schema updates.
Updated 16 days ago