Native Android SDK

v0.1.0Kotlin

Overview

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.
Note:The SDK is published in the monorepo at 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):

settings.gradle.kts
1dependencyResolutionManagement {
2 repositories {
3 google()
4 mavenCentral()
5 maven { url = uri("https://jitpack.io") } // ← Required for the Keme SDK
6 }
7}

Then add the dependency to your app module:

app/build.gradle.kts
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:

bash
1./gradlew :keme-sdk:assembleRelease
2# → 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:

app/build.gradle.kts
1dependencies {
2 implementation(files("libs/keme-sdk-release.aar"))
3
4 // 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}
Note:A future Maven Central release will publish the coordinate 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.

kotlin
1val keme = Keme.init(context, KemeConfig(gameId = "your-game-id"))
2
3lifecycleScope.launch {
4 keme.login(userId = player.id, username = player.name) // suspend, auto-provisions
5 keme.openSupport(context) // launches KemeSupportActivity
6}
Tip: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

SupportButton.kt
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 header
7 ),
8 )
9
10 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(...)).

FieldTypeDefaultNotes
gameIdString— (required)Game/app tickets are filed against.
apiBaseStringhttps://api.kemegames.com/api/v1Override for staging/self-host.
appNameString"Support"Header title in the drop-in screen.
storageTokenStorage?SharedPreferencesOverride 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.

kotlin
1lifecycleScope.launch {
2 val player = keme.login(LoginParams(userId = "player-123", username = "Ari"))
3
4 val signedIn = keme.isLoggedIn() // sync — whether a token is stored
5 val me = keme.getPlayer() // suspend — the current authenticated player
6
7 keme.logout() // sync — clears the persisted token
8}
Warning:A banned player's login throws 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.

kotlin
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.

kotlin
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 )
10
11 val mine = keme.tickets.list() // the player's tickets
12 val detail = keme.tickets.get(ticket.id) // includes messages
13 keme.tickets.reply(ticket.id, "Any update?") // body 1–5000
14 keme.tickets.rate(ticket.id, score = 5, comment = "Fast fix, thanks!") // 1–5
15}

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.

kotlin
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 body
6}

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.
kotlin
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.
Tip:The token (7-day JWT) is stored in SharedPreferences under keme.portal.token by default. Implement TokenStorage and pass it in KemeConfig(storage = ...) to use your own store.
Last updated: July 18, 2026Edit this page