Header Logo

How to Add a Streaming Talking Avatar to an Android Travel Concierge App with Kotlin

How to Add a Streaming Talking Avatar to an Android Travel Concierge App with Kotlin

Add a streaming talking avatar to an Android travel concierge app with Kotlin using WebRTC, short-lived sessions, and backend auth.

Introduction


If you are building a travel concierge app, the UI problem is not just “how do I answer questions?” It is “how do I make the interaction feel like a real agent without forcing the user to read a chat transcript?” A streaming talking avatar solves that gap: the model speaks, the user hears it, and a synchronized face makes the experience feel present instead of purely transactional.


This post shows how to add that layer to an Android app written in Kotlin, with the practical constraints that matter in production: low latency, WebRTC media flow, lifecycle handling, and clean separation between your mobile client and avatar/session management.


By the end, you should know how to:


  • wire an Android app into a realtime voice + avatar flow,

  • stream audio to a voice agent and display the synchronized talking face,

  • avoid the common session, auth, and lifecycle mistakes, and

  • choose the right integration shape for a mobile concierge use case.


What “streaming talking avatar” means in practice


For mobile, the useful mental model is: your app captures microphone audio, sends it to a voice agent running somewhere else, receives synthesized speech back, and displays a live avatar video stream that stays aligned with the audio. The avatar is not a prerecorded clip. It is a realtime media source whose mouth movement and expression are driven by the same turn-taking loop as the spoken response.


That means your implementation needs to handle a few things well:


  • Realtime transport: use WebRTC or an equivalent low-latency media path, not polling.

  • Session lifecycle: create, join, resume, and clean up sessions explicitly.

  • Audio/video sync: do not treat the avatar as a separate UI animation; it is part of the media pipeline.

  • Mobile lifecycle: Android can background, pause, and recreate activities, so the media session cannot be tied too tightly to a single screen instance.


Architecture for an Android travel concierge


A sensible production architecture is:


  1. The Android app authenticates the user with your backend.

  2. Your backend requests or looks up a realtime avatar session from the avatar service.

  3. The app joins the media session and streams microphone input.

  4. The voice agent responds with audio, and the avatar video is rendered in a dedicated view.


For a travel concierge, this gives you a UI that can answer “What gate is my flight?” or “Can you rebook me to the next train?” while keeping the conversation visually anchored. The important part is that the mobile app should never directly carry long-lived API keys. Use your server as the trust boundary.


Android client: media session and UI integration


On Android, the avatar should usually live in its own composable or view container so you can manage it independently from the rest of the screen. For a Jetpack Compose app, think of the avatar as a streaming surface with a session controller behind it.


At a high level, you need:


  • mic permission handling,

  • a session token or join credential from your backend,

  • an SDK or WebRTC layer that can join the live media session, and

  • video rendering bound to the Activity or Compose lifecycle.


The exact client SDK surface depends on the avatar/session product you choose, but the integration pattern is usually the same: request credentials, connect, render, and tear down. Keep the network/session logic out of your UI layer so configuration changes do not force a reconnect.


A simplified Kotlin shape looks like this:


class ConciergeSessionManager(

}
class ConciergeSessionManager(

}
class ConciergeSessionManager(

}


This is intentionally abstract. The key point is not the class names; it is the ownership model. The screen owns presentation. A session manager or view model owns connection state. Your backend owns privileged API calls.


Managing travel-specific conversational flow


Travel apps benefit from a tighter interaction model than generic chat. Users are usually asking for concrete actions: itinerary lookup, delay status, baggage policy, lounge access, rebooking, or local recommendations. That means the voice agent should be optimized for short turns and explicit confirmations.


Three implementation details matter:


  1. Interruptibility: the user should be able to interrupt the agent mid-sentence when correcting a gate number or booking reference.

  2. Context injection: pass itinerary context, airport, city, and locale into the session so the agent can answer from state instead of asking redundant questions.

  3. Fallback UX: when audio permission is denied or the network degrades, your app should degrade gracefully to text instead of failing hard.


On the Kotlin side, that usually means treating the avatar as one channel in a multimodal concierge flow, not the only channel. Keep the transcript and action buttons visible below the video so the user has a stable fallback path.


Session creation and auth: keep secrets off the device


Even if the mobile client directly joins a media session, the app should not contain long-lived secrets. Your backend should authenticate the user, then call the avatar API to create or retrieve the session. The app receives only the short-lived session credential it needs to connect.


If you are implementing the backend yourself, a minimal session creation call looks like this:


curl -X POST https://api.protoface.com/sessions \
}'
curl -X POST https://api.protoface.com/sessions \
}'
curl -X POST https://api.protoface.com/sessions \
}'


The exact request/response fields depend on the API details in the docs, but the pattern is stable: create a session server-side, then hand the mobile app only what it needs to join. For a production travel app, this is not optional. It keeps your API key out of the APK and lets you enforce user-specific policy on your server.


Where Protoface fits


Protoface is a good fit when you want the avatar layer to behave like part of your live voice stack instead of a separate animation feature. For an Android concierge app, the practical integration is usually: your backend creates a realtime avatar session through the REST API, your client joins that session, and the avatar video streams in sync with the conversation. The developer docs at docs.protoface.com cover the session model and the fields involved.


If your voice agent is already built in Python, the SDK is the fastest way to automate session creation, avatar management, or internal tooling. A short example:


from protoface import Client

)
from protoface import Client

)
from protoface import Client

)


Use the SDK on the server, not in Android. The mobile app should consume short-lived session data or a join URL, never your root credentials.


Android implementation gotchas


There are a few recurring mistakes when people first add a streaming avatar to a mobile app:


  • Tying the connection to the Activity: a rotation should not destroy the session unless you mean it to.

  • Rendering the video like a static asset: if the component cannot recover from network jitter, the experience will feel brittle.

  • Ignoring permission timing: ask for microphone access before joining the session so you do not create half-open states.

  • Overloading the avatar with UI state: the avatar should speak, not carry your entire application state machine.


For a travel concierge, you also want deterministic failure behavior. If the agent cannot reach the booking backend, tell the user that explicitly and offer the next action. Realtime media makes failures more visible, not less.


Operational considerations: latency, quality, and cost


Streaming avatars are sensitive to latency. If your voice model, synthesis layer, or session setup is slow, the avatar will feel disconnected from the conversation even if the video itself is smooth. In practice, the best improvements come from:


  • preparing sessions before the user hits the talk button,

  • keeping your backend close to the mobile user base,

  • avoiding unnecessary round trips during turn transitions, and

  • choosing an avatar quality tier that matches your product requirements.


Also plan for usage billing. If you are running a concierge app with heavy session volume, the avatar tier directly affects unit economics, so validate the quality you need rather than defaulting to the highest setting.


Conclusion


Adding a talking avatar to an Android travel concierge app is mostly an exercise in disciplined realtime integration: keep secrets server-side, create short-lived sessions, join a low-latency media stream on the device, and isolate the avatar from the rest of your UI so the app stays resilient across lifecycle changes.


If you want to implement this quickly, start with the public quickstarts on GitHub, read the session and auth details in the docs, and wire the avatar into a single concierge flow before you try to generalize it across the whole app. For deeper implementation details, use the documentation and the examples linked from the quickstart repository.

Add a face to your AI.

No credit card needed.

Add a face to your AI.

No credit card needed.

Add a face to your AI.

No credit card needed.