# Kotlin SDK

`com.messagebird:bird-realtime` is the official Kotlin client for [Bird Realtime](/docs/guides/realtime/overview): it holds a WebSocket to the Bird edge, subscribes to channels, and delivers events as they are published. It is hand-written rather than generated, because Realtime is a WebSocket data plane rather than a REST surface, and it speaks the same wire dialect as the browser and Swift clients.

It is a plain JVM library, so the same artifact serves an Android app and a server. Callbacks land on the Android main looper when one exists and run inline otherwise, so you do not need a separate build for each.

This client is the **receiving** side. Publishing events, authorizing channels, and terminating connections are server-side operations on the Bird API; see [publishing events](/docs/guides/realtime/publishing-events).

## Install

```kotlin
dependencies {
    implementation("com.messagebird:bird-realtime:0.1.0")
}
```

On Android, add the internet permission to your manifest. A library no longer contributes one:

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

## Construct a client

```kotlin
import com.messagebird.realtime.BirdRealtime
import com.messagebird.realtime.BirdRealtimeOptions

val bird = BirdRealtime(
    BirdRealtimeOptions(
        appKey = "your-app-key",
        region = "us1",
    )
)
bird.connect()
```

Only `appKey` is required, plus either `region` (`us1` or `eu1`, which resolves the edge host) or an explicit `wsHost`. The app key is public and ships in client code: it identifies the app, it does not authorize anything.

## Subscribe to a channel

```kotlin
val orders = bird.subscribe("orders")
orders.bind("order-updated") { data ->
    println("order changed: $data")
}
```

Payloads arrive as `kotlinx.serialization.json.JsonElement`, so read them with the serialization API rather than casting.

A channel whose name has no `private-` or `presence-` prefix is [public](/docs/guides/realtime/public-channels): any client with the app key can subscribe, so treat its contents as readable by anyone who can open your app.

## Private and presence channels

[Private](/docs/guides/realtime/private-channels) and [presence](/docs/guides/realtime/presence-channels) channels are authorized by your backend, which holds the app secret. Point the client at your endpoint: it POSTs `{"connection_id", "channel_name"}` and expects `{"auth", "member_data"?}` back.

```kotlin
val bird = BirdRealtime(
    BirdRealtimeOptions(
        appKey = "your-app-key",
        region = "us1",
        authEndpoint = "https://your-backend.example.com/bird/auth",
        authHeaders = mapOf("authorization" to "Bearer <session token>"),
    )
)

val room = bird.subscribe("presence-room-42")
if (room is PresenceChannel) {
    room.bind(BirdProtocol.Event.SUBSCRIPTION_SUCCEEDED) {
        println("me: ${room.myId}, members: ${room.members.keys}")
    }
    room.bind(BirdProtocol.Event.MEMBER_ADDED) { member -> println("joined: $member") }
}
```

Pass an `authorizer` instead to sign through your own networking stack.

A rejection from the server (a bad signature, an app over capacity) arrives on the connection rather than the channel, because the wire carries no channel attribution. Observe those with `onError`. A failure inside your authorizer does emit `bird:subscription_error` on the channel.

## Sign in a member

`signin()` tells the edge which member this connection belongs to. That identity is what lets the API [address a member](/docs/guides/realtime/sending-events-to-a-member) or [terminate their connections](/docs/guides/realtime/terminating-member-connections), and it is what satisfies an app configured to require [authorized connections](/docs/guides/realtime/authorized-connections).

```kotlin
val bird = BirdRealtime(
    BirdRealtimeOptions(
        appKey = "your-app-key",
        region = "us1",
        memberAuthEndpoint = "https://your-backend.example.com/bird/auth/member",
    )
)

val me = bird.signin() // suspending
println("signed in as ${me.memberId}")

bird.member.bind("order.shipped") { data ->
    println("addressed to me: $data")
}
```

Call `signin()` once. The identity lives on the connection, so the client signs in again automatically after a reconnect; a failed re-signin has nothing to throw to and surfaces on `onSigninError` instead, which keeps a failing member endpoint from disturbing channel subscriptions.

## Connection lifecycle

```kotlin
bird.onConnectionStateChange { previous, current ->
    println("connection: $previous -> $current")
}
bird.onError { error ->
    if (error.code == 4009) println("session ended elsewhere")
}
```

Reconnection is automatic, with full-jitter exponential backoff from 1s to a 30s cap. Close codes in 4000-4099 are refusals and terminal: the client stops and reports the code, because a refusal is a decision rather than a transient failure. Codes in 4200-4299 reconnect immediately; anything else backs off. Channels re-subscribe with fresh authorization on every reconnect. See [connection lifecycle](/docs/guides/realtime/connection-lifecycle).

Transport is TLS for every non-loopback host. `allowInsecure` permits `ws://` only for `localhost`, `127.0.0.1` and `[::1]`, so a config copied from a development target cannot silently downgrade production traffic.

## Client events

With the app's client-events setting enabled, a client subscribed to a private or presence channel can publish to its peers directly:

```kotlin
room.trigger("client-typing", buildJsonObject { put("on", true) })
```

See [client events](/docs/guides/realtime/client-events).

## Source

[github.com/messagebird/bird-sdk-kotlin](https://github.com/messagebird/bird-sdk-kotlin), MIT licensed. Report issues there.