Docs

Events

Report sign ups, purchases and other conversions with Lander.trackEvent, backed by an offline queue.

App user IDs

Setting an app user ID with Lander.setUserId is currently supported in Flutter 0.4.0 and later. This SDK version does not expose that method; its events continue to use the SDK installation ID.

Installs and opens are recorded for you. Use Lander.trackEvent for the things that happen after: a sign up, a purchase, a completed onboarding. Pass the linkId of the link that brought the user in, and the conversion is attributed to that link.

Track an event

Lander.trackEvent(
    "purchase",
    mapOf("productId" to "123", "amount" to 49.99, "currency" to "USD"),
    linkId = link?.linkId, // optional: the LanderLink that opened this screen
)
Map<String, Object> properties = new HashMap<>();
properties.put("productId", "123");
properties.put("amount", 49.99);
Lander.trackEvent("purchase", properties, linkId);

Lander.trackEvent("signup"); // name only
@JvmStatic
@JvmOverloads
fun trackEvent(
    name: String,
    properties: Map<String, Any?> = emptyMap(),
    linkId: String? = null,
)

Prop

Type

trackEvent is fire and forget: it validates the event, hands it to the on device queue and returns. It does not suspend and is safe to call from the main thread.

What Lander records without you

EventWhenSent by
InstallThe first launch report of a new install IDLander.initialize (POST /v1/sdk/init)
OpenEach time a link opens the app and the SDK resolves itThe resolve call (POST /v1/sdk/resolve)
Handoff openA successful claimHandoffThe claim call

Do not track these yourself, or they will be counted twice.

linkId is only known while you have the LanderLink. Pass it along to the screen (an intent extra or a navigation argument) and use it there:

ProductActivity.kt
class ProductActivity : ComponentActivity() {
    private val productId by lazy { intent.getStringExtra("productId")!! }
    // Set by the activity that received the LanderLink, if any.
    private val linkId by lazy { intent.getStringExtra("linkId") }

    private fun buy() {
        Lander.trackEvent(
            "purchase",
            mapOf("productId" to productId, "amount" to 49.99, "currency" to "USD"),
            // Attribute the purchase to the link that opened this screen, if any.
            linkId = linkId,
        )
    }
}

If the conversion happens later (a sign up a few screens or days after the link), keep the linkId yourself, for example in SharedPreferences, and pass it when the event happens. Unresolved links have no linkId; passing null simply records the event without a link.

How the queue works

Events go to a queue on the device first, then to POST /v1/sdk/events in batches:

  • Batched. A flush runs about 2 seconds after an event (so bursts go out together), when the app goes to the background (the last activity stops), and on the next launch. Each request carries up to 100 events.
  • Survives offline and restarts. The queue is stored in noBackupFilesDir, outside backups. Up to 1000 events are kept; beyond that the oldest are dropped.
  • Waits for the install. Lander rejects events from installs it does not know, so events wait until the first launch report has succeeded, then go out.
  • Retries with backoff. A failed batch is retried after 1 second, doubling up to 5 minutes, with jitter. A longer Retry-After from the API is honored.
  • Counted once. Every event carries a client generated UUID, so a batch sent twice (for example after a lost response) is deduplicated by Lander.
  • Never stuck. A batch the API rejects as invalid is dropped so it cannot block the queue. An auth error (401 or 403) pauses sending until the next launch and keeps the events.

Each event is stored with the time it happened (occurredAt, UTC), so a purchase made offline on Monday and sent on Tuesday is still dated Monday.

Errors

trackEvent throws an IllegalArgumentException for programming mistakes, right away:

  • name is empty or longer than 100 characters.
  • linkId is not a UUID.
  • properties contains something that is not a JSON value (a Date, a custom class, a map with non string keys, NaN or an infinite number). Convert those first, for example a date to an ISO 8601 string.

It never throws for network or server errors; the queue deals with those. It throws an IllegalStateException if called before Lander.initialize.

See your events

Open Live events in the dashboard to watch app events arrive as you test. It refreshes every 5 seconds.

Next steps

On this page