Skip to main content

Getting Started

This guide outlines the features and usage of the Paylisher Android SDK for tracking events and user behavior in mobile apps.

The SDK uses an internal queue to make calls fast and non-blocking. It also batches requests and flushes them asynchronously, making it safe to use in any part of your app.


Key Features

  • Event Capture — track custom and autocaptured events.
  • User Identification — associate events with specific users.
  • Screen & Fragment Tracking — automatic Activity and Fragment screen views.
  • Feature Flags & Group Analytics — advanced segmentation and experiments.
  • Deep Linking & Attribution — deep links, deferred deep links, and journey tracking.
  • Push & In-App Messaging — FCM-based push and rich in-app messages.
  • Internal Queueing — fast, non-blocking calls with asynchronous batching.

Requirements

  • Min SDK: 24 (Android 7.0)
  • Target SDK: 34
  • Java: 8

Installation

The Paylisher Android SDK is distributed through Maven Central.

1. Add the repositories in your settings.gradle.kts (or root build.gradle.kts):

dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}

2. Add the dependency in your app's build.gradle.kts:

dependencies {
implementation("com.paylisher:paylisher-sdk-android-lite:1.1.3")
}

Note: The core module (com.paylisher:paylisher-sdk-lite) is included automatically as a transitive dependency — you don't need to add it manually.

Push notifications: Push and Firebase In-App Messaging require the Google Services plugin and Firebase BoM in your app. That setup is covered in the Push Notifications guide.

Configuration

The best place to initialize the client is in your Application subclass.

import android.app.Application
import com.paylisher.android.PaylisherAndroid
import com.paylisher.android.PaylisherAndroidConfig

class SampleApp : Application() {
companion object {
const val PAYLISHER_API_KEY = "<ph_project_api_key>"
// Use the host from your Paylisher project settings.
// US: https://us.i.paylisher.com • EU: https://eu.i.paylisher.com
const val PAYLISHER_HOST = "https://us.i.paylisher.com"
}

override fun onCreate() {
super.onCreate()
val config = PaylisherAndroidConfig(apiKey = PAYLISHER_API_KEY, host = PAYLISHER_HOST)
PaylisherAndroid.setup(this, config)
}
}

Don't forget to register your Application subclass in AndroidManifest.xml:

<application
android:name=".SampleApp"
... >

Capturing Events

You can send custom events using capture:

import com.paylisher.Paylisher

Paylisher.capture(event = "user_signed_up")

Tip: We recommend a [object] [verb] format for your event names, where [object] is the entity that the behavior relates to and [verb] is the behavior itself. For example, project created, user signed up, or invite sent.

Setting event properties

Optionally, you can include additional information in the event by setting properties:

import com.paylisher.Paylisher

Paylisher.capture(
event = "user_signed_up",
properties = mapOf("login_type" to "email", "is_free_trial" to true)
)

Autocapture

Paylisher autocapture automatically tracks the following events for you:

  • Application Opened — when the app is opened from a closed state or comes to the foreground (e.g. from the app switcher).
  • Deep Link Opened — when the app is opened from a deep link.
  • Application Backgrounded — when the app is sent to the background.
  • Application Installed — when the app is installed.
  • Application Updated — when the app is updated.
  • $screen — when the user navigates between screens.

Capturing screen views

With captureScreenViews = true (default), Paylisher tracks Activity screen changes automatically.

The screenTitle is the <activity>'s android:label; if it isn't set, it falls back to the <application>'s android:label or the <activity>'s android:name.

<activity
android:name="com.example.app.ChildActivity"
android:label="@string/title_child_activity"
... />

To manually send a screen capture event, use the screen function. It requires a screenTitle and accepts optional properties:

Paylisher.screen(
screenTitle = "Dashboard",
properties = mapOf("background" to "blue")
)

Fragment screen tracking

Modern Android apps often use a single Activity with multiple Fragments. The SDK automatically tracks Fragment screen views, and this is enabled by default (captureFragmentScreenViews = true).

