public final class Invites

  1. Object
  2. Invites

Invite a friend, and follow the invitation through to what it caused.

Mint an invite, share it, and on the friend’s device recover the invite that produced the install. Once attribution resolves it is written as persistent analytics dimensions, so every later event – including the purchase event the framework already emits – carries the campaign and the referrer, and revenue per campaign comes out of the reports you have.

Sending

Invite invite = Invites.create(InviteRequest.create()
        .campaign("spring")
        .channel("share_sheet")
        .build());
Invites.share(invite, "Come and try this with me");

create returns immediately and works with no network, so the share sheet never waits on a server. Registration with the link service is retried in the background.

Receiving

Invites.setInviteListener(new InviteListener() {
    public void inviteReceived(InviteAttribution attribution) {
        // attribution.getCode(), getCampaign(), getPayload()
    }

    public void attributionUnavailable(String reason) {
    }
});
Invites.checkForInvite();

Call checkForInvite from your start() method. It is a pull rather than a callback on purpose: Android delivers a link by replacing the activity intent and iOS by setting a property, and reading the launch argument is the one path that behaves the same on both.

Everything reported here is gated on the analytics consent category of Analytics, and nothing is transmitted until consent is granted.

Nothing about the device is collected, before consent or after it. An earlier design wrote a coarse profile – operating system version, hardware model, language, screen size – to local storage on first launch, because an iOS install could then be matched to a click statistically. App Clips removed the need: the clip is launched by the invite link and is handed the code itself, so there is nothing to match and nothing to keep.

What is stored locally is the invite code and the bookkeeping around it – a state, a deadline, an attempt count – and the code is only ever one the person produced by tapping an invite. setAttributionWindow with 0 switches deferred attribution off entirely.

How exact the answer is

InviteAttribution.getMatchType says how the attribution was made, and every one of them is exact. MATCH_DIRECT is a link opening an application that was already installed; MATCH_REFERRER is a code that made the whole trip through the Play store; MATCH_APP_CLIP is a code an iOS App Clip received from the link itself and handed to the application it installed.

There used to be a statistical match here as well, because the App Store carries no referrer of its own and an iOS install could only be guessed at. It was occasionally wrong, it could not say which times, and it required collecting a hashed profile of people who installed nothing. App Clips made it unnecessary and it is gone.

Fields

public static final int STATE_NONE = 0Nothing has been attributed and nothing is outstanding.
public static final int STATE_PENDING = 1An invite is being resolved; the answer has not arrived yet.
public static final int STATE_RESOLVED = 2This install has been attributed to an invite.
public static final int STATE_NONE_FOUND = 3No invite will be attributed to this install.
public static final int STATE_DECLINED = 4Attribution was abandoned because analytics consent was refused.
public static final String MATCH_DIRECT = "direct"The link opened an application that was already installed.
public static final String MATCH_REFERRER = "referrer"The invite code made the whole trip through the application store and came back verbatim.
public static final String MATCH_APP_CLIP = "app_clip"An iOS App Clip received the invite link, kept the code, and handed it to the application the person then installed.
public static final String REASON_NO_MATCH = "no_match"No invite matched.
public static final String REASON_EXPIRED = "expired"The attribution window closed before an answer arrived.
public static final String REASON_CONSENT_DENIED = "consent_denied"Analytics consent was refused, so attribution was abandoned.
public static final String REASON_UNSUPPORTED = "unsupported"This platform cannot recover a deferred invite.
public static final String CATEGORY = "referral"The analytics category every invite event is reported under.
public static final String DIMENSION_CODE = "cn1_invite_code"Dimension carrying the matched invite code.
public static final String DIMENSION_CAMPAIGN = "cn1_campaign"Dimension carrying the campaign the invite belonged to.
public static final String DIMENSION_CHANNEL = "cn1_channel"Dimension carrying the channel the invite was sent through.
public static final String DIMENSION_MATCH = "cn1_invite_match"Dimension carrying how the attribution was made.
public static final long DEFAULT_ATTRIBUTION_WINDOW = 604800000LThe default attribution window: how long after a first launch a deferred invite may still be resolved.

