
Event-driven form and referral SDK with UI host, user identification, event tracking, referral link creation/redeeming, install-referrer matching, configurable API key and debug logging, themeable rendering.
This is the Compose Multiplatform SDK for WandKit.
This README covers:
wandkit-core: SDK configuration, identity, and event trackingwandkit-ui-compose: Compose host and UI for rendering formsWandKitConfig currently supports:
apiKey: your WandKit API keyisDebugLoggingEnabled: enables SDK debug loggingExample:
WandKitConfig(
apiKey = "your_api_key",
isDebugLoggingEnabled = true,
)Add the SDK modules to your app:
implementation("com.flabbergast.wandkit:core:<version>")
implementation("com.flabbergast.wandkit:ui-compose:<version>")Configure WandKit must be called before calling other WandKit methods.
WandKit.configure(
config = WandKitConfig(
apiKey = "your_api_key",
isDebugLoggingEnabled = true,
),
context = applicationContext,
)WandKit.configure(
config = WandKitConfig(
apiKey = "your_api_key",
isDebugLoggingEnabled = true,
),
)If you have a known user id, identify the user before sending events:
WandKit.identify(userId = "user_123")Clear the identified user when needed:
WandKit.clearUser()Send events with a name and optional string properties:
WandKit.event(
name = "checkout_started",
properties = mapOf(
"plan" to "pro",
"entry_point" to "pricing_screen",
),
)You can also provide a custom event timestamp:
WandKit.event(
name = "signup_completed",
occurredAt = occurredAt,
)The SDK also supports creating and redeeming referral links and codes.
Identify the current user first, then create a referral for a campaign:
WandKit.identify(userId = "user_123")
val referral = WandKit.invite(
userId = "user_123",
campaign = "samplecampaign",
)
val referralUrl = referral?.urlWandKit.invite(...) returns ReferralInfo?. Use ReferralInfo.url as the shareable referral link.
You can also pass optional string properties:
val referral = WandKit.invite(
userId = "user_123",
campaign = "samplecampaign",
properties = mapOf(
"source" to "profile_screen",
),
)If you have a referral short path, you can fetch its metadata:
val referral = WandKit.getReferral(path = "abc123")WandKit.getReferral(...) returns GetReferralResponse?.
How far an inviter is toward their reward - what drives an in-app "3 of 5 friends joined" meter:
val progress = WandKit.getReferralProgress(
userId = "user_123",
campaign = "samplecampaign",
)
val joined = progress?.convertedCount
val goal = progress?.reward?.thresholdWandKit.getReferralProgress(...) returns ReferralProgress?, and null when the
campaign does not exist or this inviter has no referral yet - call
WandKit.invite(...) first.
convertedCount is what counts toward the reward: claims that went on to sign up.
claimedCount is the larger number of installs that merely entered the code.
Ask the backend which referral this install probably came from. Nothing is bound
by this - the returned code is meant to be offered back to the user to confirm or
replace, and redeemCode is what actually claims it:
val detection = WandKit.detectReferral()
val prefill = detection?.codeFingerprint accuracy decays quickly and the server-side match window is short, so
detection has to run early - long before the user has agreed to anything. Call this
right after configure:
WandKit.detectReferralOnFirstLaunchIfNeeded()It runs once per install, in the background, and persists the result. Read it back whenever your UI is ready:
val detection = WandKit.detectedReferralA transient failure does not count as an attempt, so the next launch retries - a dropped attempt costs an inviter a referral they earned. Retries are capped, and a permanent failure (a rejected key, an unreadable response) gives up at once, so an install that can never get an answer stops fingerprinting rather than re-sending on every launch.
redeemCode clears the detection on success, since the question it exists to
answer has been answered. If the user dismisses the prefilled code instead, clear
it yourself:
WandKit.clearDetectedReferral()Redeem a code, whether the user typed it or confirmed a detected one:
val match = WandKit.redeemCode(code = "INVITE_CODE")WandKit.redeemCode(...) returns ReferralMatch?. This is the only call that
creates a claim.
WandKit.installId is this device's install ID, the same one redeemCode claims
with, and it is stable across launches. Forward it to your own backend so it can
report referral conversions server-to-server:
myBackend.reportReferralAttribution(wandkitInstallId = WandKit.installId)You can also ask the SDK to read the install referral code from the platform provider and redeem it:
val match = WandKit.matchReferral()WandKit.matchReferral() returns ReferralMatch?.
Note that this predates detectReferral() and claims the referral immediately,
without asking the user. Prefer detection unless you specifically want the old
auto-claim behaviour.
Important: if you want to use the install referral code provider, you must pass context when calling WandKit.configure(...) on Android.
Without context, the SDK cannot create the Android install referrer client, so WandKit.getInstallReferralCode() and WandKit.matchReferral() will not be able to read the install referral code.
The SDK exposes the raw install referral code lookup as well:
val installReferralCode = WandKit.getInstallReferralCode()Required Android setup:
WandKit.configure(
config = WandKitConfig(
apiKey = "your_api_key",
isDebugLoggingEnabled = true,
),
context = applicationContext,
)On Android, this uses the Play Install Referrer API and extracts the referral_code query parameter from the install referrer payload.
WandKit.matchReferral() uses this provider internally. If a referral code is available, it redeems that code automatically.
On iOS, the install referral code provider is currently not implemented and always returns null. Because of that:
WandKit.getInstallReferralCode() returns null on iOSWandKit.matchReferral() also returns null on iOS unless install referral support is implemented there laterForms are event-driven.
When you call WandKit.event(...), the backend may return a form for that event. If your app has the Compose host mounted, the SDK will present that form automatically.
There is no separate public API for manually opening a form.
To render forms, add WandKitHost() to your Compose UI tree.
@Composable
fun App() {
MaterialTheme {
Box {
MainContent()
WandKitHost()
}
}
}WandKitHost() should be mounted at the root of the composable container where forms can appear.
If that container uses ModalBottomSheetLayout, place the host at that root level so the SDK can present forms correctly inside the same container.
In practice, do not place it deep inside a screen subtree that may not be present when an event returns a form.
You can provide a custom theme to WandKitHost():
WandKitHost(
theme = WandKitThemeDefaults.system(),
)Available defaults:
WandKitThemeDefaults.light()WandKitThemeDefaults.dark()WandKitThemeDefaults.system()You can also construct a custom WandKitTheme with your own colors and typography.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WandKit.configure(
config = WandKitConfig(
apiKey = "your_api_key",
isDebugLoggingEnabled = true,
),
context = applicationContext,
)
setContent {
MaterialTheme {
Box {
ScreenContent(
onAction = {
WandKit.identify("user_123")
WandKit.event(name = "screen_action_tapped")
}
)
WandKitHost()
}
}
}
}
}This is the Compose Multiplatform SDK for WandKit.
This README covers:
wandkit-core: SDK configuration, identity, and event trackingwandkit-ui-compose: Compose host and UI for rendering formsWandKitConfig currently supports:
apiKey: your WandKit API keyisDebugLoggingEnabled: enables SDK debug loggingExample:
WandKitConfig(
apiKey = "your_api_key",
isDebugLoggingEnabled = true,
)Add the SDK modules to your app:
implementation("com.flabbergast.wandkit:core:<version>")
implementation("com.flabbergast.wandkit:ui-compose:<version>")Configure WandKit must be called before calling other WandKit methods.
WandKit.configure(
config = WandKitConfig(
apiKey = "your_api_key",
isDebugLoggingEnabled = true,
),
context = applicationContext,
)WandKit.configure(
config = WandKitConfig(
apiKey = "your_api_key",
isDebugLoggingEnabled = true,
),
)If you have a known user id, identify the user before sending events:
WandKit.identify(userId = "user_123")Clear the identified user when needed:
WandKit.clearUser()Send events with a name and optional string properties:
WandKit.event(
name = "checkout_started",
properties = mapOf(
"plan" to "pro",
"entry_point" to "pricing_screen",
),
)You can also provide a custom event timestamp:
WandKit.event(
name = "signup_completed",
occurredAt = occurredAt,
)The SDK also supports creating and redeeming referral links and codes.
Identify the current user first, then create a referral for a campaign:
WandKit.identify(userId = "user_123")
val referral = WandKit.invite(
userId = "user_123",
campaign = "samplecampaign",
)
val referralUrl = referral?.urlWandKit.invite(...) returns ReferralInfo?. Use ReferralInfo.url as the shareable referral link.
You can also pass optional string properties:
val referral = WandKit.invite(
userId = "user_123",
campaign = "samplecampaign",
properties = mapOf(
"source" to "profile_screen",
),
)If you have a referral short path, you can fetch its metadata:
val referral = WandKit.getReferral(path = "abc123")WandKit.getReferral(...) returns GetReferralResponse?.
How far an inviter is toward their reward - what drives an in-app "3 of 5 friends joined" meter:
val progress = WandKit.getReferralProgress(
userId = "user_123",
campaign = "samplecampaign",
)
val joined = progress?.convertedCount
val goal = progress?.reward?.thresholdWandKit.getReferralProgress(...) returns ReferralProgress?, and null when the
campaign does not exist or this inviter has no referral yet - call
WandKit.invite(...) first.
convertedCount is what counts toward the reward: claims that went on to sign up.
claimedCount is the larger number of installs that merely entered the code.
Ask the backend which referral this install probably came from. Nothing is bound
by this - the returned code is meant to be offered back to the user to confirm or
replace, and redeemCode is what actually claims it:
val detection = WandKit.detectReferral()
val prefill = detection?.codeFingerprint accuracy decays quickly and the server-side match window is short, so
detection has to run early - long before the user has agreed to anything. Call this
right after configure:
WandKit.detectReferralOnFirstLaunchIfNeeded()It runs once per install, in the background, and persists the result. Read it back whenever your UI is ready:
val detection = WandKit.detectedReferralA transient failure does not count as an attempt, so the next launch retries - a dropped attempt costs an inviter a referral they earned. Retries are capped, and a permanent failure (a rejected key, an unreadable response) gives up at once, so an install that can never get an answer stops fingerprinting rather than re-sending on every launch.
redeemCode clears the detection on success, since the question it exists to
answer has been answered. If the user dismisses the prefilled code instead, clear
it yourself:
WandKit.clearDetectedReferral()Redeem a code, whether the user typed it or confirmed a detected one:
val match = WandKit.redeemCode(code = "INVITE_CODE")WandKit.redeemCode(...) returns ReferralMatch?. This is the only call that
creates a claim.
WandKit.installId is this device's install ID, the same one redeemCode claims
with, and it is stable across launches. Forward it to your own backend so it can
report referral conversions server-to-server:
myBackend.reportReferralAttribution(wandkitInstallId = WandKit.installId)You can also ask the SDK to read the install referral code from the platform provider and redeem it:
val match = WandKit.matchReferral()WandKit.matchReferral() returns ReferralMatch?.
Note that this predates detectReferral() and claims the referral immediately,
without asking the user. Prefer detection unless you specifically want the old
auto-claim behaviour.
Important: if you want to use the install referral code provider, you must pass context when calling WandKit.configure(...) on Android.
Without context, the SDK cannot create the Android install referrer client, so WandKit.getInstallReferralCode() and WandKit.matchReferral() will not be able to read the install referral code.
The SDK exposes the raw install referral code lookup as well:
val installReferralCode = WandKit.getInstallReferralCode()Required Android setup:
WandKit.configure(
config = WandKitConfig(
apiKey = "your_api_key",
isDebugLoggingEnabled = true,
),
context = applicationContext,
)On Android, this uses the Play Install Referrer API and extracts the referral_code query parameter from the install referrer payload.
WandKit.matchReferral() uses this provider internally. If a referral code is available, it redeems that code automatically.
On iOS, the install referral code provider is currently not implemented and always returns null. Because of that:
WandKit.getInstallReferralCode() returns null on iOSWandKit.matchReferral() also returns null on iOS unless install referral support is implemented there laterForms are event-driven.
When you call WandKit.event(...), the backend may return a form for that event. If your app has the Compose host mounted, the SDK will present that form automatically.
There is no separate public API for manually opening a form.
To render forms, add WandKitHost() to your Compose UI tree.
@Composable
fun App() {
MaterialTheme {
Box {
MainContent()
WandKitHost()
}
}
}WandKitHost() should be mounted at the root of the composable container where forms can appear.
If that container uses ModalBottomSheetLayout, place the host at that root level so the SDK can present forms correctly inside the same container.
In practice, do not place it deep inside a screen subtree that may not be present when an event returns a form.
You can provide a custom theme to WandKitHost():
WandKitHost(
theme = WandKitThemeDefaults.system(),
)Available defaults:
WandKitThemeDefaults.light()WandKitThemeDefaults.dark()WandKitThemeDefaults.system()You can also construct a custom WandKitTheme with your own colors and typography.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WandKit.configure(
config = WandKitConfig(
apiKey = "your_api_key",
isDebugLoggingEnabled = true,
),
context = applicationContext,
)
setContent {
MaterialTheme {
Box {
ScreenContent(
onAction = {
WandKit.identify("user_123")
WandKit.event(name = "screen_action_tapped")
}
)
WandKitHost()
}
}
}
}
}