Thin Jetpack Compose UI over the real Go client (pkg/client) compiled to a
gomobile .aar, so the phone speaks NATS and runs the same end-to-end crypto as
any other peer. Connect, create room (nats/matrix), publish and live-receive.
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.
fn run gradle_assemble_debug_bash_infra projects/message_bus/apps/unibus_android
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/:
cd projects/message_bus/apps/unibus
exportANDROID_HOME=$HOME/android-sdk
exportANDROID_NDK_HOME=$(ls -d $HOME/android-sdk/ndk/* | head -1)exportPATH="$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
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
Levantar el control plane real (NATS embebido + REST de membresía):
cd projects/message_bus/apps/unibus
go run ./cmd/membershipd # HTTP :8470, NATS :4250
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.
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.