When enabled, the SDK:

  • Captures the Fragment onResume() lifecycle event.
  • Sends screen view events with clean Fragment names (e.g. Home from HomeFragment).
  • Disables Activity-level tracking to prevent duplicate events.
  • Supports nested fragments (ViewPager, BottomSheet, etc.).

To customize how Fragment names are displayed:

config.fragmentScreenNameTransformer = { fragment ->
when (fragment) {
is HomeFragment -> "Home Screen"
is ProfileFragment -> "User Profile"
else -> fragment.javaClass.simpleName
}
}

For apps using the AndroidX Navigation Component, you can opt in to enhanced tracking (navigation labels, routes, and arguments as event properties):

config.captureNavigationDestinations = true // disabled by default

To revert to legacy Activity-only tracking:

config.captureFragmentScreenViews = false
config.captureScreenViews = true

Identifying users

We highly recommend reading our Identify User guide to understand how to correctly use this method.

Using identify, you can associate events with specific users. This enables full insight into how they use your product across different sessions, devices, and platforms.

An identify call has the following arguments:

  • distinctId: Required. A unique identifier for your user — typically their email or database ID.
  • userProperties: Optional. A map of key/value pairs to set the person properties.
  • userPropertiesSetOnce: Optional. Like userProperties, but only sets a property if the user doesn't already have it set.
import com.paylisher.Paylisher

Paylisher.identify(
distinctId = distinctID,
userProperties = mapOf(
"name" to "John Doe",
"email" to "[email protected]"
),
userPropertiesSetOnce = mapOf(
"signup_date" to "2024-12-01"
),
)

Call identify as soon as you can — typically when your app first loads and directly after your user logs in. When you call identify, all previously tracked anonymous events are linked to the user.

Get the current user's distinct ID

To check whether you've already called identify for a user, call distinctId(). This returns either the ID automatically generated by Paylisher or the ID passed to identify():

val id = Paylisher.distinctId()

Alias

Sometimes you want to assign multiple distinct IDs to a single user. This is helpful when your primary distinct ID is inaccessible — for example, when a distinct ID used on the frontend is not available in your backend.

In this case, use alias to assign another distinct ID to the same user:

Paylisher.alias("distinct_id")

Anonymous and identified events

Paylisher captures two types of events: anonymous and identified.

Identified events let you attribute events to specific users and attach person properties. They're best suited for logged-in users:

  • Tracking logged-in users in B2B and B2C SaaS apps.
  • Doing user-segmented product analysis.
  • Analyzing the complete conversion lifecycle.

Anonymous events are events without individually identifiable data. They're best suited for web-style analytics or apps where users aren't logged in.

Under the hood, the key difference is that identified events create a person profile for the user, whereas anonymous events do not.

💡 Tip: Anonymous events can be significantly cheaper to process than identified ones, so capture identified events only when needed.

How to capture anonymous events

The Android SDK captures anonymous events by default. This behavior is controlled by the personProfiles config:

  • PersonProfiles.IDENTIFIED_ONLY (default, recommended) — Anonymous events are captured by default. Identified events are only captured for users who already have a person profile.
  • PersonProfiles.ALWAYS — Capture identified events for all events.
  • PersonProfiles.NEVER — Capture anonymous events for all events.
val config = PaylisherAndroidConfig(
apiKey = PAYLISHER_API_KEY,
host = PAYLISHER_HOST,
).apply {
personProfiles = PersonProfiles.IDENTIFIED_ONLY
}

How to capture identified events

With the default IDENTIFIED_ONLY config, anonymous events are captured by default. To capture identified events, call any of the following — each creates a person profile for the user, after which all subsequent events are captured as identified:

  • identify()
  • alias()
  • group()

Setting person properties

To set properties on your users via an event, use the userProperties and userPropertiesSetOnce parameters:

import com.paylisher.Paylisher

Paylisher.capture(
event = "button_b_clicked",
properties = mapOf("color" to "blue"),
userProperties = mapOf("string" to "value1", "integer" to 2)
)

