# Use SupaHost with Android and Kotlin

> Connect an Android app to a SupaHost project with supabase-kt. Gradle dependencies, keys through BuildConfig, a client with Auth and Postgrest, a Compose list, and sign-in with an emailed code.

Source: https://supahost.app/docs/getting-started/android-kotlin · Updated 2026-11-11

`supabase-kt` is the Kotlin client for Supabase, used on Android and in Kotlin Multiplatform. It's built on Ktor and kotlinx.serialization, and installs features as plugins. Give it your SupaHost project URL and `anon` key and it works unchanged.

The examples use Jetpack Compose and the `recipes` table from the [quickstart](/docs/getting-started/quickstart).

## Add the dependencies

In your app module's `build.gradle.kts`, add the serialization plugin, the supabase-kt BOM and the modules you need, plus a Ktor engine:

```kotlin
plugins {
    // ...
    kotlin("plugin.serialization") version "<your Kotlin version>"
}

dependencies {
    implementation(platform("io.github.jan-tennert.supabase:bom:<supabase-kt version>"))
    implementation("io.github.jan-tennert.supabase:postgrest-kt")
    implementation("io.github.jan-tennert.supabase:auth-kt")
    implementation("io.ktor:ktor-client-android:<ktor version>")
}
```

Take the current versions from the supabase-kt releases page on GitHub; each release lists the Kotlin and Ktor versions it needs. Add the internet permission to `AndroidManifest.xml`:

```xml
<uses-permission android:name="android.permission.INTERNET" />
```

## Keep keys out of source

Add the Project URL and `anon` key from the **Connect** tab (subtab **App**) to `local.properties`, which Android Studio already ignores in git:

```properties
SUPABASE_URL=https://app-one.acme.supahost.space
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIs...
```

Expose them through `BuildConfig`:

```kotlin
// app/build.gradle.kts
import java.util.Properties

val localProps = Properties().apply {
    rootProject.file("local.properties").inputStream().use { load(it) }
}

android {
    buildFeatures { buildConfig = true }
    defaultConfig {
        buildConfigField("String", "SUPABASE_URL", "\"${localProps["SUPABASE_URL"]}\"")
        buildConfigField("String", "SUPABASE_ANON_KEY", "\"${localProps["SUPABASE_ANON_KEY"]}\"")
    }
}
```

The `anon` key is fine inside an APK. The `service_role` key never is.

## Create the client

```kotlin
// SupabaseClient.kt
import io.github.jan.supabase.auth.Auth
import io.github.jan.supabase.createSupabaseClient
import io.github.jan.supabase.postgrest.Postgrest

val supabase = createSupabaseClient(
    supabaseUrl = BuildConfig.SUPABASE_URL,
    supabaseKey = BuildConfig.SUPABASE_ANON_KEY,
) {
    install(Auth)
    install(Postgrest)
}
```

Create it once and reuse it. The Auth plugin saves the session on the device and refreshes it.

## Query a table

```kotlin
import io.github.jan.supabase.postgrest.from
import io.github.jan.supabase.postgrest.query.Columns
import io.github.jan.supabase.postgrest.query.Order
import kotlinx.serialization.Serializable

@Serializable
data class Recipe(val id: Long, val title: String, val minutes: Int? = null)

suspend fun loadRecipes(): List<Recipe> =
    supabase.from("recipes")
        .select(Columns.list("id", "title", "minutes")) {
            order("created_at", Order.DESCENDING)
        }
        .decodeList<Recipe>()
```

Show them in Compose:

```kotlin
@Composable
fun RecipeList() {
    var recipes by remember { mutableStateOf<List<Recipe>>(emptyList()) }
    var error by remember { mutableStateOf<String?>(null) }

    LaunchedEffect(Unit) {
        runCatching { loadRecipes() }
            .onSuccess { recipes = it }
            .onFailure { error = it.message }
    }

    error?.let { Text("Error: $it") }
    LazyColumn {
        items(recipes, key = { it.id }) { r ->
            Text("${r.title} (${r.minutes ?: "?"} min)", modifier = Modifier.padding(12.dp))
        }
    }
}
```

Inserting takes any serializable value:

```kotlin
@Serializable
data class NewRecipe(val title: String, val minutes: Int?)

supabase.from("recipes").insert(NewRecipe("Lemon rice", 30))
```

The insert only succeeds for a signed-in user, because the quickstart's insert policy is for `authenticated`.

## Sign in

Email and password:

```kotlin
import io.github.jan.supabase.auth.auth
import io.github.jan.supabase.auth.providers.builtin.Email

supabase.auth.signUpWith(Email) {
    email = "cook@example.com"
    password = "a-long-password"
}
supabase.auth.signInWith(Email) {
    email = "cook@example.com"
    password = "a-long-password"
}
supabase.auth.signOut()
```

An emailed one-time code is the easiest passwordless option on Android, because a typed code needs no redirect. (If you prefer links, add your app's scheme, such as `recipebox://login`, to **Redirect URLs** on the Auth tab and handle the deep link.)

```kotlin
import io.github.jan.supabase.auth.OtpType
import io.github.jan.supabase.auth.providers.builtin.OTP

supabase.auth.signInWith(OTP) { email = "cook@example.com" }
// Later, with the code from the email:
supabase.auth.verifyEmailOtp(type = OtpType.Email.EMAIL, email = "cook@example.com", token = code)
```

## Watch the session

```kotlin
import io.github.jan.supabase.auth.status.SessionStatus

LaunchedEffect(Unit) {
    supabase.auth.sessionStatus.collect { status ->
        signedIn = status is SessionStatus.Authenticated
    }
}
```

## Google sign-in

Use Android's Credential Manager to get a Google ID token, then sign in with it. supabase-kt's `compose-auth` module can do both steps for you. With a token in hand:

```kotlin
import io.github.jan.supabase.auth.providers.Google
import io.github.jan.supabase.auth.providers.builtin.IDToken

supabase.auth.signInWith(IDToken) {
    idToken = googleIdToken
    provider = Google
}
```

Enable Google on the project's **Auth** tab first; see [Sign in with Google](/docs/auth/google). Browser-based OAuth needs an `https://` App Link as the return address, listed under **Redirect URLs**.

## Next steps

- [Magic links and email OTP](/docs/auth/magic-links-otp)
- [Uploading files](/docs/storage/uploads) (add the `storage-kt` module)
- [Realtime on SupaHost](/docs/realtime/overview) (add `realtime-kt`)
- [Can I use SupaHost for a mobile app?](/kb/mobile-app-database)
