---
name: unibus_android
domain: tools
version: 0.1.0
description: "Cliente Android (Jetpack Compose) del bus unibus: conecta, crea salas y chatea con el mismo cliente Go (pkg/client) compilado a un .aar via gomobile, conservando el cifrado E2E."
tags: [messaging, nats, e2e, android, compose, kotlin, gomobile, client]
lang: kt
framework: compose
entry_point: "app/src/main/kotlin/com/fnregistry/unibus/MainActivity.kt"
dir_path: "projects/message_bus/apps/unibus_android"
repo_url: "https://gitea.organic-machine.com/dataforge/unibus_android"
uses_functions:
  - fn_theme_kt_ui
  - fn_tokens_kt_ui
  - fn_stack_kt_ui
  - fn_group_kt_ui
  - fn_card_kt_ui
  - fn_badge_kt_ui
  - fn_button_kt_ui
  - fn_text_kt_ui
  - fn_title_kt_ui
  - fn_text_input_kt_ui
  - fn_switch_kt_ui
uses_types: []
e2e_checks:
  - id: bind
    cmd: "cd ../unibus && gomobile bind -target=android -androidapi 24 -o ../unibus_android/app/libs/unibus.aar github.com/enmanuel/unibus/mobile"
    timeout_s: 600
  - id: build
    cmd: "fn run gradle_assemble_debug_bash_infra projects/message_bus/apps/unibus_android"
    timeout_s: 360
---

# unibus_android

Cliente Android del bus de mensajería `unibus`. La aplicación es una fina capa de
interfaz en Jetpack Compose sobre el cliente Go real del bus: `pkg/client` se compila
a una librería nativa Android (`unibus.aar`) con `gomobile bind`, de modo que el teléfono
habla el protocolo NATS y ejecuta el mismo cifrado end-to-end (ChaCha20-Poly1305 +
Ed25519 + X25519) que cualquier otro peer. No se reimplementa ni el protocolo ni la
criptografía: hay una única fuente de verdad compartida con el resto del ecosistema.

## Arquitectura

```
MainActivity.kt (Compose UI)
   └── mobile.Session  (Kotlin, generado por gomobile)
         └── unibus.aar  (libgojni.so: pkg/client + nats.go + crypto del registry)
               ├── data plane  → NATS TCP        (nats://host:4250)
               └── control plane → HTTP REST      (http://host:8470)
```

El wrapper Go que define la API plana del binding vive en
`projects/message_bus/apps/unibus/mobile/unibus.go` (paquete `mobile`): expone
`NewSession`, `CreateRoom`, `Join`, `Publish`, `Subscribe`, `Request` y la interfaz
`FrameListener` con tipos compatibles con gomobile (string, []byte, int, error,
interface).

## Regenerar el binding (.aar)

Tras tocar `pkg/client` o el wrapper `mobile/`:

```bash
cd projects/message_bus/apps/unibus
export ANDROID_HOME=$HOME/android-sdk
export ANDROID_NDK_HOME=$(ls -d $HOME/android-sdk/ndk/* | head -1)
export PATH="$PATH:$(go env GOPATH)/bin"
gomobile bind -target=android -androidapi 24 \
  -o ../unibus_android/app/libs/unibus.aar \
  github.com/enmanuel/unibus/mobile
```

## Build del APK

```bash
fn run gradle_assemble_debug_bash_infra projects/message_bus/apps/unibus_android
# o, directo:
cd projects/message_bus/apps/unibus_android && ./gradlew :app:assembleDebug
# salida: app/build/outputs/apk/debug/app-debug.apk
```

## Probarlo contra el bus

1. Levantar el control plane real (NATS embebido + REST de membresía):
   ```bash
   cd projects/message_bus/apps/unibus
   go run ./cmd/membershipd            # HTTP :8470, NATS :4250
   ```
2. En el **emulador** (`Pixel_API34`), instalar el APK. La app usa `host = 10.0.2.2`,
   que el emulador mapea al loopback del PC, así que conecta sin más configuración.
3. En la app: Conectar → Crear sala → escribir un mensaje. El mensaje viaja al bus por
   NATS y vuelve al propio suscriptor (eco del subject), confirmando el camino completo
   APK → bus → APK.

## Gotchas

- `cmd/membershipd` escucha en `127.0.0.1` (líneas `addr := "127.0.0.1:" + httpPort`).
  Desde el emulador funciona (10.0.2.2 → loopback del host). Para un **teléfono real** en
  la LAN o vía Tailscale hay que bindear el control plane y el NATS embebido a `0.0.0.0`
  y usar la IP de red del PC/VPS en los campos Host de la app.
- El control plane v1 es HTTP sin TLS; el manifest declara `usesCleartextTraffic="true"`.
  Para producción, exponer el bus con TLS y quitar ese flag.
- El `.aar` pesa ~24 MB (runtime Go embebido) y el APK debug ~54 MB. Es esperado: el
  cliente Go completo viaja dentro.
- `onFrame` del `FrameListener` llega en una goroutine de entrega de NATS; la UI hace
  `Handler(mainLooper).post { ... }` antes de tocar el estado de Compose.

## Capability growth log

- v0.1.0 (2026-06-05) — baseline: conectar, crear sala (modo nats/matrix), publicar y
  recibir mensajes en vivo. Binding gomobile del `pkg/client` con cifrado E2E intacto.
