Skip to content
IntegrateAndroid SDK (Kotlin/Java)

Integrate

Android SDK

The reference for the Android SDK, which catches the invite a native Android app was installed or opened with and hands it over in the shape POST /v1/attributions takes. Kotlin, and usable from Java.

The Android SDK catches the invite your app was installed or opened with: an App Link to /a/<code>, the Play install referrer, your deep-link SDK's params, or, if nothing else came, a probable match of the first launch to a recent click. It catches once per install and keeps what it caught. It only reacts to Flow Affiliate invites, never to your app's own referral links. It holds no keys, and no money ever comes from the phone. It gives your app an affiliate, which your app sends to your backend, and your backend forwards to POST /v1/attributions. Links into your app explains each way in.

It is written in Kotlin, works from Java, and runs from Android 5.0 (minSdk 21).

Install

// app/build.gradle.kts
dependencies {
    implementation("io.yflow:affiliate-android:0.1.0")
}

It brings Google's Play Install Referrer library and kotlinx-coroutines, and adds the INTERNET permission to your manifest. Nothing else.

Set up

Set it up once in your Application, hand it the intent your Activity is opened with, and start it at launch. Hand the intent over before you call start(), so a link counts ahead of the Play referrer.

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        FlowAffiliate.init(this, FlowAffiliateConfig(appId = "6f1c2b3a-4d5e-4f60-8a7b-9c0d1e2f3a4b"))
    }
}

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        FlowAffiliate.handle(intent)
        lifecycleScope.launch {
            val affiliate = FlowAffiliate.start() // null when nothing was caught
        }
    }

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

FlowAffiliateConfig

appIdStringrequired

Your app's id in Flow Affiliate, a UUID, shown as App ID in the program's Install the SDK step. Not a secret: it is safe to ship in the app.

apiUrlStringdefault https://api.affiliate.yflow.io
Flow Affiliate's API.
linkHostsList<String>default none

Your own link domain, if you have one, such as go.yourapp.com; *.yourapp.com covers its subdomains. They are added to Flow Affiliate's, FlowAffiliateConfig.DEFAULT_LINK_HOSTS: go.affiliate.yflow.io and every subdomain of it, your app's link host yourapp.go.affiliate.yflow.io included. A link's /a/<code> is read only on these; fg_ref and fg_click on any link.

installMatchBooleandefault true

With nothing else caught, the first launch asks Flow Affiliate whether it matches a recent click. false turns it off. See install matching.

timeoutMsLongdefault 5000
How long the install match may take. Without an answer, a later launch asks again.

It checks the channels in this order and keeps the first that names a code or a click:

  1. The link the app was opened with, handed over with handle(intent), on every launch until something is caught. source is link.
  2. The Play install referrer, on the first launch, unless a link came first. source is play_referrer.
  3. Your deep-link SDK's params, handed over with ingest. source is branch for Branch, other for anything else.
  4. The install match, once per install: on the first launch, or a later one within 72 hours if Flow Affiliate wasn't reached. Only when nothing above caught anything. source is install_match and confidence is probable.

Only Flow Affiliate invites count: fg_click and fg_ref on any link, or /a/<code> on a link host. Your app's own referral links and keys, such as code, are never read. The first catch is kept for the life of the install; later ones are answered (handle returns true) but affiliate doesn't change.

The install id and the catch are kept in SharedPreferences, which Android deletes with the app. When Android's backup restores them onto a new install, the SDK sees that the install is not the one that saved them and starts over, so a reinstall is a new install.

An installed app opens straight from a publisher's link. Your app proves it owns the link host, and handle(intent) reads the link.

  1. Under Tracking → Links, fill in your Android package name and its SHA-256 certificate fingerprints. Flow Affiliate then serves /.well-known/assetlinks.json on your app's link host, built from exactly those fingerprints and covering /a/*.
  2. Enter the fingerprint of your Play App Signing key: Google Play re-signs your app with it, so it is the certificate Android checks on every phone that installed from Play. Play Console shows it under App integrity, as the app signing key certificate. Add your upload key and your debug key too, to test builds you install yourself.
  3. Declare the link host in your manifest, as Tracking → Links → Advanced → Tracking host shows it, with autoVerify and the /a/ path. Add your own domain as another data host if you have one.
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop">
    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https" />
        <data android:host="yourapp.go.affiliate.yflow.io" />
        <data android:pathPrefix="/a/" />
    </intent-filter>
</activity>

Check it on a phone:

adb shell pm verify-app-links --re-verify com.yourapp
adb shell pm get-app-links com.yourapp      # the host should say "verified"
adb shell am start -a android.intent.action.VIEW -d "https://yourapp.go.affiliate.yflow.io/a/k7qxa2mp"

An Android phone without your app goes to Google Play instead, with the click in the install referrer. There is nothing to set up in the app for that: paste your Google Play link under Tracking → Links. Only installs from Google Play carry it.

The affiliate

Affiliate is the body of POST /v1/attributions. toJson() gives it as JSON: send it to your backend as it is, and your backend forwards it, adding appUserId only for a user who already has an account.

{
  "url": "https://yourapp.go.affiliate.yflow.io/a/k7qxa2mp",
  "code": "k7qxa2mp",
  "source": "link",
  "confidence": "deterministic",
  "platform": "android",
  "installId": "5b1d0c9e-2f4a-4b8c-9d7e-6f5a4b3c2d1e"
}

