Skip to main content

Embed Flutter SDK

Follow this guide in order the first time you integrate. Native microphone setup is required on both Android and iOS — skipping it is the most common source of “the mic dialog never appears” and silent voice failures.

Introduction

The embed_flutter SDK adds a voice AI agent to your app: a floating action button (FAB) backed by LiveKit, navigation-aware visibility, and a user-context channel to your embed backend. Package: embed_flutter · Version: 0.0.17 · Requires: Flutter ≥ 3.0.0 / Dart ≥ 3.0.0 What you get out of the box
  • Realtime voice with the agent through the FAB
  • Screen and app context for richer conversations (route tracking via EmbedNavigatorObserver / EmbedRouteListener, optional explicit SCREEN_STATE)
  • Event tracking: host-driven analytics and custom payloads via embedEvent, plus agent lifecycle signals (embedOnAgent)
  • Server-driven UI for the FAB via widget_config from device registration
  • Advanced FAB behavior (route flows/groups, show delays + policies, insets, continuity rules): covered in EmbedWidget advanced — read it once you move past a simple enabledRoutes list

1. Installation

Add the package to your pubspec.yaml:
Then fetch:

1.1 Android Setup

Open android/app/src/main/AndroidManifest.xml and add:

1.2 iOS Setup

Step 1 — Info.plist Add the microphone usage description to your ios/Runner/Info.plist:
Step 2 — Podfile The SDK requires explicit configuration in your ios/Podfile to enable microphone permission requests. Add the PERMISSION_MICROPHONE=1 macro inside your post_install block:
Without the PERMISSION_MICROPHONE=1 macro in the Podfile, the microphone permission dialog will never appear on iOS and the permission will be reported as permanently denied — even if it has not been requested before.
After editing the Podfile, run:

2. How the SDK Works (Mental Model)

Understanding these three concepts up front makes integration straightforward: Order of operations every time:

3. Initialization

Call embedInitialize() before runApp().
Optional: set app version (sent with every analytics event):

4. Wrapping Your App with EmbedWidget

EmbedWidget must be the outermost widget — it wraps your MaterialApp / MaterialApp.router / CupertinoApp.
The agent button appears only on routes listed in enabledRoutes. On all other routes (e.g. splash, login, settings) it is hidden automatically.
Alternatively, to show the agent on every screen except a few:

Available Events

Basic Usage

Here’s a complete example showing how to use the SDK with flow-based activation:
Navigating between routes:
EmbedNavigatorObserver intercepts every push, pop, and replace and shows or hides the agent button based on whether the new route is in enabledRoutes.

5.2 MaterialApp with onGenerateRoute

When routes carry parameters you typically use onGenerateRoute. Everything else is identical.
Always pass settings: settings to MaterialPageRoute. Without it the route name is null and the SDK cannot determine whether to show the agent.

5.3 GoRouter

GoRouter uses the name field of each GoRoute for matching. Pass the observer in GoRouter.observers.
Navigating with GoRouter:

GoRouter with nameExtractor (path-based matching)

If you prefer to match on URL paths instead of route names, supply a nameExtractor to the observer:
Then use those extracted names in enabledRoutes as usual.

5.4 GoRouter with ShellRoute (Bottom Tab Navigator)

ShellRoute keeps a persistent shell (e.g. a bottom navigation bar) while swapping child routes. The challenge is that ShellRoute children run inside a nested navigator — the top-level observer does not fire for them. Solution: Wrap each tab’s screen with EmbedRouteListener. This widget notifies the SDK of the current screen whenever the tab is displayed.
Key rules for ShellRoute:

5.5 MaterialApp Bottom TabNavigator (BottomNavigationBar)

When using BottomNavigationBar with a plain MaterialApp, push a new named route for each tab using Navigator.pushReplacementNamed so the route stack stays shallow and the observer fires on every tab switch.
How it works:
  • Navigator.pushReplacementNamed fires didReplace on EmbedNavigatorObserver, which updates the current route in the SDK.
  • The SDK checks the new route name against enabledRoutes and shows or hides the agent button — no extra code needed.

6. Sending Events

6.1 USER_DATA (Required)

This is the only required event. Send it after you have a logged-in user ID. It activates voice features and associates all future analytics with that user.
When to call it:
The SDK queues this event internally if the server config hasn’t loaded yet and flushes it automatically — you do not need to wait or retry.

6.2 SCREEN_STATE (Optional)

Sends additional context about the current screen to the agent. Useful when you want the agent to know which step of a multi-step form the user is on, or when multiple routes share one screen widget.
Call it in initState or whenever meaningful context changes (e.g. a stepper advances).

6.3 CUSTOM_EVENT (Optional)

Captures any interaction you want to relay to the agent — button taps, option selections, modal opens, etc.

6.4 ANALYTICS_DATA (Optional)

Like CUSTOM_EVENT but requires a named event identifier. Use this for conversion events, funnel steps, or any event you need to categorise by a fixed name on the backend.
Event reference table:

7. EmbedWidget Parameters Reference

For per-flow visibility props — embedButtonDelayMs, groupDelays, groupContinuity, and groupInsets — see EmbedWidget advanced.

7.1 Route Match Modes

By default route names must match exactly. Use routeMatchMode to relax this:
Leading slashes are normalised — 'home' and '/home' are treated identically.

8. Advanced: Widget Visibility and Positioning

This section covers the common cases — custom position, show-on-all, and the mic callback. For route flows/groups, per-flow show delays + policies, continuity, and per-flow insets, see the companion guide:

EmbedWidget advanced — FAB visibility (flows, delays, continuity, insets)

Important for production UX. Read it once you move past a simple enabledRoutes list and need the FAB to behave differently per flow or screen.

Custom FAB Position

Override the default floating button position with rightPadding and bottomPadding:

Show on All Routes with Exclusions

When you want the agent everywhere except a handful of screens:

Microphone Permission Callback

Handle the case where the user denies microphone access when starting a call:
When is onPermissionStatusChanged called?
  • After the user taps the agent button and the permission dialog is shown.
  • Before the call connection is established.
  • true = permission granted, the call will proceed.
  • false = permission denied or permanently denied.

9. Advanced: Listening to SDK Events

Subscribe to events emitted by the agent from anywhere in your app.

Agent lifecycle events

Available SdkEventName constants:

10. Utility Functions

11. Complete End-to-End Example

This example covers everything: GoRouter with a bottom tab shell, EmbedRouteListener for tabs, USER_DATA after login, SCREEN_STATE for sub-screen context, and CUSTOM_EVENT / ANALYTICS_DATA for interactions.

12. Troubleshooting

Agent button not visible

Agent not showing on a specific tab (ShellRoute / BottomNavigationBar)

iOS microphone permission never requested

GoRouter routes not tracked

Clearing SDK data on logout


EmbedWidget advanced — FAB visibility

Route flows/groups, per-flow show delays and policies, continuity, and per-flow insets. The companion guide for production-grade FAB behavior.

Support

Last updated: April 2026 · Flutter: 3.0.0+ · embed_flutter: 0.0.17