Methods

public static void registerInstallReferrerSource(InstallReferrerSource source)Registers the platform hook that reads the application store’s install referrer.
public static void registerAppClipHandoffSource(AppClipHandoffSource source)Registers the platform hook that reads the invite code an iOS App Clip left for this application.
public static Invite create(InviteRequest request)Mints an invite and returns it immediately.
public static void share(Invite invite, String message)Shares an invite through the native share sheet.
public static void share(Invite invite, String message, Rectangle sourceRect, ShareResultListener resultListener)Shares an invite through the native share sheet and reports the outcome.
public static void reportShareResult(Invite invite, ShareResult result)Reports the outcome of a share your application performed itself, rather than through share.
public static void setInviteListener(InviteListener l)Registers the listener that receives the invite behind this install.
public static InviteListener getInviteListener()The registered listener, or null.
public static boolean checkForInvite()Looks for an invite: first in the launch argument, then, when this looks like a fresh install, by asking the link service.
public static boolean handleUrl(String url)Offers a url to the invite machinery directly, for applications that consume the launch argument themselves or route it through com.codename1.router.
public static InviteAttribution getAttribution()The attribution for this install, or null when there is none yet.
public static int getState()Where attribution has got to: one of the STATE_ constants.
public static void conversion(String action)Reports that the invited user reached the outcome the invite existed for – signed up, joined the room, completed onboarding.
public static void conversion(String action, double value, String currency)Reports a conversion carrying a value, so revenue can be attributed to the campaign and the referrer.
public static void setLinkBase(String url)Points the invite machinery at a different link service.
public static String getLinkBase()The link service base address in use.
public static void setAttributionWindow(long millis)How long after a first launch a deferred invite may still be resolved.
public static long getAttributionWindow()The attribution window in milliseconds.
public static void setReattribution(boolean value)Whether a later invite replaces an earlier attribution.
public static boolean isReattribution()Whether last touch attribution is enabled.
public static void flush()Retries anything queued: unregistered invites, and an outstanding deferred match.
public static void reset()Forgets every trace of invite attribution on this device: the pending lookup, the resolved attribution and the referral dimensions.
public static boolean isRegistered(Invite invite)Whether the link service has acknowledged this invite.

Inherited methods

Field details

STATE_NONE

public static final int STATE_NONE = 0
Nothing has been attributed and nothing is outstanding.

STATE_PENDING

public static final int STATE_PENDING = 1
An invite is being resolved; the answer has not arrived yet.

STATE_RESOLVED

public static final int STATE_RESOLVED = 2
This install has been attributed to an invite.

STATE_NONE_FOUND

public static final int STATE_NONE_FOUND = 3
No invite will be attributed to this install.

STATE_DECLINED

public static final int STATE_DECLINED = 4
Attribution was abandoned because analytics consent was refused.

MATCH_DIRECT

public static final String MATCH_DIRECT = "direct"
The link opened an application that was already installed. Exact.

MATCH_REFERRER

public static final String MATCH_REFERRER = "referrer"
The invite code made the whole trip through the application store and came back verbatim. Exact.

MATCH_APP_CLIP

public static final String MATCH_APP_CLIP = "app_clip"
An iOS App Clip received the invite link, kept the code, and handed it to the application the person then installed. Exact.

REASON_NO_MATCH

public static final String REASON_NO_MATCH = "no_match"
No invite matched. The ordinary outcome for an uninvited install.

REASON_EXPIRED

public static final String REASON_EXPIRED = "expired"
The attribution window closed before an answer arrived.

REASON_UNSUPPORTED

public static final String REASON_UNSUPPORTED = "unsupported"
This platform cannot recover a deferred invite.

CATEGORY

public static final String CATEGORY = "referral"
The analytics category every invite event is reported under.

