Amba

Android Kotlin SDK

Kotlin/Android quickstart for com.layers.amba:amba-sdk-android — Maven Central install, Amba.configure(Application), tokens via EncryptedSharedPreferences, Google sign-in, FCM push.

Amba for Android is published to Maven Central as com.layers.amba:amba-sdk-android. It supports every standard Android ABI and uses EncryptedSharedPreferences for credential storage.

Requirements: Android API 24+ (Android 7.0), Kotlin 1.9+, Java 17 toolchain.

1. Install via Gradle

Make sure mavenCentral() is in your project repositories:

// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
    }
}

Then add the dependency to your app module:

// app/build.gradle.kts
dependencies {
    implementation("com.layers.amba:amba-sdk-android:1.0.0")
    implementation("androidx.security:security-crypto:1.1.0-alpha06") // for EncryptedSharedPreferences-backed storage
    implementation("com.google.android.gms:play-services-auth:21.2.0") // for Google sign-in
}
 
android {
    defaultConfig {
        minSdk = 24
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    kotlinOptions { jvmTarget = "17" }
}

2. Configure in Application.onCreate

// MyApplication.kt
package com.example.myapp
 
import android.app.Application
import com.layers.amba.Amba
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
 
class MyApplication : Application() {
    val applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
 
    override fun onCreate() {
        super.onCreate()
 
        // BuildConfig field added via the Gradle snippet below — never ship
        // a hard-coded API key in source. Use a local properties file +
        // BuildConfig.AMBA_API_KEY, an env var, or your secrets framework.
        Amba.configure(apiKey = BuildConfig.AMBA_API_KEY)
    }
}

Register the Application class in AndroidManifest.xml:

<application
    android:name=".MyApplication"
    ...>

Inject the API key via BuildConfig

// app/build.gradle.kts
import java.util.Properties
 
val localProps = Properties().apply {
    rootProject.file("local.properties").takeIf { it.exists() }?.inputStream()?.use(::load)
}
 
android {
    defaultConfig {
        buildConfigField(
            "String",
            "AMBA_API_KEY",
            "\"${localProps.getProperty("AMBA_API_KEY") ?: "amb_dev_ck_XXXX"}\"",
        )
    }
    buildFeatures { buildConfig = true }
}

Then add AMBA_API_KEY=amb_dev_ck_XXXX to your local.properties (gitignored). Replace with your real key from amba init or app.amba.dev.

3. First sign-in + first event

Amba.events.track is authenticated server-side — the request needs a session token. Sign in (anonymously is fine) before the first track call:

// Inside any Activity / ViewModel
import com.layers.amba.Amba
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
 
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent { /* compose UI */ }
 
        lifecycleScope.launch {
            // Sign-in first — minted anonymously on cold start, restored from
            // EncryptedSharedPreferences on subsequent launches.
            val session = Amba.auth.signInAnonymously()
            Log.d("amba", "signed in as ${session.user.id}")
 
            // Track now happens with a valid session token.
            Amba.events.track("app_opened", mapOf("source" to "icon"))
        }
    }
}

All Amba.* methods are suspend — call from a coroutine scope (lifecycleScope, viewModelScope, or your own CoroutineScope).

4. Session storage via EncryptedSharedPreferences

By default the SDK persists session tokens in EncryptedSharedPreferences (backed by the Android Keystore). No additional code is needed — the storage adapter is wired automatically when the androidx.security:security-crypto dependency is on the classpath.

Sessions survive app restarts, package upgrades, and reboots. They're cleared on full app reinstall (adb uninstall) or when the user explicitly signs out.

If you need a custom store (e.g. an in-memory store for tests, or a Room-backed adapter), construct AmbaClient directly:

import com.layers.amba.AmbaClient
 
val client = AmbaClient(
    apiKey = BuildConfig.AMBA_API_KEY,
)
// In tests, you can instead provide a fake AmbaCoreFfiInterface via the
// internal constructor.

5. Sign in with Google

import androidx.activity.result.ActivityResultLauncher
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.layers.amba.Amba
import kotlinx.coroutines.launch
 
class SignInActivity : ComponentActivity() {
    private lateinit var googleSignInLauncher: ActivityResultLauncher<Intent>
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestIdToken("YOUR_GOOGLE_OAUTH_WEB_CLIENT_ID")
            .requestEmail()
            .build()
        val googleClient = GoogleSignIn.getClient(this, gso)
 
