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

  1. Autotrack events
  2. Track custom events
  3. Display server-side experiments (if an experiment is created)
  4. Display server-side personalization campaigns (if a personalization is created)

Minimum Requirements for using the Android SDK

  1. 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.
  2. Java and Kotlin Compatibility

    • Java Compatibility:The consumer app should support Java 8 (sourceCompatibility and targetCompatibility set to 1.8)
    • Kotlin Compatibility: The consumer app should set the Kotlin jvmTarget to 1.8 for alignment with the SDK.
  3. Required Permissions

    The following permissions are required only if the SDK uses features that need them:
    • 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" />
    
  4. Namespace Compatibility

    • Ensure that namespace = com.intempt.core does not conflict with existing namespaces in the consumer app.

🚧

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.

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 nameDefinition
App instalation/upgradeTracks app installations or upgrades, capturing version and build details, key for understanding adoption rates.
ChangeThis 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.
TouchRecorded when a user taps or touches a UI control, such as buttons, sliders, or any interactive elements.
Session startLogs the start of a new user session, essential for analyzing session frequency and engagement.
Session endRecorded after 30 minutes of inactivity, whether in background or foreground, for session length analysis.
Fragment transitionIs 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 screenCaptured every time a user views a screen within the app. Useful for tracking navigation and screen engagement.
Leave screenLogged 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 doNotCaptureText 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 and personalization:
    • 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

  1. 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.

  1. 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.