userPropertiesSetOnce works just like userProperties, except that it will only set the property if the user doesn't already have that property set:

import com.paylisher.Paylisher

Paylisher.capture(
event = "button_b_clicked",
properties = mapOf("color" to "blue"),
userPropertiesSetOnce = mapOf("string" to "value1", "integer" to 2)
)

Super Properties

Super Properties are properties associated with events that are set once and then sent with every capture call, be it a $screen or anything else.

They are set using Paylisher.register, which takes a key and value, and they persist across sessions:

import com.paylisher.Paylisher

Paylisher.register("team_id", 22)

The call above ensures that every event sent by the user includes "team_id": 22.

Note that this does not store properties against the User, only against their events. To store properties against the User object, use Paylisher.identify.

Removing stored Super Properties

Super Properties are persisted across sessions, so you have to explicitly remove them if they are no longer relevant. To stop sending a Super Property with events, use Paylisher.unregister:

import com.paylisher.Paylisher

Paylisher.unregister("team_id")

If you are doing this as part of a user logging out, you can instead use Paylisher.reset, which clears all stored Super Properties and more.


Opt out of data capture

You can completely opt users out of data capture. There are two options:

  • Opt users out by default by setting optOut to true in your config:
val config = PaylisherAndroidConfig(
apiKey = "<ph_project_api_key>",
host = "<ph_client_api_host>"
)
config.optOut = true
PaylisherAndroid.setup(this, config)
  • Opt users out on a per-person basis by calling optOut():
Paylisher.optOut()

Similarly, you can opt users back in:

Paylisher.optIn()

To check if a user is opted out:

Paylisher.isOptOut()

Flush

You can set the number of events that should queue before flushing. Setting this to 1 sends events immediately and uses more battery. The default is 20.

You can also configure the flush interval. By default the SDK flushes all events after 30 seconds, no matter how many events have been gathered.

import com.paylisher.android.PaylisherAndroidConfig

val config = PaylisherAndroidConfig(apiKey = PAYLISHER_API_KEY, host = PAYLISHER_HOST).apply {
flushAt = 20
flushIntervalSeconds = 30
}

You can also manually flush the queue:

import com.paylisher.Paylisher

Paylisher.flush()

Reset after logout

To reset the user's ID and anonymous ID, call reset. Usually you do this right after the user logs out. This also clears local data such as super properties, feature flags, and journey ID:

import com.paylisher.Paylisher

Paylisher.reset()

Group analytics

Group analytics lets you associate a user's session events with a group (e.g. teams, organizations, etc.). For more information, contact your Paylisher team.

  • Associate the events for this session with a group:
import com.paylisher.Paylisher

Paylisher.group(type = "company", key = "company_id_in_your_db")
  • Associate the events for this session with a group and update the properties of that group:
import com.paylisher.Paylisher

Paylisher.group(
type = "company",
key = "company_id_in_your_db",
groupProperties = mapOf("name" to "Awesome Inc.")
)

The name is a special property used in the Paylisher UI for the name of the group. If you don't specify a name property, the group ID is used instead.

Feature flags

Paylisher feature flags let you roll out features gradually and run experiments. Flags are preloaded automatically on startup (see preloadFeatureFlags).

import com.paylisher.Paylisher

// Boolean flags
if (Paylisher.isFeatureEnabled("my-flag")) {
// do something
}

// Multivariate flags return the variant key
val variant = Paylisher.getFeatureFlag("my-flag")

// Flags can carry a JSON payload for dynamic, per-user content
val payload = Paylisher.getFeatureFlagPayload("my-flag")

You can also react to flags being (re)loaded via the onFeatureFlags config callback:

val config = PaylisherAndroidConfig(apiKey = PAYLISHER_API_KEY, host = PAYLISHER_HOST).apply {
onFeatureFlags = PaylisherOnFeatureFlags { print("feature flags loaded") }
}

When sendFeatureFlagEvent is enabled (default), a $feature_flag_called event is sent automatically the first time a flag is used.