Affiliate

codeString?
The code, read on the device or answered by the install match. Use it to show who invited the person.
clickIdString?
The click id, read the same way.
sourceAffiliateSourcerequired
LINK, PLAY_REFERRER, BRANCH, INSTALL_MATCH or OTHER; .value is the wire name, link and so on.
confidenceAffiliateConfidence?
DETERMINISTIC for a link or the Play referrer, PROBABLE for an install match. For deep-link params, Branch's +match_guaranteed; otherwise null.
installIdStringrequired
A random id made at the install's first launch. Asking again with it answers with the same attribution key.
urlString?
The link the app was opened with.
paramsMap<String, Any?>?
Your deep-link SDK's params, as you handed them over.
publisherNameString?
An install match only: the publisher's display name, for "Invited by Anna". For display; the ask ignores it.
platformString
Always android.

Send it to your backend

Asking binds nothing, so send it as soon as you have it, before sign-up if you like. affiliate holds the kept catch from the start of every launch; asking again with the same installId answers with the same key, so sending it again is safe.

lifecycleScope.launch {
    FlowAffiliate.affiliate.filterNotNull().collect { affiliate ->
        backend.post("/affiliate/attribution", affiliate.toJson())
    }
}

Your backend asks Flow Affiliate with its own signed key, and keeps the attribution key until it knows whether the user is new. Then it confirms with user.registered. See Attribution for apps.

// your backend, with the Node SDK
const ask = await fg.createAttribution(req.body);
if (ask.ok && ask.data.attributable) await saveForInstall(req.body.installId, ask.data.attributionKey);

If links reach your app through Branch, AppsFlyer or Adjust, hand their params over with ingest. Only fg_ref and fg_click, and Flow Affiliate links among the values, are read. It returns true when they carry an invite.

// Branch: the JSONObject from onInitFinished
FlowAffiliate.ingest(referringParams)
// AppsFlyer: the deep link's click event, or the conversion data map
FlowAffiliate.ingest(deepLinkResult.deepLink?.clickEvent)
// Adjust: the deferred deep link
FlowAffiliate.ingest(mapOf("deeplink" to deeplink.toString()))

Typed codes

Every app needs a field where the person can enter the publisher's code: an install match can miss, and a phone that didn't install from Google Play carries no referrer. Show it at sign-up when nothing was caught, and fill it in when something was. normalizeCode reads what someone typed, also in groups, or the code in a Flow Affiliate link or a message with one. Codes are 3 to 24 letters and digits, and match whatever their case.

FlowAffiliate.normalizeCode(" K7QX A2MP ")                               // "K7QXA2MP"
FlowAffiliate.normalizeCode("https://go.affiliate.yflow.io/a/k7qxa2mp")  // "k7qxa2mp"
FlowAffiliate.normalizeCode("Join me: https://yourapp.go.affiliate.yflow.io/a/k7qxa2mp") // "k7qxa2mp"
FlowAffiliate.normalizeCode("hello!")                                    // null

It is not part of the automatic catch: your backend asks with it as code. A code that isn't Flow Affiliate's answers unknown_code, so a field you already have for your own referral codes keeps working. See the code field.

The invite greeting

Optional. invite(code) answers what Flow Affiliate's invite page shows for a code (GET /v1/share/landing/:code): the greeting the publisher chose, the welcome bonus if the link carries one, and the app's name, logo, colour and tagline. Null for a code that isn't live, or when Flow Affiliate doesn't answer within 2 seconds.

val invite = FlowAffiliate.invite(affiliate.code ?: return)
// invite?.greeting: "Anna invites you to Yourapp", invite?.bonus: "5 USD"

From Java, FlowAffiliate.invite(code, invite -> { … }) calls back on the main thread.

FlowAffiliate

FlowAffiliate

init(context, config)Unit
Sets the SDK up, in Application.onCreate. Only the first call counts.
start()suspend Affiliate?

Checks what this launch brought, once, and answers with the affiliate, or null when nothing was caught. Calling it again answers with the same check. From Java, start(listener) calls back on the main thread.

handle(intent)Boolean
The intent an Activity was opened with, from onCreate and onNewIntent. Also takes a Uri or a String. True when it carries an invite.
ingest(params)Boolean
A deep-link SDK's params, as a Map or Branch's JSONObject. True when they carry an invite.
affiliateStateFlow<Affiliate?>
What this install was first caught with, kept until the app is deleted. From Java, getAffiliate().getValue().
addListener(listener)Unit
Calls listener on the main thread whenever affiliate changes; removeListener stops it.
installIdString?
This install's id; null until the first start() has read it.
normalizeCode(input)String?
The code in what someone typed. Reads /a/<code> on your linkHosts too.
invite(code)suspend AffiliateInvite?
The greeting and bonus for a code.
reset()Unit
Forgets the install id and the catch, as a reinstall would, and checks again. For testing.

What it never does

  • Touch the clipboard. It never reads or checks it.
  • Hold a key, or ask about attribution. Your backend asks, with its own signed key. No money comes from the phone.
  • Match without saying so. The install match sends the OS version, model, screen size, language, time zone and install id, and nothing else; Flow Affiliate sees the IP address the request comes from and keeps only a keyed hash of it. installMatch = false turns it off.