Native Android SDK
v0.1.0KotlinOverview
The official Keme Android SDK gives your game a fully native in-app support experience — drop-in tickets and knowledge base, packaged as an .aar library. It is written in idiomatic Kotlin with coroutines and wraps the Keme portal REST API, handling token persistence, the {success,data} response envelope, list normalisation, and typed errors — so you call login() and openSupport() and you're done.
- minSdk 21 (Android 5.0+).
- Idiomatic Kotlin with coroutines — network calls are suspend functions.
- Drop-in KemeSupportActivity support screen — no UI to build.
- The SDK declares INTERNET and registers KemeSupportActivity in its own manifest — merged into your app automatically.
- OkHttp + kotlinx.serialization under the hood. No hidden dependencies.
github.com/kemegames-studio/keme-sdk. Current version v0.1.0.Installation
Option A — JitPack (recommended)
Add the JitPack repository to your settings.gradle(.kts):
1dependencyResolutionManagement {2 repositories {3 google()4 mavenCentral()5 maven { url = uri("https://jitpack.io") } // ← Required for the Keme SDK6 }7}
Then add the dependency to your app module:
1dependencies {2 implementation("com.github.kemegames-studio.keme-sdk:keme-sdk:v0.1.0")3}
After adding the dependency, sync your project with Gradle files (File → Sync Project with Gradle Files in Android Studio).
Option B — Local .aar
Build the artifact from the repo:
1./gradlew :keme-sdk:assembleRelease2# → keme-sdk/build/outputs/aar/keme-sdk-release.aar
Drop it into your app's libs/ folder and reference it. The .aar does not bundle its transitive dependencies, so declare them yourself:
1dependencies {2 implementation(files("libs/keme-sdk-release.aar"))34 // The .aar does NOT bundle its transitive deps — declare them yourself:5 implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")6 implementation("com.squareup.okhttp3:okhttp:4.12.0")7 implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")8 implementation("com.google.android.material:material:1.12.0")9 implementation("androidx.appcompat:appcompat:1.7.0")10 implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.4")11}
com.keme:sdk, at which point these transitive dependencies are resolved automatically and you won't need to list them.Quickstart
Three lines get you a working support surface. init creates the client, login authenticates the player (auto-provisioning them on first login), and openSupport launches the drop-in screen.
1val keme = Keme.init(context, KemeConfig(gameId = "your-game-id"))23lifecycleScope.launch {4 keme.login(userId = player.id, username = player.name) // suspend, auto-provisions5 keme.openSupport(context) // launches KemeSupportActivity6}
login (and every other network call) is a suspend function — call it from a coroutine, e.g. inside lifecycleScope.launch { … }. openSupport is synchronous.Full flow with a support button
1class SupportButton(private val activity: AppCompatActivity) {2 private val keme = Keme.init(3 activity,4 KemeConfig(5 gameId = "your-game-id",6 appName = "Marble Sort Support", // shown in the support screen header7 ),8 )910 fun onTap() = activity.lifecycleScope.launch {11 if (!keme.isLoggedIn()) {12 keme.login(LoginParams(userId = currentPlayerId, username = playerName))13 }14 keme.openSupport(activity)15 }16}
Configuration
Create the client with Keme.init(context, KemeConfig(...)).
| Field | Type | Default | Notes |
|---|---|---|---|
| gameId | String | — (required) | Game/app tickets are filed against. |
| apiBase | String | https://api.kemegames.com/api/v1 | Override for staging/self-host. |
| appName | String | "Support" | Header title in the drop-in screen. |
| storage | TokenStorage? | SharedPreferences | Override token persistence. |
Authentication
Call login with the player's userId (and optional username / email). The first login for a given userId auto-provisions the player — there is no separate registration step — and the returned 7-day bearer token is stored automatically.
1lifecycleScope.launch {2 val player = keme.login(LoginParams(userId = "player-123", username = "Ari"))34 val signedIn = keme.isLoggedIn() // sync — whether a token is stored5 val me = keme.getPlayer() // suspend — the current authenticated player67 keme.logout() // sync — clears the persisted token8}
KemeException with status = 403. Optional username / email on LoginParams are only used the first time a player is provisioned; later logins ignore them.Opening the Support Screen
keme.openSupport(context) launches KemeSupportActivity, the drop-in support screen — ticket list, ticket detail with reply, and a new-ticket form. The activity is registered in the SDK's own manifest, so there is no additional setup. The player must be logged in first, since the screen calls authenticated endpoints.
1// Launch the drop-in support screen (player must be logged in first)2keme.openSupport(context)
Tickets
Drive tickets directly through keme.tickets without the drop-in UI. Subjects are 5–200 characters and descriptions are 10–5000. All methods are suspend functions.
1lifecycleScope.launch {2 val ticket = keme.tickets.create(3 CreateTicketParams(4 subject = "Lost my gems after the update",5 description = "I had 500 gems before v1.4 and now they're gone.",6 category = TicketCategory.BILLING,7 priority = TicketPriority.P2,8 ),9 )1011 val mine = keme.tickets.list() // the player's tickets12 val detail = keme.tickets.get(ticket.id) // includes messages13 keme.tickets.reply(ticket.id, "Any update?") // body 1–500014 keme.tickets.rate(ticket.id, score = 5, comment = "Fast fix, thanks!") // 1–515}
Categories & priorities
- TicketCategory: BUG, BILLING, ACCOUNT, GAMEPLAY, OTHER.
- TicketPriority: P1, P2, P3, P4.
- Ticket exposes categoryEnum / priorityEnum conveniences; the raw wire strings remain available so unknown future values never crash deserialization.
Knowledge Base
Surface self-serve help content through keme.kb.
1lifecycleScope.launch {2 val results = keme.kb.search("gems")3 val categories = keme.kb.categories()4 val featured = keme.kb.featured()5 val article = keme.kb.article("how-to-restore-purchases") // includes body6}
Errors
Every non-2xx response, network failure, or malformed body throws KemeException(message, status, body):
- status — HTTP code, or 0 for a network/transport error.
- body — the raw response body (usually JSON), when present.
- message — parsed from the API message field (handles both string and string[] shapes) or a network error description.
1try {2 keme.tickets.create(params)3} catch (e: KemeException) {4 Log.w("Keme", "failed (${e.status}): ${e.message}")5}
gameId vs userId
These are two different identities and are easy to confuse:
- gameId identifies the game/app (e.g. game-marble-sort). Set it once in KemeConfig at init; it is attached to every ticket you create. Fetch valid ids with keme.games().
- userId identifies the player uniquely within your game. Pass it to login(LoginParams(userId = ...)). On the wire it maps to the portal gameUid field. The first login auto-provisions the player.
SharedPreferences under keme.portal.token by default. Implement TokenStorage and pass it in KemeConfig(storage = ...) to use your own store.