Android

admin

MetaRouter Android SDK

Capture behavioral data directly from your Android app and route it through MetaRouter to any destination — without juggling multiple vendor SDKs.

Why use the Mobile SDK?

One integration, unlimited destinations
Stop embedding separate SDKs for analytics, marketing, and data warehouses. Collect once, route everywhere through MetaRouter.

Update routing without app releases
Add or remove destinations server-side. No code changes, no app store review cycles.

Reliable delivery, even offline
Events are queued locally and delivered automatically when connectivity returns. Built-in retry logic handles transient failures.

Automatic identity resolution
Track users from first app open through sign-up and beyond. The SDK manages anonymous IDs, persists identity across sessions, and connects pre-login activity to known users.

Privacy-ready by design
Built-in support for advertising ID consent, easy opt-out handling, and clean user data reset for GDPR/CCPA compliance.

Lightweight footprint
No bloated dependencies. Minimal impact on app size and startup time.

Quick start

1) Add the SDK

Add JitPack to your project's settings.gradle.kts:

dependencyResolutionManagement {
    repositories {
        maven { url = uri("https://jitpack.io") }
    }
}

Then add the dependency in your module’s build.gradle.kts:

dependencies {
    implementation("com.github.metarouterio:android-sdk:1.0.1")
}

2) Initialize

Add this to your Application.onCreate():

val analytics = MetaRouter.Analytics.initialize(
    context = applicationContext,
    options = InitOptions(
        writeKey = "YOUR_WRITE_KEY",
        ingestionHost = "https://YOUR_CLUSTER.YOUR_SITE.com"
    )
)

Your writeKey and ingestionHost are available in the MetaRouter dashboard.

3) Start tracking

// Track user actions
analytics.track(
    "Product Viewed",
    "sku" to "SHOE-123",
    "price" to 89.99
)

// Identify known users
analytics.identify(
    "user-456",
    "email" to "[email protected]",
    "plan" to "premium"
)

// Track screen views
analytics.screen(
    "Product Detail",
    "category" to "Footwear"
)

Events are batched and delivered automatically. No additional setup required.


Core capabilities

Event tracking

MethodPurpose
track(event, properties)Capture user actions — purchases, clicks, feature usage
screen(name, properties)Record screen views for navigation analytics
page(name, properties)Record page views (web semantics, if applicable)

User identity

MethodPurpose
identify(userId, traits)Associate events with a known user
group(groupId, traits)Associate users with a company, team, or account
alias(newUserId)Link anonymous activity to a newly identified user
getAnonymousId()Returns the anonymous ID the SDK assigned to this device

Lifecycle & privacy

MethodPurpose
flush()Send queued events immediately
reset()Clear all user data (use on logout)
setAdvertisingId(id)Set GAID for attribution (with user consent)
clearAdvertisingId()Remove GAID when user opts out

Identity that just works

The SDK automatically handles the complexity of user identity:

Before login — Users are assigned a stable anonymous ID
On login — Call identify() to attach a known user ID
Across sessions — Identity persists through app restarts
On logout — Call reset() to clear everything and start fresh

Connect anonymous browsing to authenticated users with alias():

// User browses anonymously, then signs up
analytics.track("Product Viewed", "sku" to "ABC") // tracked as anonymous

// User creates account
analytics.alias("new-user-789") // links anonymous → known
analytics.identify("new-user-789", "email" to "[email protected]")

// Full journey is now connected in your downstream tools

Configuration Options

Use InitOptions to configure delivery cadence, logging, in-memory buffering, and disk-backed persistence.

val analytics = MetaRouter.Analytics.initialize(
    context = applicationContext,
    options = InitOptions(
        writeKey = "YOUR_WRITE_KEY",                   // Required
        ingestionHost = "https://YOUR_CLUSTER.mr-in.com", // Required
        flushIntervalSeconds = 10,                     // Optional (default: 10)
        debug = false,                                 // Optional (default: false)
        maxQueueEvents = 2000,                         // Optional (default: 2000)
        maxDiskEvents = 10000                          // Optional (default: 10000)
    )
)
OptionRequiredDefaultDescription
writeKeyYesYour MetaRouter write key. Must not be empty.
ingestionHostYesYour MetaRouter ingestion endpoint. Must be a valid http/https URL with no trailing slash.
flushIntervalSecondsNo10How often the SDK attempts to send queued events. Must be greater than 0 (throws IllegalArgumentException otherwise).
debugNofalseEnables verbose MetaRouter logcat output. Can also be toggled at runtime via analytics.enableDebugLogging().
maxQueueEventsNo2000Maximum events held in the in-memory queue. Must be greater than 0. Queue is also bounded by a 5 MB byte cap.
maxDiskEventsNo10000Maximum unsent events retained on disk for crash safety and offline recovery. Set to 0 to disable disk persistence. Negative values are rejected.
trackLifecycleEventsNofalseAutomatically emit the four Application * lifecycle events.

Lifecycle Events

Turn on automatic tracking of the four standard application-lifecycle events — Application Installed, Application Updated, Application Opened, and Application Backgrounded — to anchor attribution, retention, and session reporting without instrumenting each one by hand. They flow through the same pipeline as your own track calls.

Enable it

Lifecycle events are opt-in. Set trackLifecycleEvents = true in InitOptions:

