Android SDK
Install the TrueEngage Android SDK, track events and screens, identify users and set up Firebase push.
Use the Android SDK (version 0.8.1) to track events and users in a native Android app and to send push notifications through Firebase Cloud Messaging (FCM). It is written in Kotlin, works from Java, and supports Android 6.0 (API 23) and later.
Before you start
- An app key for your Android app. Use a
test_key in debug builds. See Test and Live keys. - For push: a Firebase project, the app's
google-services.json, and the Google Services Gradle plugin.
Install
Add mavenCentral() to your repositories, then add the dependency:
repositories {
mavenCentral()
}
dependencies {
implementation("io.trueengage:trueengage-android:1.0.0")
}
Initialize
Initialize in Application.onCreate(), never in an Activity.
class App : Application() {
override fun onCreate() {
super.onCreate()
TrueEngage.initialize(
this,
TEConfig.builder(BuildConfig.TE_APP_KEY) // test_xxxxxxxx in debug, live_… in release
.dataRegion(DataRegion.IN)
.logLevel(if (BuildConfig.DEBUG) LogLevel.VERBOSE else LogLevel.ERROR)
.build(),
)
}
}
From Java:
TrueEngage.initialize(this, TEConfig.builder(BuildConfig.TE_APP_KEY).dataRegion(DataRegion.IN).build());
initialize is synchronous and makes no network call. Calls made before it are queued and replayed. TrueEngage.isInitialized tells you if it has run.
Configuration
Set these on the builder. Only the app key is required.
| Builder method | Type | Default | Meaning |
|---|---|---|---|
TEConfig.builder(appKey) |
String | none | test_… or live_…. Selects the environment. |
dataRegion(…) |
DataRegion.IN |
IN |
Picks the API host. India is the only data region today. |
apiHost(…) |
String | region host | Full base URL. Overrides the region. |
logLevel(…) |
NONE, ERROR, WARN, INFO, VERBOSE |
ERROR |
Logcat logging. |
optOutByDefault(…) |
Boolean | false |
Collect nothing until optIn(). |
flushIntervalSeconds(…) |
Int | 30 |
Seconds between timed flushes. |
flushBatchSize(…) |
Int | 50 |
Flush when this many items wait. |
maxQueueSize(…) |
Int | 10000 |
Items kept before the oldest are dropped. |
sessionTimeoutMinutes(…) |
Int | 30 |
Idle time that ends a session. |
screenTracking(…) |
Boolean | false |
Automatic screen tracking. |
flushOnOptOut(…) |
Boolean | true |
On opt-out, send what is queued first. false deletes it. |
push(PushConfig(…)) |
PushConfig |
defaults | See the push table below. |
locationPrompt(…) |
"manual" or "auto" |
"manual" |
See Device location. |
locationRefreshHours(…) |
Int | 4 |
Minimum hours between location fixes. Minimum 1. |
PushConfig fields:
| Field | Default | Meaning |
|---|---|---|
autoRegister |
true |
Get the FCM token on every launch and send it. |
requestPermissionOnInit |
false |
Leave off. Ask at a better moment. |
notificationChannelId |
"trueengage_default" |
Channel for campaign pushes. |
notificationChannelName |
"Notifications" |
Channel name shown in system settings. |
smallIconResource |
app icon | Drawable name, such as ic_notification. |
accentColor |
none | Color as an ARGB int. |
Identify and log out
TrueEngage.identify("u-1001", mapOf("firstName" to "Priya", "plan" to "pro"))
TrueEngage.logout() // ends the session, keeps the push token
TrueEngage.reset() // full wipe: new anonymous id, queue cleared
Call identify after sign-in with the same id you use in your own systems. Call logout on sign-out. See Events, attributes and identity.
Track events and screens
TrueEngage.track("Order Placed", mapOf(
"orderId" to "ORD-8891",
"amount" to 2499.50,
"itemCount" to 3,
"isFirstOrder" to true,
"placedAt" to Date(),
"categories" to listOf("footwear", "sale"),
))
TrueEngage.track("Sync Finished", mapOf("rows" to 120), nonInteractive = true)
TrueEngage.trackScreen("Checkout")
trackScreen sends Screen Viewed. It also takes an optional properties map. Call TrueEngage.flush() to send the queue now. You rarely need it.
User attributes
TrueEngage.setUserAttribute("lifetimeValue", 48200)
TrueEngage.setUserAttributes(mapOf("plan" to "pro", "city" to "Indore"))
TrueEngage.removeUserAttribute("trialEndsAt")
Reserved attributes have their own setters:
TrueEngage.setEmail("priya@example.com")
TrueEngage.setPhone("+919876543210") // E.164, not reformatted
TrueEngage.setFirstName("Priya")
TrueEngage.setLastName("Sharma")
TrueEngage.setGender("female")
TrueEngage.setBirthDate(Date(95, 3, 12))
TrueEngage.setCity("Indore")
TrueEngage.setRegion("Madhya Pradesh")
TrueEngage.setCountryCode("IN")
TrueEngage.setTimezone("Asia/Kolkata")
TrueEngage.setLocale("en-IN")
Set up push (FCM)
Add
google-services.jsonand the Google Services Gradle plugin to your app.Upload your FCM credentials to the Android provider in the console.
Ask for notification permission at a good moment. Android 13 and later show a prompt. A user who denies it cannot be asked again by the app.
TrueEngage.requestPushPermission(activity) { result -> // "granted" or "denied" }Forward the result from your Activity:
override fun onRequestPermissionsResult(code: Int, perms: Array<String>, results: IntArray) { if (!PushPermission.onRequestPermissionsResult(this, code, results)) { super.onRequestPermissionsResult(code, perms, results) } }
The SDK gets the FCM token on every launch and sends it with the system notification setting. The library declares its own Firebase messaging service, a transparent click activity and a dismiss receiver, so a basic setup needs no more code.
Taps are recorded as Push Clicked. Then the app opens: through the deep link if your app handles it, otherwise on the launcher activity. The push payload is in intent.getBundleExtra("io.trueengage.push").
To listen natively:
TrueEngage.setEventListener { type, payload ->
// type: pushClicked | pushReceived | pushDismissed | tokenRegistered
}
Campaign pushes must be data-only
TrueEngage sends title, body, image and a te block in the FCM data field. Do not add a notification block to messages you send yourself for TrueEngage. With one, Android shows the push itself when the app is in the background, and the SDK never sees it. You then get no receipt and no click attribution.
If your app has its own FirebaseMessagingService
Only one messaging service handles each message. Remove the library's service in your manifest, then forward payloads:
<service
android:name="io.trueengage.sdk.push.TEFirebaseMessagingService"
tools:node="remove" />
override fun onMessageReceived(message: RemoteMessage) {
if (!PushHandler.handle(applicationContext, message.data)) {
// not a TrueEngage push; handle it yourself
}
}
override fun onNewToken(token: String) {
TrueEngage.setPushToken(token)
}
The library also registers a receiver that records Push Received for every TrueEngage push, whichever service handles it. The receiver only records. It never shows a notification.
If you draw the notification yourself, pass display = false to PushHandler.handle(...) and record taps yourself:
TrueEngage.trackPushClicked(remoteMessage.data) // false if not a TrueEngage push
Each push is recorded at most once for received and once for clicked, so overlapping calls are safe.
Smart links and deep links
Smart links open your app through Android App Links. The SDK resolves the link and hands the result to your app, which decides where to go. It never navigates.
- Add an
autoVerifyintent filter to the activity that receives links, with both hosts from Smart Links, then Domains in the console. - Call
TrueEngage.handleIntent(intent)from bothonCreateandonNewIntent. - Listen with
TrueEngage.onDeepLink { link -> ... }and route bylink.deepLinkValueandlink.params.
TrueEngage.onDeepLink { link ->
val path = link.deepLinkValue.orEmpty() // for example "/product/42"
if (path.startsWith("/product/")) openProduct(path.removePrefix("/product/")) else openHome()
}
The listener fires once per open. That includes the deferred match after a first install, which uses the Play Install Referrer and needs no code. A link that arrives before you attach a listener is held and delivered once. Never put personal data in link parameters. For the manifest entry, testing and every field of the payload, see Deep links and deferred deep links.
Create links in the app
createLink makes a short smart link for a share, an invite or a referral. Turn on in-app links in Smart Links, then Settings first.
TrueEngage.createLink(TELinkOptions(deepLinkValue = "/product/42", channel = "invite")) { result ->
when (result) {
is TELinkResult.Success -> share(result.link.url)
is TELinkResult.Failure -> { /* DISABLED, RATE_LIMITED, INVALID or NETWORK */ }
}
}
The callback runs on the main thread, and nothing is retried for you. See Links API and webhooks.
Ad click capture
When the Play install referrer (first launch) or an opened link carries utm_*, gclid, gbraid, wbraid, gad_source, gad_campaignid, fbclid or fbp, the SDK records one touch. A link with none of them records nothing, and nothing is recorded while tracking is opted out. Pass your Firebase app instance id so Google Analytics 4 can match app events:
TrueEngage.setFirebaseAppInstanceId(id)
To include the Google advertising id, declare com.google.android.gms.permission.AD_ID and add play-services-ads-identifier. See See where each user came from and Send events to Google Analytics 4.
Location
Location is off unless you turn it on. Your app declares the permission and shows its own explanation first.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
TrueEngage.requestLocationPermission(activity) { result ->
// "granted" | "denied" | "restricted" | "not_requested"
}
override fun onRequestPermissionsResult(code: Int, perms: Array<String>, results: IntArray) {
super.onRequestPermissionsResult(code, perms, results)
TrueEngage.handleLocationPermissionResult(code, results)
}
TrueEngage.updateLocation { stored -> /* true if a new fix was stored */ }
See Device location for the Google Play disclosure requirement and what is collected.
Consent and opt-out
TrueEngage.optOut() // everything; saved across restarts
TrueEngage.optOutOf("push") // "tracking" | "push" | "inApp" | "all"
TrueEngage.optIn()
TrueEngage.optInTo("push")
TrueEngage.isOptedOut("tracking")
See Consent and opt-out.
Clearing ad identifiers
The SDK tells TrueEngage to delete the stored advertising id when the user limits ad tracking, when the id is reset (the clear and the new id travel together), when you pass null to setAdvertisingIdentifier() or setFirebaseAppInstanceId(), and when you call optOut("tracking") or optOut(). It happens automatically, only for ids the SDK reported before, and needs no code. An opt-out clear is sent even with flushOnOptOut set to false. reset() sends nothing. Requires SDK 0.8.1. See When ad identifiers are cleared.
Debug and validate
val info = TrueEngage.debugInfo() // ids, queue depth, last flush status, counters
TrueEngage.enableValidation() // logs name and type checks for each event
Put debugInfo() on a hidden debug screen. Set logLevel(LogLevel.VERBOSE) in debug builds.
Verify it works
- Run a debug build that uses a
test_key. Open the app and trigger an event. - In the console, switch to Test and go to Audience → Users.
- Search for the id you passed to
identifyand open the profile. - Check the activity list for
App Openedand your own events.

Also open Apps in the sidebar. The first event from a new app verifies it.
Troubleshooting
| Symptom | Likely cause and fix |
|---|---|
| No user in the console | Wrong environment. A test_ key writes to Test only. |
| Nothing arrives | The device is offline, or the user opted out. Check debugInfo() for queue depth and last flush status. |
| Events stop after a while | The key was rejected. The SDK pauses for one hour on a bad key. |
| First events are missing | initialize ran in an Activity instead of Application.onCreate(). |
| No push token | google-services.json or the Google Services plugin is missing. Check logcat at VERBOSE. |
Push shows but no Push Received or click |
The message has a notification block. Send data-only messages. |
| Another library wins the messaging service | Forward payloads with PushHandler.handle as shown above. |
Location returns restricted |
ACCESS_FINE_LOCATION is missing from your manifest. |
Was this page helpful?