DIMENSION_CODE

public static final String DIMENSION_CODE = "cn1_invite_code"
Dimension carrying the matched invite code.

DIMENSION_CAMPAIGN

public static final String DIMENSION_CAMPAIGN = "cn1_campaign"
Dimension carrying the campaign the invite belonged to.

DIMENSION_CHANNEL

public static final String DIMENSION_CHANNEL = "cn1_channel"
Dimension carrying the channel the invite was sent through.

DIMENSION_MATCH

public static final String DIMENSION_MATCH = "cn1_invite_match"
Dimension carrying how the attribution was made.

DEFAULT_ATTRIBUTION_WINDOW

public static final long DEFAULT_ATTRIBUTION_WINDOW = 604800000L
The default attribution window: how long after a first launch a deferred invite may still be resolved.

Method details

registerInstallReferrerSource

public static void registerInstallReferrerSource(InstallReferrerSource source)
Registers the platform hook that reads the application store’s install referrer. The Codename One build calls this before the application starts on platforms that have one; an application does not.

Parameters

source InstallReferrerSource
the platform source, or null to remove it

registerAppClipHandoffSource

public static void registerAppClipHandoffSource(AppClipHandoffSource source)
Registers the platform hook that reads the invite code an iOS App Clip left for this application. The Codename One build calls this before the application starts on platforms that have one; an application does not.

Parameters

source AppClipHandoffSource
the platform source, or null to remove it

create

public static Invite create(InviteRequest request)

Mints an invite and returns it immediately.

This never blocks and never fails for want of a network. The code is generated on the device, so Invite.getUrl is usable at once; registration with the link service is queued and retried until it lands. A link clicked before that registration arrives is still attributed, because the server records the click against the code and joins it when the registration turns up.

Parameters

request InviteRequest
what to mint, must not be null

Returns

the invite, never null

Throws

IllegalStateException
when the device cannot supply secure randomness. The code is the digest of a secret and that secret is what proves who minted it, so a guessable one is a forgeable proof – an invite anybody it is shared with could register as their own. Failing here is visible on the broken device; minting anyway is invisible on every device the link reaches.

share

public static void share(Invite invite, String message)
Shares an invite through the native share sheet.

Parameters

invite Invite
the invite to share, must not be null
message String
text placed before the link, or null for the link alone

share

public static void share(Invite invite, String message, Rectangle sourceRect, ShareResultListener resultListener)

Shares an invite through the native share sheet and reports the outcome.

The invite funnel’s invite_shared event is emitted from here, and only when the platform confirms the user actually shared – a dismissed sheet reports invite_share_dismissed instead. That is what makes the “shared” number a measurement rather than an assumption.

Parameters

invite Invite
the invite to share, must not be null
message String
text placed before the link, or null for the link alone
sourceRect Rectangle
popover anchor hint, may be null
resultListener ShareResultListener
receives the share outcome, may be null

reportShareResult

public static void reportShareResult(Invite invite, ShareResult result)

Reports the outcome of a share your application performed itself, rather than through share. Use this when the invite goes out through your own user interface – a contact picker, a message composer, a copy-link button – so the funnel still records whether it was really sent.

invite_shared is emitted only when result says the user actually shared; a dismissed sheet reports invite_share_dismissed instead. Calling this is optional and calling it twice for one share double counts, so call it once, from the share callback.

Parameters

invite Invite
the invite that was shared, must not be null
result ShareResult
the outcome the platform reported, may be null

setInviteListener

public static void setInviteListener(InviteListener l)

Registers the listener that receives the invite behind this install.

An answer that arrived before the listener was registered – which happens routinely on a cold launch from a link, because the platform delivers the link before the application starts – is delivered as soon as this is called.

Parameters

l InviteListener
the listener, or null to remove it

getInviteListener

public static InviteListener getInviteListener()
The registered listener, or null.

Returns

the listener

checkForInvite

public static boolean checkForInvite()