val analytics = MetaRouter.Analytics.initialize(
    context = applicationContext,
    options = InitOptions(
        writeKey = "your-write-key",
        ingestionHost = "https://your-ingestion-endpoint.com",
        trackLifecycleEvents = true,
    )
)

With the flag left at its default (false), the SDK emits none of these events and recordOpenedUrl(...) is a no-op. Upgrading the SDK never turns them on silently — an app has to opt in.

The events

EventWhen it firesProperties
Application InstalledFirst launch after a fresh install (no prior identity or lifecycle state on the device)version, build
Application UpdatedFirst launch after the app's version/build changes — also the first launch after upgrading from a pre-lifecycle SDK buildversion, build, previous_version, previous_build ("unknown" for the SDK-upgrade case)
Application OpenedCold launch while in the foreground, and each background → foreground resumeversion, build, from_background (false on cold launch, true on resume), plus url / referring_application when a deep link was recorded
Application BackgroundedApp moves to the background

On cold launch, Application Installed/Updated fire before Application Opened, so attribution sees the install or update ahead of the session start. Foreground and background transitions are driven by ProcessLifecycleOwner, so they track the whole app process rather than individual activities.

Install/update state is stored per-device, in a namespace separate from identity — calling reset() clears the user but keeps the install/update history.

Deep links

The SDK doesn't auto-capture deep links; you forward the URL so it can be attached to the next Application Opened event.

MethodPurpose
recordOpenedUrl(uri: Uri, sourceApplication: String? = null)Record a deep-link URL to attach to the next Application Opened event.

Call it from your entry activity:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        forwardDeepLink(intent)
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        forwardDeepLink(intent)
    }

    private fun forwardDeepLink(intent: Intent?) {
        val uri = intent?.data ?: return
        // `Activity.referrer` gives the calling app's host.
        MetaRouter.Analytics.client().recordOpenedUrl(uri, referrer?.host)
    }
}

The next Application Opened the SDK emits carries url (and referring_application when you pass a source). The buffer is one-shot — cleared once that event fires — and last-write-wins if you record more than one URL before it.

🔒

Deep-link URLs often carry secrets — auth tokens, OTPs, magic-link codes. The SDK forwards the URL as-is and does not sanitize it. Strip sensitive query parameters before calling recordOpenedUrl(...).

Notes

  • Disabling disk persistencemaxDiskEvents = 0 puts the queue in pure in-memory ring buffer mode. Oldest events are dropped when maxQueueEvents (or the 5 MB byte cap) is hit, and events will not survive process kill. Use for privacy-constrained or ephemeral environments.
  • Snapshot location — Disk snapshots are stored under noBackupFilesDir (excluded from Android Auto Backup) and are cleared on reset() or after successful rehydration on next launch.

Advertising & attribution

For ad attribution, you can include the Google Advertising ID (GAID):

// Only after obtaining user consent
analytics.setAdvertisingId(advertisingId)

// When user opts out
analytics.clearAdvertisingId()

Privacy note: Always obtain explicit consent before collecting advertising IDs. See the GitHub docs for implementation details:
https://github.com/metarouterio/android-sdk#advertising-id-gaid


WebView Bridge

Hybrid screens don't have to be a blind spot. If parts of your app render as web content inside a WebView, the bridge captures the events your pages fire and delivers them through the native SDK, so web and native activity arrive at your cluster as one user, one pipeline, with native identity and device context on every event.

Attach each WebView when you create it, before loadUrl:

val webView = WebView(context)
analytics.attachWebView(webView, allowedOrigins = listOf("https://www.example.com"))
webView.loadUrl("https://www.example.com/checkout")

Pages loaded from an allowed origin get a window.metarouterBridge object with track and page methods. Properties can be a plain object or a JSON string so a typical integration is a few lines in your existing tag manager:

function logEvent(name, params) {
  if (!window.metarouterBridge) return;
  var value = JSON.stringify(params);
  name === 'page_view'
    ? window.metarouterBridge.page(name, value)
    : window.metarouterBridge.track(name, value);
}

The page surface is identical on Android and iOS, one web integration serves both apps.

What arrives at your cluster: the page contributes the event and its page facts (context.page: url, path, search, title, referrer); the SDK contributes everything durable: anonymousId/userId, device and app context, timestamps, and message IDs. Duplicate deliveries are dropped automatically, and every message gets an acknowledgment or a specific error code (malformed input is rejected), never silently lost.

Things to remember:

  • Attach before loadUrl registrations only apply to page loads that start afterwards.
  • Origins are explicit (https://host[:port]), no wildcards. The allowlist is what keeps ads and redirects in the same WebView from injecting events into your stream.
  • Attachment survives reset() and re-initialization; no re-attach needed.
  • On devices whose WebView provider lacks the required features, the bridge logs a warning and captures nothing — it never falls back to an unscoped mechanism.

Debugging: adb logcat -s MetaRouter shows attach confirmations, rejected messages with their error codes, and duplicate drops. From Chrome DevTools (chrome://inspect), check typeof window.metarouterBridge, or watch native replies live with window.__metaRouterNativeChannel.onmessage = e => console.log(e.data).

Requires SDK 1.4.0+.


Requirements

ComponentVersion
AndroidAPI 23+ (6.0 Marshmallow)
Kotlin2.0+