Instrument and configure the Android-Kotlin SDK
This guide covers the everyday work with the Android-Kotlin SDK after Get started: tracking your own events, managing identity and sessions, choosing Autocapture features, controlling how events reach Amplitude, and extending the SDK with plugins. For exact signatures and every option, go to the Reference. To find the release that added or changed a feature, go to Android-Kotlin SDK release notes.
The samples use an instance named amplitude, created as in Initialize the SDK.
Track events
Track a custom event
Pass an event name, and optionally a map of event properties:
amplitude.track("Song Played", mapOf("title" to "Happy Birthday", "duration_s" to 42))
Don't track push notification events from the device. The SDK uploads only while the app runs, so a notification event can reach Amplitude long after it happened. Send those events from your server instead.
Pass the event as an object
Build a BaseEvent to set fields that the name-and-properties form doesn't take, such as event-level groups or your own insertId for deduplication:
import com.amplitude.core.events.BaseEvent
val event = BaseEvent().apply {
eventType = "Song Played"
eventProperties = mutableMapOf("title" to "Happy Birthday")
groups = mutableMapOf("orgId" to "15")
insertId = "song-played-1234"
}
amplitude.track(event)
Override fields for one event
Pass EventOptions to set fields such as userId, timestamp, or sessionId for one event only:
import com.amplitude.core.events.EventOptions
amplitude.track(
"Purchase Synced",
mapOf("order_id" to "A-100"),
EventOptions().apply { timestamp = orderTimeMillis },
)
Get the upload result for an event
Pass a callback with a BaseEvent. The SDK calls it when it finishes with the event: status is 200 when Amplitude accepted it, or the error status when the SDK dropped it.
amplitude.track(BaseEvent().apply { eventType = "Button Clicked" }) { event, status, message ->
Log.d("MyApp", "${event.eventType}: $status $message")
}
To get results for every event, set the callback configuration option instead. For the statuses, go to Read the upload result.
Set user properties
Build an Identify with one or more operations and pass it to identify():
import com.amplitude.core.events.Identify
val identify = Identify()
.set("color", "green")
.setOnce("initial_source", "organic")
.add("login_count", 1)
.append("visited_pages", "home")
.unset("temporary_property")
amplitude.identify(identify)
For every operation, go to Identify. Use each property once per Identify; the SDK ignores a second operation on the same property. clearAll() removes every user property permanently.
To reduce the number of events it sends, the SDK holds identify events that contain only set operations and merges them. It sends the merged event after identifyBatchIntervalMillis (30 seconds by default), or right before the next other event, whichever comes first.
User property changes appear on the user's profile right away. Charts apply them to events the user sends afterward, not to earlier events.
Assign a user to a group
Groups let you analyze by an account or organization, for example counting distinct organizations that performed an event. setGroup() assigns the current user to a group and sets groupType as a user property, replacing any earlier value for that type.
amplitude.setGroup("orgId", "15")
amplitude.setGroup("sport", arrayOf("tennis", "soccer")) // more than one group
To put one event in a group without changing the user, set groups on a BaseEvent. Go to Pass the event as an object.
Set group properties
amplitude.groupIdentify("plan", "enterprise", Identify().set("seats", 50))
Group property changes apply to events sent afterward. Amplitude supports up to 5 group types per event and 10 groups in total.
Track revenue
Create a Revenue with at least price, and pass it to revenue(). The SDK sends a revenue_amount event.
import com.amplitude.core.events.Revenue
val revenue = Revenue().apply {
productId = "com.example.premium"
price = 3.99
quantity = 3
}
amplitude.revenue(revenue)
For revenue verification, set revenueType and call revenue.setReceipt(receipt, receiptSignature), which sets receipt and receiptSig. For every field, go to Revenue.
Identity and sessions
How the SDK chooses identifiers on startup
Device ID
On startup, the SDK uses the first of these that applies:
- The
deviceIdconfiguration option, if you set it. - The device ID it stored on an earlier run, unless that ID came from the App Set ID.
- The advertising ID, if
useAdvertisingIdForDeviceIdistrue,newDeviceIdPerInstallisfalse, and the user hasn't limited ad tracking. - The App Set ID with
Sappended, ifuseAppSetIdForDeviceIdistrue. - A random UUID with
Rappended.
Because the SDK stores the device ID with the app's data, a backup restored to a new device can bring the old device ID with it. Set the same user ID on every device a user signs in to, so Amplitude merges their devices into one user.
User ID
The SDK restores the user ID it stored on an earlier run. A setUserId() call made before setup finishes wins over the stored value.
Set the user ID
Set the user ID when a user signs in. null clears it.
amplitude.setUserId("user@example.com")
val userId = amplitude.getUserId()
Reset identity when a user signs out
reset() clears the user ID and picks a device ID again, so later events count as a new anonymous user:
amplitude.reset()
reset() doesn't start a new session. It skips only the stored device ID, so it keeps the same device ID when you set the deviceId configuration option, useAdvertisingIdForDeviceId, or useAppSetIdForDeviceId. Amplitude then links events before and after the reset through that device ID. To start a separate anonymous user, set a new device ID after the reset:
amplitude.reset()
amplitude.setDeviceId(UUID.randomUUID().toString())
Set the device ID
Use a value that's unique per device, such as a UUID:
amplitude.setDeviceId(UUID.randomUUID().toString())
Read and update identity together
amplitude.identity returns the user ID, device ID, and local user properties. setIdentity() replaces all three in one step, so plugins such as Experiment see one change instead of several. Read the device ID after setup finishes:
// Inside a coroutine
amplitude.isBuilt.await()
amplitude.setIdentity("user-123", amplitude.getDeviceId(), mapOf("plan" to "paid"))
If the properties change, the SDK sends one identify event that sets them. The local properties live only in memory for the current process. Removing a property from the map doesn't remove it in Amplitude. To remove it, send Identify().unset(). Passing null as the device ID clears it. Requires 1.36.0 or later.
How sessions work
A session starts when the app comes to the foreground, or when you track an event while no session is active. The session continues while the app is in the foreground. When the app goes to the background, the session ends after minTimeBetweenSessionsMillis (5 minutes by default) passes without an event. The next return to the foreground, or the next event, then starts a new session. An event tracked in the background within that time extends the session.
Events in a session share a session_id, which is the session's start time in milliseconds. With SESSIONS autocapture on (the default), the SDK also tracks session_start and session_end events. With it off, the SDK still assigns session IDs.
From 1.34.0, the SDK detects foreground and background from the process lifecycle, so multi-activity apps and apps that start a service before any activity get correct sessions.
Some session data can look incomplete:
- If the user never returns to the app, the SDK sends no
session_endfor that session. - Events tracked in the background make a session look longer than the time the user spent in the app.
- A
session_endsent after a user property change carries the new property values. To stop Amplitude from applying the event's user properties, set$skip_user_properties_syncon it with an enrichment plugin.
Change the session timeout
val amplitude = Amplitude("YOUR_API_KEY", applicationContext) {
minTimeBetweenSessionsMillis = 10_000
}
Stop sending session events
Set autocapture without SESSIONS. Events still get session IDs.
val amplitude = Amplitude("YOUR_API_KEY", applicationContext) {
autocapture = emptySet()
}
Track an event outside a session
Set sessionId = -1 in EventOptions. The event doesn't belong to, extend, or start a session, and doesn't change the session ID of later events. Use it for events the user didn't trigger in the app, such as a background sync.
val outOfSession = EventOptions().apply { sessionId = -1 }
amplitude.track(BaseEvent().apply { eventType = "Sync Completed" }, outOfSession)
amplitude.identify(Identify().set("synced", true), outOfSession)
Opt users out of tracking
While optOut is true, the SDK drops every event and doesn't store it.
amplitude.optOut = true // Stop tracking
amplitude.optOut = false // Resume tracking
To start opted out, set optOut = true in the configuration.
Control which properties the SDK collects
Create a TrackingOptions, turn off the fields you don't want, and set it in the configuration:
import com.amplitude.android.TrackingOptions
val amplitude = Amplitude("YOUR_API_KEY", applicationContext) {
trackingOptions = TrackingOptions().disableIpAddress().disableLatLng().disableCarrier()
}
Amplitude derives city, region, and DMA from the IP address, so disableIpAddress() is what stops them. disableCity(), disableRegion(), disableDma(), and disableApiLevel() change nothing on the device. Turning a field off affects new events only; it doesn't remove data Amplitude already has. For every method, go to TrackingOptions.
Turn on COPPA control
For apps that must comply with the Children's Online Privacy Protection Act (COPPA), set enableCoppaControl = true. COPPA control stops the SDK from collecting the advertising ID, IP address, and latitude and longitude. Without the IP address, Amplitude also stops deriving city. COPPA control doesn't stop the App Set ID. To stop the App Set ID too, also call disableAppSetId().
Use the advertising ID or app set ID
The SDK can use a Google Play identifier as the device ID. Users can reset or turn off the advertising ID, and the advertising ID can be null. For most apps, the App Set ID is the more stable choice.
Advertising ID
Add the Play services dependency:
kotlinimplementation("com.google.android.gms:play-services-ads-identifier:18.0.1")If your app targets Android 13 or later, declare the permission in
AndroidManifest.xml. The SDK doesn't declare it for you.xml<uses-permission android:name="com.google.android.gms.permission.AD_ID" />If you minify with R8 or ProGuard, keep the classes the SDK loads by reflection:
text-keep class com.google.android.gms.ads.** { *; } -keep class com.google.android.gms.common.** { *; }Turn on the advertising ID:
kotlinval amplitude = Amplitude("YOUR_API_KEY", applicationContext) { useAdvertisingIdForDeviceId = true }
App Set ID
The App Set ID identifies an app install on a device without being tied to advertising. It resets after 13 months without use.
Add the dependency. The SDK ships keep rules for it.
kotlinimplementation("com.google.android.gms:play-services-appset:16.0.2")Turn on the App Set ID:
kotlinval amplitude = Amplitude("YOUR_API_KEY", applicationContext) { useAppSetIdForDeviceId = true }
Track location
By default, Amplitude derives location from the IP address. To add the device's last known latitude and longitude, set locationListening = true, and request ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION from the user. The SDK adds coordinates only while the app has the permission.
val amplitude = Amplitude("YOUR_API_KEY", applicationContext) {
locationListening = true
}
The default is false. Default changed in 1.20.7.
Autocapture
Choose autocapture features
By default, the SDK tracks session events only. To turn on more features, set autocapture. Requires 1.17.0 or later.
import com.amplitude.android.autocaptureOptions
val amplitude = Amplitude("YOUR_API_KEY", applicationContext) {
autocapture = autocaptureOptions {
+sessions
+appLifecycles
+screenViews
+deepLinks
+elementInteractions
+frustrationInteractions
}
}
Setting autocapture replaces the default set, so include +sessions to keep session events. In Java, getAutocapture() already contains SESSIONS; call remove(AutocaptureOption.SESSIONS) to drop it. AutocaptureOption.ALL turns on everything.
With enableAutocaptureRemoteConfig on (the default), autocapture settings saved in Amplitude override the local set. Settings that Amplitude doesn't send keep their local values.
Features and events
| Option | Events |
|---|---|
SESSIONS | session_start, session_end. Go to How sessions work. |
APP_LIFECYCLES | [Amplitude] Application Installed, Updated, Opened, Backgrounded. Go to App lifecycles. |
SCREEN_VIEWS | [Amplitude] Screen Viewed, [Amplitude] Fragment Viewed. Go to Screen views. |
DEEP_LINKS | [Amplitude] Deep Link Opened. Go to Deep links. |
ELEMENT_INTERACTIONS | [Amplitude] Element Interacted. Go to Element interactions. |
FRUSTRATION_INTERACTIONS | [Amplitude] Rage Click, [Amplitude] Dead Click. Go to Frustration interactions. |
App lifecycles
| Event | When | Properties |
|---|---|---|
[Amplitude] Application Installed | The first launch after install. | [Amplitude] Version, [Amplitude] Build |
[Amplitude] Application Updated | The first launch after the build number (versionCode) changes. | [Amplitude] Version, [Amplitude] Build, [Amplitude] Previous Version, [Amplitude] Previous Build |
[Amplitude] Application Opened | Each launch or return to the foreground after the first. | [Amplitude] Version, [Amplitude] Build, [Amplitude] From Background |
[Amplitude] Application Backgrounded | The app goes to the background. | None |
Screen views
The SDK tracks [Amplitude] Screen Viewed for each activity and [Amplitude] Fragment Viewed for each fragment. Fragment tracking needs androidx.fragment in your app and requires 1.18.0 or later.
| Property | Description |
|---|---|
[Amplitude] Screen Name | The activity's title, then its label, then its name, then its class name, whichever is set first. |
[Amplitude] Fragment Class | Fragment Viewed only. The fragment's fully qualified class name. |
[Amplitude] Fragment Identifier | Fragment Viewed only. The resource name of the fragment's ID or container view. |
[Amplitude] Fragment Tag | Fragment Viewed only. The fragment's tag. |
Deep links
The SDK tracks [Amplitude] Deep Link Opened with [Amplitude] Link URL and [Amplitude] Link Referrer when an activity starts or resumes with an intent that has data.
If the activity uses the singleTop, singleTask, or singleInstance launch mode, Android delivers a new link to onNewIntent() without restarting the activity. Call setIntent() there, or the SDK keeps reading the original intent:
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
}
Element interactions
The SDK tracks [Amplitude] Element Interacted when a user interacts with an element, in both Android Views and Jetpack Compose. From 1.35.0, it also tracks non-tap gestures such as swipes and long presses, and selectable Compose controls such as Checkbox and Switch.
| Property | Description |
|---|---|
[Amplitude] Action | touch, valueChange, pan, longPress, pinch, or rotation. |
[Amplitude] Target Class | The view's class name. Views only. |
[Amplitude] Target Resource | The resource entry name of the view's ID. Views only. |
[Amplitude] Target Tag | The view's tag when it's a primitive value, or the Compose Modifier.testTag. |
[Amplitude] Target Text | The text of a Button. Views only. |
[Amplitude] Target Accessibility Label | The element's accessibility label. Requires 1.23.0 or later. |
[Amplitude] Target Source | Android View or Jetpack Compose. |
[Amplitude] Hierarchy | The chain of parent views. Views only. |
[Amplitude] Screen Name | Go to Screen views. |
Compose elements have no class, resource, text, or hierarchy, so add Modifier.testTag to the composables you want to tell apart. A tag also stays stable when you restyle the UI:
Button(onClick = { /* ... */ }, modifier = Modifier.testTag("login_button")) {
Text("Log In")
}
Frustration interactions
FRUSTRATION_INTERACTIONS tracks two events. Requires 1.22.0 or later.
[Amplitude] Rage Click: four or more clicks on the same element within one second, no more than 50 dp apart. The event reports every click in the burst.[Amplitude] Dead Click: a click on an interactive element followed by no visible change within three seconds. Dead click detection needs a plugin that reports UI changes, which the Session Replay Android plugin provides. Without one, the SDK tracks no dead clicks.
Both events carry the Element interactions properties, plus:
| Property | Description |
|---|---|
[Amplitude] Begin Time | When the first click happened, as an ISO-8601 UTC timestamp. |
[Amplitude] End Time | Rage click: when the last click happened. Dead click: three seconds after the click. ISO-8601 UTC. |
[Amplitude] Duration | Milliseconds between Begin Time and End Time. Always 3000 for a dead click. |
[Amplitude] X, [Amplitude] Y | The first click's position, from the top-left of the screen. |
[Amplitude] Click Count | The number of clicks. Always 1 for a dead click. |
[Amplitude] Clicks | Rage click only. A list of clicks, each with [Amplitude] X, [Amplitude] Y, and Time. |
To track only one of the two, set interactionsOptions:
import com.amplitude.android.DeadClickOptions
import com.amplitude.android.InteractionsOptions
import com.amplitude.android.RageClickOptions
val amplitude = Amplitude("YOUR_API_KEY", applicationContext) {
autocapture = autocaptureOptions { +frustrationInteractions }
interactionsOptions = InteractionsOptions(
rageClick = RageClickOptions(enabled = true),
deadClick = DeadClickOptions(enabled = false),
)
}
Exclude elements from frustration tracking
Users tap some elements quickly by design, such as stepper buttons. Other elements never change the screen, such as a close button. Exclude these elements from rage or dead click detection. The SDK still tracks [Amplitude] Element Interacted for them.
import com.amplitude.android.FrustrationAnalyticsUtils
import com.amplitude.android.ignoreFrustrationAnalytics
// Views: ignore both, or only rage clicks
FrustrationAnalyticsUtils.ignoreFrustrationAnalytics(backButton)
FrustrationAnalyticsUtils.ignoreFrustrationAnalytics(incrementButton, rageClick = true, deadClick = false)
FrustrationAnalyticsUtils.unignoreView(backButton)
// Compose: ignore only dead clicks
Button(
onClick = { submitForm() },
modifier = Modifier.ignoreFrustrationAnalytics(rageClick = false, deadClick = true),
) { Text("Submit") }
rageClick = true ignores rage clicks and deadClick = true ignores dead clicks. Both default to true.
Network tracking
NetworkTrackingPlugin tracks [Amplitude] Network Request events for HTTP requests made through OkHttp. It isn't part of autocapture: add it as an interceptor on each OkHttp client you want to track, and add it to the instance. Requires 1.21.0 or later, and OkHttp in your app.
import com.amplitude.android.network.NetworkTrackingPlugin
val networkPlugin = NetworkTrackingPlugin()
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(networkPlugin)
.build()
amplitude.add(networkPlugin)
By default, the plugin captures responses with status 500 to 599 from every host except Amplitude. To choose hosts and statuses, pass NetworkTrackingOptions:
import com.amplitude.android.network.NetworkTrackingOptions
import com.amplitude.android.network.NetworkTrackingOptions.CaptureRule
val options = NetworkTrackingOptions(
captureRules = listOf(
CaptureRule(hosts = listOf("*"), statusCodeRange = (400..599).toList()),
CaptureRule(hosts = listOf("*.example.com", "example.com"), statusCodeRange = (500..599).toList()),
),
ignoreHosts = listOf("analytics.example.com"),
)
amplitude.add(NetworkTrackingPlugin(options))
The plugin first skips ignoreHosts and, by default, Amplitude's own requests. It then checks rules from the last to the first, and the first rule whose hosts match decides. With the rules above, the plugin captures a 503 from example.com. It doesn't capture a 401 from example.com, because the last rule matched the host but not the status. It captures a 401 from other.com through the first rule.
To match by URL, filter by method, or capture headers and JSON body fields, use the URL-based rule. Requires 1.28.0 or later.
import com.amplitude.android.network.NetworkTrackingOptions.CaptureBody
import com.amplitude.android.network.NetworkTrackingOptions.CaptureHeader
import com.amplitude.android.network.NetworkTrackingOptions.URLPattern
CaptureRule(
urls = listOf(URLPattern.Regex("^https://api\\.example\\.com/v1/.*")),
methods = listOf("POST"),
statusCodeRange = (200..599).toList(),
requestHeaders = CaptureHeader(allowlist = listOf("X-Request-Id")),
responseHeaders = CaptureHeader(allowlist = listOf("X-Response-Id")),
requestBody = CaptureBody(allowlist = listOf("user/*"), excludelist = listOf("**/password")),
responseBody = CaptureBody(allowlist = listOf("profile/**")),
)
The plugin never captures the authorization, cookie, or proxy-authorization headers, and it masks credentials and common sensitive query parameters in URLs. For every option, go to NetworkTrackingPlugin.
| Property | Description |
|---|---|
[Amplitude] URL, [Amplitude] URL Query, [Amplitude] URL Fragment | The request URL, with sensitive parts masked, and its parts. |
[Amplitude] Request Method, [Amplitude] Status Code | The HTTP method and response status. |
[Amplitude] Error Message | The local error when the request failed without a status. |
[Amplitude] Start Time, [Amplitude] Completion Time, [Amplitude] Duration | Timing, in milliseconds. |
[Amplitude] Request Body Size, [Amplitude] Response Body Size | Body sizes in bytes. |
[Amplitude] Request Headers, [Amplitude] Response Headers, [Amplitude] Request Body, [Amplitude] Response Body | Present when a rule captures them. |
Track media playback
To track Media3 and ExoPlayer playback, add com.amplitude:streaming-analytics-android at the same version as analytics-android. The SDK installs the package automatically when the package is on the classpath. Requires 1.33.0 or later. For changes to media playback events, go to the release notes.
Data delivery
How the SDK uploads events
The SDK writes each event to a file in the app's storage, then uploads queued events when:
- 30 events (
flushQueueSize) are waiting. - 30 seconds (
flushIntervalMillis) pass after an event is queued. - The app goes to the background (
flushEventsOnClose, on by default). - The device reconnects after being offline.
- You call
amplitude.flush().
Stored events survive app restarts. When an upload fails, the SDK keeps the events and retries with growing delays: about 1, 8, 32, and 128 seconds, then every 5 minutes, with no limit. After a 429, it waits 30 seconds. It drops events only when Amplitude rejects them, for example for an invalid API key. The deprecated flushMaxRetries option has no effect. Retries became unlimited in 1.31.0.
Tune upload performance
Raise flushIntervalMillis and flushQueueSize to upload less often, or lower them to see events sooner:
val amplitude = Amplitude("YOUR_API_KEY", applicationContext) {
flushIntervalMillis = 60_000
flushQueueSize = 50
}
flushQueueSize sets when an upload starts, not how many events a request carries. To upload large volumes through the Batch API, set useBatch = true.
Choose a data region
For a project in Amplitude's EU data center, set serverZone. Use the API key from the EU project.
import com.amplitude.core.ServerZone
val amplitude = Amplitude("YOUR_API_KEY", applicationContext) {
serverZone = ServerZone.EU
}
Send events through a proxy
Set serverUrl to your proxy's URL. The proxy forwards the request to Amplitude's endpoint for your data center and returns Amplitude's response unchanged, so the SDK can read upload results.
val amplitude = Amplitude("YOUR_API_KEY", applicationContext) {
serverUrl = "https://analytics.example.com/2/httpapi"
}
The SDK sends plain JSON to a custom serverUrl. To compress the request body, set enableRequestBodyCompression = true; your proxy must accept Content-Encoding: gzip.
serverUrl covers event uploads only. The SDK still fetches remote configuration from sr-client-cfg.amplitude.com (EU: sr-client-cfg.eu.amplitude.com) and sends its diagnostics to Amplitude directly.
Work offline
The SDK holds events while the device has no network and uploads them when the device reconnects. Offline mode needs the ACCESS_NETWORK_STATE permission. Without it, the SDK logs No ACCESS_NETWORK_STATE permission, offline mode is not supported. ... and treats the device as always online, so uploads fail and retry instead. Requires 1.13.0 or later.
To control offline mode yourself, for example to hold uploads until the user gives consent, set offline = AndroidNetworkConnectivityCheckerPlugin.Disabled in the configuration. This setting removes the connectivity check. Then set amplitude.configuration.offline to true or false from your code.
Use a custom HTTP client
The SDK uploads with HttpURLConnection. To use another client, such as OkHttp for certificate pinning or custom timeouts, implement HttpClientInterface and set httpClient. Requires 1.20.0 or later. The SDK repository has a sample OkHttp client with a gzip interceptor.
val httpClient = CustomOkHttpClient()
val amplitude = Amplitude("YOUR_API_KEY", applicationContext) {
this.httpClient = httpClient
}
httpClient.initialize(amplitude.configuration)
A custom client handles compression itself.
Plugins
Plugins run code on every event or react to identity changes. The SDK runs Before, then Enrichment, then Destination plugins for each event. A Before or Enrichment plugin returns the event, changed or not, or null to drop it. For every hook, go to Plugin.
Add properties to every event
import com.amplitude.core.Amplitude
import com.amplitude.core.events.BaseEvent
import com.amplitude.core.platform.Plugin
class AppFlavorPlugin : Plugin {
override val type = Plugin.Type.Enrichment
override lateinit var amplitude: Amplitude
override fun execute(event: BaseEvent): BaseEvent {
event.eventProperties = (event.eventProperties ?: mutableMapOf()).apply {
put("app_flavor", BuildConfig.FLAVOR)
}
return event
}
}
amplitude.add(AppFlavorPlugin())
To run before every other enrichment, for example to guard a field other plugins read, use Plugin.Type.Before.
Send events to another destination
Extend DestinationPlugin and override track(). Override identify(), groupIdentify(), revenue(), and flush() as needed. Don't override execute(); it's final on destination plugins.
import com.amplitude.core.events.BaseEvent
import com.amplitude.core.platform.DestinationPlugin
class LogcatDestination : DestinationPlugin() {
override fun track(payload: BaseEvent): BaseEvent {
Log.d("Events", "${payload.eventType} ${payload.eventProperties}")
return payload
}
}
amplitude.add(LogcatDestination())
React to identity changes
Extend ObservePlugin to react to sign-in, sign-out, and other identity changes without touching events. Override onUserIdChanged and onDeviceIdChanged, plus any of onSessionIdChanged, onOptOutChanged, onIdentityChanged, and onReset.
import com.amplitude.core.Amplitude
import com.amplitude.core.platform.ObservePlugin
class LoginObserver : ObservePlugin() {
override lateinit var amplitude: Amplitude
override fun onUserIdChanged(userId: String?) {
// React to sign-in or sign-out.
}
override fun onDeviceIdChanged(deviceId: String?) {}
}
amplitude.add(LoginObserver())
Remove a plugin
Pass the same instance you added:
val plugin = AppFlavorPlugin()
amplitude.add(plugin)
amplitude.remove(plugin)
Run more than one instance
To send events to more than one project, create one instance per project, each with its own instanceName. Each instance keeps its own storage, identity, and queue.
val product = Amplitude("PRODUCT_API_KEY", applicationContext) {
instanceName = "product"
}
val marketing = Amplitude("MARKETING_API_KEY", applicationContext) {
instanceName = "marketing"
}
Create each instance once, for example in Application.onCreate, and inject it where you need it. With Hilt, Koin, or Dagger, provide it as an application-scoped singleton. Creating a second instance with the same instanceName retires the first one, which then drops new events with Dropping event '<type>': this Amplitude instance was replaced. Requires 1.30.1 or later.
Was this helpful?
For AI agents: a documentation index is available at /docs/llms.txt. Append .md to any page URL for markdown, or send Accept: text/markdown.