Session replay

To set up session replay, install the Android SDK, enable Record user sessions in your project settings, and enable the sessionReplay option. Session replay is currently experimental.

val config = PaylisherAndroidConfig(apiKey = PAYLISHER_API_KEY, host = PAYLISHER_HOST).apply {
sessionReplay = true
sessionReplayConfig.maskAllTextInputs = true
sessionReplayConfig.maskAllImages = false
sessionReplayConfig.captureLogcat = true
sessionReplayConfig.screenshot = true
}

All configuration options

When creating the Paylisher client, there are many options you can set:

val config = PaylisherAndroidConfig(apiKey = PAYLISHER_API_KEY, host = PAYLISHER_HOST).apply {
// Capture certain application events automatically. (true by default)
captureApplicationLifecycleEvents = true

// Capture deep link events. (true by default)
captureDeepLinks = true

// Capture Activity screen views automatically. (true by default)
captureScreenViews = true

// Capture Fragment screen views (single-activity architecture). (true by default)
captureFragmentScreenViews = true

// Capture Navigation Component destination changes. (false by default, opt-in)
captureNavigationDestinations = false

// Maximum number of events to keep in queue before flushing. (20 by default)
flushAt = 20

// Maximum number of events in memory and on disk. When exceeded, the oldest event is
// dropped and the new one takes its place. (1000 by default)
maxQueueSize = 1000

// Maximum number of events in a single batch call. (50 by default)
maxBatchSize = 50

// Maximum delay before flushing the queue, in seconds. (30 by default)
flushIntervalSeconds = 30

// Log the SDK messages to Logcat. (false by default)
debug = false

// Prevent capturing any data while enabled. (false by default)
optOut = false

// Send a '$feature_flag_called' event when a feature flag is used. (true by default)
sendFeatureFlagEvent = true

// Preload feature flags automatically on startup. (true by default)
preloadFeatureFlags = true

// Callback invoked when feature flags are loaded. (not set by default)
onFeatureFlags = PaylisherOnFeatureFlags { /* ... */ }

// Callback that allows you to sanitize event properties. (not set by default)
propertiesSanitizer = PaylisherPropertiesSanitizer { properties -> properties }

// Hook to encrypt and decrypt events. (no encryption by default)
encryption = null

// Hook to customize generation of the anonymous id (random UUID v7 by default).
getAnonymousId = { it }

// How user profiles are processed: NEVER, ALWAYS, IDENTIFIED_ONLY.
// (IDENTIFIED_ONLY by default)
personProfiles = PersonProfiles.IDENTIFIED_ONLY

// What happens when identify() is called again with the same distinctId after the user
// is already identified on this device:
// - IGNORE (default) suppress duplicate identify calls (legacy behavior)
// - CAPTURE emit a new $identify event so person/device properties can be refreshed
repeatedIdentifyBehavior = RepeatedIdentifyBehavior.IGNORE

// Custom screen name transformer for Fragment tracking. (not set by default)
fragmentScreenNameTransformer = { fragment -> fragment.javaClass.simpleName }

// Deep link configuration (tracking + auth-required destinations).
// See the Deep Linking guide. (default config)
// deepLinkConfig = PaylisherDeepLinkConfig.default()

// Deferred deep link configuration (install attribution).
// See the Deep Linking guide. (default config)
// deferredDeepLinkConfig = PaylisherDeferredDeepLinkConfig.default()

// Engage-served in-app message configuration.
// See the In-App Messaging guide. (null by default)
// engageInAppConfig = null

// Campaign / deferred-deep-link API hosts (advanced; defaults used if null).
campaignApiHost = null
deferredDeepLinkApiHost = null

// Enable Recording of Session Replay (experimental). (false by default)
sessionReplay = false
// Session Replay configuration (experimental).
// sessionReplayConfig = PaylisherSessionReplayConfig()
}

If you have additional questions or need help with implementation, feel free to reach out to your Paylisher team.