        googleSignInLauncher = registerForActivityResult(
            ActivityResultContracts.StartActivityForResult()
        ) { result ->
            val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
            val account = task.result
            val idToken = account.idToken ?: return@registerForActivityResult
            lifecycleScope.launch {
                Amba.auth.signInWithGoogle(idToken)
            }
        }
 
        // Trigger sign-in (typically from a button click):
        // googleSignInLauncher.launch(googleClient.signInIntent)
    }
}

Pull YOUR_GOOGLE_OAUTH_WEB_CLIENT_ID from Google's API credentials console — use the Web client ID, not the Android one.

6. Register for FCM push

import com.google.firebase.messaging.FirebaseMessaging
import com.layers.amba.Amba
import com.layers.amba.PushPlatform
import kotlinx.coroutines.launch
 
fun registerForFcm(scope: CoroutineScope) {
    FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
        if (!task.isSuccessful) return@addOnCompleteListener
        val token = task.result
        scope.launch {
            Amba.push.register(
                token = token,
                platform = PushPlatform.FCM,
                bundleId = "com.example.myapp",
            )
        }
    }
}

Add Firebase to your project the standard way (google-services.json in app/, the Google Services Gradle plugin) and configure FCM in the amba console (server key + sender ID) once per package name.

7. First collection insert

import com.layers.amba.Amba
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
 
@Serializable
data class Post(val id: String, val title: String, val body: String)
 
lifecycleScope.launch {
    val inserted = Amba.collections.insert(
        name = "posts",
        row = mapOf("title" to "Hello amba", "body" to "first post from Android"),
    )
 
    val response = Amba.collections.findTyped<Post>(
        name = "posts",
        options = com.layers.amba.FindOptions(
            order = listOf(
                com.layers.amba.OrderBy(
                    column = "created_at",
                    direction = com.layers.amba.OrderBy.Direction.DESC,
                ),
            ),
            limit = 20u,
        ),
    )
    Log.d("amba", "got ${response.data.size} posts")
}

The Kotlin SDK uses kotlinx.serialization for typed reads — findTyped<T>() decodes JSON straight into @Serializable data classes.

8. AI proxy

import com.layers.amba.Amba
import com.layers.amba.AiMessageRequest
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import kotlinx.coroutines.launch
 
lifecycleScope.launch {
    val response = Amba.ai.anthropic.messages.create(
        AiMessageRequest(
            promptSlug = "support_assistant",
            variables = buildJsonObject { put("user_query", "How do I cancel?") },
            maxTokens = 1024u,
        ),
    )
    Log.d("amba", response.toString())
}

ProGuard / R8

The SDK ships with consumer rules in the published AAR — no app-side ProGuard config is required. Generated classes and runtime dependencies are kept automatically.

If you see UnsatisfiedLinkError: amba_core at runtime after enabling R8, verify the .so files actually shipped into your APK:

unzip -l app/build/outputs/apk/release/app-release.apk | grep amba_core

Should list one entry per ABI you've enabled in splits / abiFilters. If empty, the dependency wasn't bundled — check that implementation("com.layers.amba:amba-sdk-android:1.0.0") is in the runtime classpath, not compileOnly.

Common pitfalls

  • Amba.configure not called before Amba.* throws IllegalStateException("amba SDK not configured"). Always configure in Application.onCreate, not in an Activity.
  • Calling SDK methods on the Main thread without a coroutine scope — every method is suspend. Use lifecycleScope.launch { ... } or viewModelScope.launch { ... }.
  • FCM token registered but no delivery — verify the Firebase Server Key in the amba console (Push → FCM) matches the project that issued the token. If you regenerated Firebase credentials, re-upload to amba.
  • Google sign-in DEVELOPER_ERROR (status code 10) — your debug keystore SHA-1 isn't registered on Google's OAuth client. Add keytool -list -v -keystore ~/.android/debug.keystore | grep SHA1 output to the Android OAuth client's authorized fingerprints.
  • App bundle size too large — by default the SDK includes all four ABIs (~7 MB raw). Use Android App Bundle (bundleRelease) so Play Store delivers only the ABI matching each device. Don't abiFilters down to a subset unless you know what you're doing — you'll lock out devices.

See also