# Integrating Relay into an existing Android app

This is the complete path for a team that already has an Android app and wants to add chat +
calling to it — not a new demo app. Everything you need is on this page; the platform's own
[`packages/android/README.md`](../packages/android/README.md) has the full API reference if you
need more than what's shown here.

For iOS or web, see [`integration-ios.md`](integration-ios.md) /
[`integration-web.md`](integration-web.md) instead. A shorter, cross-platform overview lives at
[`integration.md`](integration.md).

## The mental model

Relay is a separate service your existing backend talks to, plus a client library your Android
app embeds. It does **not** replace your login system, your user database, or your backend — it
has no idea who your users are until you tell it, on every request, via a short-lived token your
own backend mints. Three actors:

- **Your backend** — already exists, already authenticates your users. Gains one new endpoint:
  mint a Relay user token for whoever is signed in.
- **The Relay service** — one shared Communication Service, reachable at a URL you configure. Owns
  chat storage, presence, the realtime gateway, and brokers WebRTC calls. You don't write any of
  this.
- **`relay-core`/`relay-ui`/`relay-call`** — Kotlin/Compose modules your existing app depends on.
  Talk to the Relay service directly (not through your backend) for everything after the initial
  token exchange.

Nothing here requires a rewrite. `RelayChat` and `RelayCallOverlay` are ordinary composables you
place inside a screen you already have; if you already have your own chat UI, skip to Headless
below and keep it.

## Step 1 — Create a project and get your keys

Once, against the Relay service (ask whoever runs it for the URL, or run `service/` yourself — see
[`service/README.md`](../service/README.md)):

```bash
curl -s -X POST https://relay.example.com/projects \
  -H 'content-type: application/json' \
  -d '{"name":"My App"}'
# {
#   "publicKey": "pk_5f1e…",   ← ships inside your Android app, safe to embed
#   "secretKey": "sk_9c2a…"    ← backend only, shown exactly once — store it in your secrets manager now
# }
```

`publicKey` identifies your project to Relay and is fine baked into your app's APK — it grants
nothing by itself. `secretKey` mints user tokens for anyone in your project; it must never reach
client code, an APK, or git. If you lose it, rotate it (`POST /projects/me/keys`) rather than
trying to recover it — it's stored as a hash.

## Step 2 — Add a token endpoint to your existing backend

This is the one piece of backend work, and the only place Relay touches your existing auth. Add a
route your already-signed-in users can call:

```
POST /api/relay-token          (your backend, behind your existing session/auth middleware)
  → looks up the current user from YOUR session (however you already do that)
  → calls Relay:  POST /users/token  { "externalId": "<your user id>", "displayName": "...", "avatarUrl": "..." }
                  Authorization: Bearer sk_9c2a…
  → returns { "userToken": "...", "expiresAt": "..." } to your Android client
```