Looks for an invite: first in the launch argument, then, when this looks like a fresh install, by asking the link service.

Safe and cheap to call on every start; it will not attribute twice and will not report twice.

Call it on every start rather than only the first. A lookup that ended with “not yet” – the invite exists but the inviter minted it offline and their registration has not reached the service – is retried here, at most once per retry interval, so an invite that becomes claimable during the session is picked up in the session rather than on the next cold start. flush does the same for an application that knows it has just regained connectivity.

Returns

true when the launch argument carried an invite link

handleUrl

public static boolean handleUrl(String url)
Offers a url to the invite machinery directly, for applications that consume the launch argument themselves or route it through com.codename1.router.

Parameters

url String
the url to inspect, may be null

Returns

true when the url carried an invite code

getAttribution

public static InviteAttribution getAttribution()
The attribution for this install, or null when there is none yet.

Returns

the attribution

getState

public static int getState()
Where attribution has got to: one of the STATE_ constants.

Returns

the current state

conversion

public static void conversion(String action)
Reports that the invited user reached the outcome the invite existed for – signed up, joined the room, completed onboarding. No-op unless this install was attributed.

Parameters

action String
what the user did

conversion

public static void conversion(String action, double value, String currency)
Reports a conversion carrying a value, so revenue can be attributed to the campaign and the referrer. No-op unless this install was attributed.

Parameters

action String
what the user did
value double
the value of the conversion
currency String
the currency code, or null

setLinkBase

public static void setLinkBase(String url)

Points the invite machinery at a different link service. Defaults to the Codename One cloud, honouring the cloudServerURL display property.

Set the invite.domain build hint to the same host. This changes where links are MINTED and nothing else. The Android intent filter and the iOS associated-domain entitlement are written at BUILD time from that hint, so a host set only here is a host the installed app does not claim: every invite link opens the browser instead of the app, and neither the OS nor the framework reports anything. A mismatch is logged once, because it cannot be refused – pointing at a staging service and accepting the browser is a legitimate thing to do.

A bare host is accepted and read as https://. Anything else that is not HTTPS is REFUSED: Invite.getUrl() promises an absolute https url, and the generated Android filter and iOS associated domain match nothing else, so an http:// base mints links that always open the browser – and it would pass the host check below, which compares hosts and not schemes.

Parameters

url String
the base address, with no trailing path

Throws

IllegalArgumentException
when the address is not HTTPS

getLinkBase

public static String getLinkBase()
The link service base address in use.

Returns

the base address, never null

setAttributionWindow

public static void setAttributionWindow(long millis)
How long after a first launch a deferred invite may still be resolved. Clamped to at most 30 days. Zero switches deferred attribution off, which is the supported way to ship without the statistical match.

Parameters

millis long
the window in milliseconds

getAttributionWindow

public static long getAttributionWindow()
The attribution window in milliseconds.

Returns

the window

setReattribution

public static void setReattribution(boolean value)
Whether a later invite replaces an earlier attribution. Off by default: first touch stands, so a user’s cohort does not change underneath the reports.

Parameters

value boolean
true for last touch

isReattribution

public static boolean isReattribution()
Whether last touch attribution is enabled.

Returns

true when a later invite replaces an earlier one

flush

public static void flush()
Retries anything queued: unregistered invites, and an outstanding deferred match. Called for you on the paths that matter; exposed for an application that knows it has just regained connectivity.

reset

public static void reset()

Forgets every trace of invite attribution on this device: the pending lookup, the resolved attribution and the referral dimensions.

Analytics.resetClientId triggers this for you, because an erasure that left the referral dimensions behind would re-link the fresh identity to the same inviter.

isRegistered

public static boolean isRegistered(Invite invite)

Whether the link service has acknowledged this invite.

An unacknowledged invite is still shareable and still attributes – registration is retried until it lands – so this is a diagnostic rather than a gate.

Parameters

invite Invite
the invite to ask about, may be null

Returns

true once the server has acknowledged it