`externalId` is **your** user id — whatever you already use as a primary key. Relay never invents
its own identity; every `userId` your app sees on the wire (message senders, call participants,
presence) is that same id. Tokens expire in ~15 minutes; `relay-core` calls this endpoint again
automatically when one expires, so this route just needs to exist and be fast.
`sample-apps/web-demo/token-server.mjs` is a complete, 60-line reference implementation of this
exact endpoint (framework-agnostic — the shape is identical regardless of which client calls it).
Full details: [`service/README.md`](../service/README.md#how-a-host-backend-mints-tokens).

## Step 3 — Add the SDK to your app

**Install** — until the modules are published, add them as a composite build or
`includeBuild("path/to/relay-sdk/packages/android")`, then in your existing app module's
`build.gradle.kts`:

```kotlin
implementation(project(":relay-ui"))     // chat UI — pulls in relay-core
implementation(project(":relay-call"))   // add only if you want calling too
```

Requires minSdk 26, Kotlin 2.1, Compose BOM 2025.01.

**Initialize** — once per signed-in user, next to wherever your app already builds its other
network clients:

```kotlin
val relay = RelayClient(RelayConfig(
    baseUrl = "https://relay.example.com",
    publicKey = "pk_5f1e…",
    tokenProvider = { myExistingBackendClient.fetchRelayToken() },  // calls Step 2's endpoint
    packageId = BuildConfig.APPLICATION_ID,                        // optional, see Security below
))
```

Call `relay.connect()` where your app already handles "became foreground" / sign-in
(`onStart`/after login), and `relay.goToBackground()` / `relay.disconnect()` at the matching
background/sign-out points — the store resyncs by itself on every reconnect (the server never
replays missed events), so there's no missed-message bookkeeping to add yourself.

**Mount the UI** — inside whatever screen already hosts your chat/conversation list today:

```kotlin
setContent {
    MaterialTheme {                 // your app's existing theme — RelayChat inherits its colors/type
        RelayChat(client = relay)   // list → thread → composer, as one composable
    }
}
```

`RelayChat` reads the ambient `MaterialTheme.colorScheme`/`.typography` your app already installs,
so dropping it into an existing themed app already gets you most of the way to matching your
brand — see Theming (Step 6) for the parts that still need an explicit override.

**Headless**, if you already have chat UI and only want the data layer:

```kotlin
relay.connect()
relay.chat.loadConversations()
val convo = relay.chat.openConversation("user-42")          // your own user id
relay.chat.sendMessage(convo.id, "hello")
relay.chat.state.collect { snapshot -> /* your own UI reads conversations, threads, typing, presence */ }
```

## Step 4 — Calling (1:1 and group, one API)

Calling is a separate module (`relay-call`) so a chat-only integration doesn't pull in WebRTC.
One call-start API handles both 1:1 and group calls — the SDK looks at the conversation you pass
it and shows the right layout itself:

```kotlin
val calls = CallCenter(context, relay)                 // once per client
Box { RelayChat(relay); RelayCallOverlay(calls) }       // banner + full-screen UI, 1:1 or group automatically
// in a thread header:
CallButtons(calls, conversation)
// or trigger it yourself:
calls.start(conversation, CallType.VIDEO)
```

Group calls (up to 6 participants) render as a grid — 2 stacked, 3 as 2-over-1, up to 6 as three
rows of 2 — with per-tile name badges, mute indicators, tap-to-fullscreen, and a per-participant
"mute for me" control that only affects what you hear locally, without touching what anyone else
hears. Request `RECORD_AUDIO` (and `CAMERA` for video) before starting a call.

### Ringing when the app is fully closed (FCM)

Once your project has FCM credentials (set from the admin panel), the service sends a
high-priority FCM data message for incoming calls (and a cancel when the ring ends):

```kotlin
// Application.onCreate()
RelayCallHost.configure(this, MainActivity::class.java) { RelayClient(relayConfig) }
// use RelayCallHost.client() / RelayCallHost.callCenter() in your UI so state is shared

// your FirebaseMessagingService
override fun onMessageReceived(m: RemoteMessage) {
    if (RelayCallPush.onMessage(this, m.data)) return   // Relay handled a call push
    // ... your own notifications
}
override fun onNewToken(token: String) = RelayCallPush.onNewToken(token)
// after sign-in: RelayCallPush.registerCurrentToken(); on sign-out: RelayCallPush.unregisterCurrentToken()
```

`RelayCallService` rings, vibrates, shows a full-screen incoming-call notification with
Answer/Decline, and keeps the process alive for the length of the call. Ask for
`POST_NOTIFICATIONS` on API 33+. Chat messages arrive as ordinary FCM notification messages with
`data.relay = "chat_message"` and `conversationId` for deep-linking.

## Step 5 — Wire your own "add participants" picker

Group chats need a way to pick who to add. Relay does **not** ship a directory of your users — it
has no idea who they are beyond the ids that show up in tokens — so this is always a callback into
your own app. Two ways to hook it in:

**Your own picker UI entirely** (fastest to wire, if you already have a contacts/user list
screen) — return the ids yourself, in whatever UI you want:

```kotlin
MessageThread(client = relay, conversationId = id, onPickGroupMembers = { showMyOwnContactPicker() })
```

**Relay's built-in picker**, backed by your directory — you supply a search function, Relay
renders the search/list/selection UI itself:

```kotlin
MessageThread(client = relay, conversationId = id, onSearchPeople = { query -> myUserDirectory.search(query) })
```

`onSearchPeople` is called once with an empty query when the screen opens (a starting list) and
again as the user types; return `RelayUser`s (id, display name, avatar, online status) from
whatever directory your app already has. If you supply both, `onSearchPeople` wins; if you supply
neither, "Add people" is simply hidden.

## Step 6 — Make it look like your app

Every color, most icons, and text sizing are overridable, with defaults equal to Relay's own look
— so this step is optional, and doing nothing (or just wrapping in your existing `MaterialTheme`,
per Step 3) is a fully supported choice.

```kotlin
RelayTheme(
    // .copy() on the Material-derived default lets you override just the colors you care about
    colors = RelayColors.fromMaterialTheme(MaterialTheme.colorScheme).copy(
        accent = MyBrandBlue,
        danger = MyBrandRed,
    ),
    icons = RelayIcons(micOn = myMicIcon, callEnd = myHangUpIcon),
    typography = RelayTypography(fontFamily = myBrandFont, fontScale = 1.1f),
) {
    RelayChat(relay)
    RelayCallOverlay(calls)
}
```

This covers chat *and* call screens uniformly — the call screen used to be the one place still
hardcoded to Relay's own dark-navy look regardless of your app's theme; it now reads from the same
`colors`/`icons`/`typography` as the rest. `colors`, `icons`, and `typography` are each
independently optional — override one color without supplying the rest, and everything else keeps
Relay's default (which itself already derives from your app's ambient `MaterialTheme` if you don't
touch it at all).

## Attachments & link previews

`MessageComposer`'s attach button (Camera / Gallery / File) needs **no permission at all** for
Gallery or File — both are out-of-process pickers. **Camera is the one exception**: add
`<uses-permission android:name="android.permission.CAMERA"/>` to your own manifest if you want it;
`MessageComposer` requests it at runtime the first time it's tapped. A message with an `http(s)://`
link gets an automatic preview card — no setup needed, fetched server-side.

## Security checklist before you ship

- `secretKey` lives only on your backend (Step 2), never in client code or git.
- Pass `packageId` (`BuildConfig.APPLICATION_ID`) in `RelayConfig` if you've configured an Android
  package allowlist for your project in the admin panel; harmless to omit otherwise — the SDK
  sends it as `X-App-Package-Id` on every request.
- Decide who can message whom in **your own backend**, before minting a token or before your UI
  offers a conversation — Relay has no friend graph or block list of its own, so any two users in
  the same project can otherwise open a chat with each other.

## What you still own

Relay doesn't do: authentication (you already have this — it just needs a way to mint tokens for
whoever is signed in), a user directory or search (Step 5 is how your existing one plugs in), push
certificate/key management beyond what you configure once in the admin panel, or your app's icon —
that comes from your own app module automatically, there's nothing to override there.

## Where to go next

- Full API reference: [`packages/android/README.md`](../packages/android/README.md).
- Backend/token details and every security note: [`service/README.md`](../service/README.md).
- Push notification credentials (FCM) setup: [`push-credentials.md`](push-credentials.md).
- The reference integration is a real, running app: `packages/android/app-demo`.
