# EmbedWidget advanced (Flutter)

> Advanced FAB visibility for Flutter: route allow-lists, multi-flow grouping, pre-show delays and policies, per-flow positioning, and continuity.

URL: /embed/integration/embed-flutter-advance
Markdown: /embed/integration/embed-flutter-advance.md



# EmbedWidget — Advanced FAB Visibility (Flutter) [#embedwidget--advanced-fab-visibility-flutter]

Advanced control over **when and where** the floating agent button (FAB) appears
in your Flutter app: route allow-lists, multi-flow grouping, pre-show delays +
policies, per-flow positioning, and continuity.

This is the Flutter counterpart of the React Native
[EmbedProvider Advanced guide](/embed/integration/embed-provider-advanced).

<Note>
  **Terminology:*&#x2A; A React &#x2A;*"group"*&#x2A; maps to a Flutter &#x2A;*"flow"** — the string
  key used in `enabledRoutes`, `groupDelays`, `groupContinuity`, and
  `groupInsets`. Throughout this doc, *flow = group*.
</Note>

***

## Overview [#overview]

The FAB becomes visible on a screen only when **all** of these are true:

| Gate                                                | Controlled by                        |
| --------------------------------------------------- | ------------------------------------ |
| Widget enabled                                      | `showEmbedWidget: true`              |
| Current route is in an enabled flow (or all routes) | `enabledRoutes` / `showOnAllRoutes`  |
| Route is not blacklisted                            | `disabledRoutes`                     |
| Agent is live for the flow                          | backend config (`is_live`)           |
| Call channel initialized                            | internal (LiveKit)                   |
| Pre-show delay elapsed                              | `embedButtonDelayMs` / `groupDelays` |

Two integration surfaces:

* **`EmbedWidget`** — full-control widget that wraps your app. Exposes every
  visibility option below.
* **`EmbedProvider`** — convenience wrapper that also calls `embedInitialize`
  for you. Exposes the common subset (see
  [Differences](#differences-from-the-react-provider)).

***

## Prerequisites [#prerequisites]

| Requirement               | Notes                                                                          |
| ------------------------- | ------------------------------------------------------------------------------ |
| `embed_flutter` installed | added to `pubspec.yaml`                                                        |
| SDK initialized           | `embedInitialize(apiKey, flowName: ...)` (or use `EmbedProvider`)              |
| Route detection wired     | `EmbedNavigatorObserver`, `EmbedRouteListener`, or manual `setCurrentScreen`   |
| Route names match         | the names you pass to `enabledRoutes` must match the names your router reports |

<CardGroup cols="1">
  <Card title="Flutter integration (install, native setup, basic EmbedWidget)" href="/embed/integration/flutter">
    Start here if you have not finished installation, Android/iOS native setup,
    `embedInitialize`, and a minimal `EmbedWidget` around your `MaterialApp`.
  </Card>
</CardGroup>

***

## Basic setup [#basic-setup]

### Step 1 — Minimal (show on all routes) [#step-1--minimal-show-on-all-routes]

```dart
EmbedWidget(
  showOnAllRoutes: true,
  child: MaterialApp(
    navigatorObservers: [EmbedNavigatorObserver()],
    home: const HomeScreen(),
  ),
);
```

### Step 2 — Allow-list specific routes (a flow) [#step-2--allow-list-specific-routes-a-flow]

```dart
EmbedWidget(
  enabledRoutes: const {
    'main': ['home_screen', 'profile_plans'],
  },
  child: MaterialApp(
    navigatorObservers: [EmbedNavigatorObserver()],
    home: const HomeScreen(),
  ),
);
```

### Step 3 — Add a global pre-show delay [#step-3--add-a-global-pre-show-delay]

```dart
EmbedWidget(
  enabledRoutes: const {'main': ['home_screen', 'profile_plans']},
  embedButtonDelayMs: 3000, // FAB appears 3s after entering an enabled route
  child: /* ... */,
);
```

### Step 4 — Full per-flow groups (delay policy, continuity, position) [#step-4--full-per-flow-groups-delay-policy-continuity-position]

```dart
EmbedWidget(
  enabledRoutes: const {
    'main': ['home_screen', 'profile_plans'],
    'checkout': ['cart_screen', 'payment_screen'],
  },
  embedButtonDelayMs: 2000, // fallback for flows without a groupDelays entry
  groupDelays: const {
    'main': EmbedButtonDelay(
      delayMs: 4000,
      policy: EmbedButtonDelayPolicy.perScreen,
    ),
    'checkout': EmbedButtonDelay(
      delayMs: 1000,
      policy: EmbedButtonDelayPolicy.oncePerAppSession,
    ),
  },
  groupContinuity: const {
    'checkout': EmbedButtonContinuity.continuous, // stay visible within flow
  },
  groupInsets: const {
    'checkout': EmbedButtonInset(right: 16, bottom: 120),
  },
  child: /* ... */,
);
```

***

## How screen-based visibility works [#how-screen-based-visibility-works]

The SDK keeps a single source of truth in `EmbedRouteManager` (a singleton):
the **current route** and the **current flow**. On every route change the manager
re-evaluates whether the FAB should be active for that route.

```
route change ──▶ EmbedRouteManager.updateRoute(name)
                   │  resolves flow via enabledRoutes + RouteMatchMode
                   ▼
              isEmbedActive?  ──▶ EmbedWidget shows / hides the FAB
```

Route names are **normalized** so a leading slash is ignored — `/home` and
`home` match the same pattern. This makes GoRouter (which reports `/home`) and
the standard `Navigator` (which may use `home`) interoperable.

***

## Route detection options [#route-detection-options]

Pick whichever fits your routing setup. All three feed the same
`EmbedRouteManager`.

### 1. `EmbedNavigatorObserver` (recommended for `Navigator` / GoRouter) [#1-embednavigatorobserver-recommended-for-navigator--gorouter]

```dart
MaterialApp(
  navigatorObservers: [EmbedNavigatorObserver()],
  // ...
);
```

For GoRouter (which reports full paths) or any custom naming, supply
`nameExtractor` to map a route to the name used in `enabledRoutes`:

```dart
EmbedNavigatorObserver(
  nameExtractor: (route) {
    final path = route.settings.name ?? '';
    if (path.startsWith('/home')) return 'home_screen';
    // The app's home/initial route is reported as '/' — map it explicitly:
    if (path == '/' ) return 'welcome_screen';
    return path.isNotEmpty ? path : null; // return null to ignore a route
  },
);
```

### 2. `EmbedRouteListener` (declarative, per-screen) [#2-embedroutelistener-declarative-per-screen]

Wrap a screen's body; it reports `routeName` to the manager on build:

```dart
EmbedRouteListener(
  routeName: 'home_screen',
  child: HomeScreenBody(),
);
```

### 3. `EmbedRouteManager().setCurrentScreen(...)` (manual) [#3-embedroutemanagersetcurrentscreen-manual]

Framework-agnostic — call from a screen's `initState` (useful for shell routes,
custom page managers, or the app's initial `home:` route whose name is `/`):

```dart
@override
void initState() {
  super.initState();
  EmbedRouteManager().setCurrentScreen('welcome_screen');
}
```

***

## Global delay [#global-delay]

`embedButtonDelayMs` keeps the FAB hidden for N milliseconds after the user
lands on an enabled route, then shows it with animation. The timer resets when
navigating away and back (subject to [delay policy](#visibility-groups-flows)).

It is the **fallback** delay: any flow without its own `groupDelays` entry uses
this value. Default `0` (appear immediately).

<Note>
  The nudge/inactivity countdown starts only **after** the FAB becomes visible
  (i.e. after this delay), not on route entry.
</Note>

***

## Visibility groups (flows) [#visibility-groups-flows]

A flow groups a set of routes under shared behavior. Define routes with
`enabledRoutes`; attach per-flow behavior with the parallel maps `groupDelays`,
`groupContinuity`, and `groupInsets` (all keyed by the same flow name).

### `EmbedButtonDelay` [#embedbuttondelay]

```dart
EmbedButtonDelay({
  required int delayMs,
  EmbedButtonDelayPolicy policy = EmbedButtonDelayPolicy.perScreen,
});
```

### `EmbedButtonDelayPolicy` [#embedbuttondelaypolicy]

| Value                 | Behavior                                                                                                                              |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `perScreen` (default) | Delay runs on **every** screen visit within the flow. With `continuity: keepVisible`, an already-visible FAB stays up (no re-delay).  |
| `oncePerGroupEntry`   | Delay runs only when **entering** the flow; screens visited while staying in the flow skip it. Leaving and re-entering re-applies it. |
| `oncePerAppSession`   | Delay runs only the **first time** the flow is shown in the app session; later visits show immediately.                               |

Parse from a string with `EmbedButtonDelayPolicy.fromString('oncePerGroupEntry')`.

### `EmbedButtonContinuity` [#embedbuttoncontinuity]

| Value         | Alias        | Behavior                                                                                              |
| ------------- | ------------ | ----------------------------------------------------------------------------------------------------- |
| `keepVisible` | `continuous` | FAB stays visible when navigating between screens in the same flow — no hide/show cycle, no re-delay. |
| `reset`       | `perScreen`  | FAB hides then re-shows on every route change, even within the flow.                                  |

Aliases match the React API; `EmbedButtonContinuity.continuous == keepVisible`
and `EmbedButtonContinuity.perScreen == reset`. Parse with
`EmbedButtonContinuity.fromString('continuous')`.

### Delay-resolution chain [#delay-resolution-chain]

```
groupDelays[flow].delayMs  →  embedButtonDelayMs  →  0
```

***

## Insets (positioning) [#insets-positioning]

Default position is bottom-right, derived from `rightPadding` / `bottomPadding`
(logical pixels). Override per flow with `groupInsets`:

```dart
class EmbedButtonInset {
  final double right;  // default 20.0
  final double bottom; // default 100.0
  const EmbedButtonInset({this.right = 20.0, this.bottom = 100.0});
}
```

```dart
groupInsets: const {
  'checkout': EmbedButtonInset(right: 16, bottom: 120),
},
```

A matching flow's inset takes precedence over `rightPadding` / `bottomPadding`.

<Note>
  The backend UI config may also carry a `widget_position`
  (`bottom-left` / `top-right` / …) and padding that influence the corner the
  FAB anchors to.
</Note>

***

## Configuration reference [#configuration-reference]

### `EmbedWidget` props [#embedwidget-props]

| Prop                        | Type                                  | Default | Description                                                      |
| --------------------------- | ------------------------------------- | ------- | ---------------------------------------------------------------- |
| `child`                     | `Widget`                              | —       | Your app (typically `MaterialApp`).                              |
| `showEmbedWidget`           | `bool`                                | `true`  | Master on/off for the FAB.                                       |
| `apiKey`                    | `String?`                             | —       | Optional; usually set via `embedInitialize`.                     |
| `embedUrl`                  | `String?`                             | —       | Backend URL override.                                            |
| `enabledRoutes`             | `Map<String, List<String>>?`          | —       | Flow → routes the FAB is allowed on.                             |
| `showOnAllRoutes`           | `bool`                                | `false` | Show on every route (overrides `enabledRoutes`).                 |
| `disabledRoutes`            | `List<String>?`                       | —       | Routes where the FAB is always hidden (blacklist).               |
| `routeMatchMode`            | `RouteMatchMode`                      | `exact` | How routes match patterns: `exact` / `contains` / `startsWith`.  |
| `embedButtonDelayMs`        | `int`                                 | `0`     | Global pre-show delay; fallback for flows without `groupDelays`. |
| `groupDelays`               | `Map<String, EmbedButtonDelay>?`      | —       | Per-flow delay + `EmbedButtonDelayPolicy`.                       |
| `groupContinuity`           | `Map<String, EmbedButtonContinuity>?` | —       | Per-flow continuity (`keepVisible`/`reset`).                     |
| `groupInsets`               | `Map<String, EmbedButtonInset>?`      | —       | Per-flow position override.                                      |
| `rightPadding`              | `double?`                             | `20`    | Default FAB right padding (dp).                                  |
| `bottomPadding`             | `double?`                             | `100`   | Default FAB bottom padding (dp).                                 |
| `onPermissionStatusChanged` | `void Function(bool)?`                | —       | Microphone permission callback.                                  |

### `EmbedProvider` props [#embedprovider-props]

Convenience wrapper; also calls `embedInitialize`. Exposes a subset:

| Prop                             | Type                             | Default      |
| -------------------------------- | -------------------------------- | ------------ |
| `child`                          | `Widget`                         | —            |
| `apiKey`                         | `String`                         | — (required) |
| `embedUrl`                       | `String?`                        | —            |
| `flowName`                       | `String?`                        | —            |
| `enabledRoutes`                  | `Map<String, List<String>>?`     | —            |
| `showOnAllRoutes`                | `bool`                           | `false`      |
| `disabledRoutes`                 | `List<String>?`                  | —            |
| `routeMatchMode`                 | `RouteMatchMode`                 | `exact`      |
| `embedButtonDelayMs`             | `int`                            | `0`          |
| `groupDelays`                    | `Map<String, EmbedButtonDelay>?` | —            |
| `rightPadding` / `bottomPadding` | `double?`                        | `20` / `100` |
| `onPermissionStatusChanged`      | `void Function(bool)?`           | —            |
| `onInitResult`                   | `void Function(bool, String?)?`  | —            |

<Warning>
  `EmbedProvider` does **not** currently expose `groupInsets` or
  `groupContinuity`. Use `EmbedWidget` directly if you need those.
</Warning>

### Types [#types]

| Type                     | Members                                                              |
| ------------------------ | -------------------------------------------------------------------- |
| `RouteMatchMode`         | `exact`, `contains`, `startsWith`                                    |
| `EmbedButtonDelay`       | `delayMs: int`, `policy: EmbedButtonDelayPolicy`                     |
| `EmbedButtonDelayPolicy` | `perScreen`, `oncePerGroupEntry`, `oncePerAppSession` + `fromString` |
| `EmbedButtonContinuity`  | `keepVisible`(=`continuous`), `reset`(=`perScreen`) + `fromString`   |
| `EmbedButtonInset`       | `right: double = 20`, `bottom: double = 100`                         |

***

## Usage examples [#usage-examples]

### Example 1 — Multi-screen flow + one-shot confirmation flow [#example-1--multi-screen-flow--one-shot-confirmation-flow]

```dart
EmbedWidget(
  enabledRoutes: const {
    'shopping': ['catalog', 'product', 'cart'],
    'confirm': ['order_confirmed'],
  },
  groupDelays: const {
    'shopping': EmbedButtonDelay(
      delayMs: 3000,
      policy: EmbedButtonDelayPolicy.oncePerGroupEntry,
    ),
    'confirm': EmbedButtonDelay(
      delayMs: 0, // immediate on the confirmation screen
      policy: EmbedButtonDelayPolicy.oncePerAppSession,
    ),
  },
  groupContinuity: const {
    'shopping': EmbedButtonContinuity.continuous,
  },
  child: /* ... */,
);
```

### Example 2 — Two single-screen flows with different delays [#example-2--two-single-screen-flows-with-different-delays]

```dart
EmbedWidget(
  enabledRoutes: const {
    'help': ['support'],
    'billing': ['invoices'],
  },
  groupDelays: const {
    'help': EmbedButtonDelay(delayMs: 1000),
    'billing': EmbedButtonDelay(delayMs: 5000),
  },
  child: /* ... */,
);
```

### Example 3 — Global delay only (no per-flow config) [#example-3--global-delay-only-no-per-flow-config]

```dart
EmbedWidget(
  enabledRoutes: const {'main': ['home', 'profile']},
  embedButtonDelayMs: 2000, // applies to every enabled route
  child: /* ... */,
);
```

***

## Practical scenarios [#practical-scenarios]

| Scenario                                               | Configuration                                                                              |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
| **Let users read a form before the FAB appears**       | `embedButtonDelayMs: 4000` (or a per-flow `EmbedButtonDelay`).                             |
| **Keep the FAB steady across a multi-step flow**       | `groupContinuity: {'flow': EmbedButtonContinuity.continuous}` + `oncePerGroupEntry` delay. |
| **Show the FAB only once per session, then instantly** | `EmbedButtonDelay(delayMs: 2000, policy: oncePerAppSession)`.                              |
| **Avoid overlapping a bottom bar on one flow**         | `groupInsets: {'flow': EmbedButtonInset(bottom: 140)}`.                                    |
| **Hide the FAB on auth/splash screens**                | omit them from `enabledRoutes`, or add to `disabledRoutes`.                                |

***

## Differences from the React provider [#differences-from-the-react-provider]

| React Native                                    | Flutter                                                              | Notes                                   |
| ----------------------------------------------- | -------------------------------------------------------------------- | --------------------------------------- |
| `includeScreens`                                | `enabledRoutes`                                                      | Flutter groups routes under a flow key. |
| `embedButtonVisibilityConfig` (single object)   | `groupDelays` + `groupContinuity` + `groupInsets` (parallel maps)    | Same capabilities, different shape.     |
| group `delayMs` / `delayPolicy`                 | `EmbedButtonDelay`                                                   | ✅                                       |
| group `continuity` (`continuous`/`perScreen`)   | `EmbedButtonContinuity` (+ aliases)                                  | ✅                                       |
| `embedButtonDelayMs`                            | `embedButtonDelayMs`                                                 | ✅                                       |
| `defaultDelayMs`                                | —                                                                    | covered by global `embedButtonDelayMs`. |
| `EmbedButtonInset { top, right, bottom, left }` | `EmbedButtonInset { right, bottom }`                                 | ⚠️ Flutter supports right/bottom only.  |
| `defaultInset`                                  | —                                                                    | use `rightPadding`/`bottomPadding`.     |
| `navigationRef`                                 | `EmbedNavigatorObserver` / `EmbedRouteListener` / `setCurrentScreen` | route detection.                        |
| `appVersion`                                    | —                                                                    | not exposed.                            |

***

## Troubleshooting [#troubleshooting]

| Symptom                                    | Likely cause                                                  | Fix                                                                                         |
| ------------------------------------------ | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| FAB never appears on a route               | route name doesn't match `enabledRoutes`                      | confirm the name your router reports; use `nameExtractor` or `setCurrentScreen`.            |
| FAB missing on the **initial/home** screen | `home:` route is reported as `/`, not your name               | map `/` in `nameExtractor`, or call `setCurrentScreen('...')` in that screen's `initState`. |
| FAB appears off-screen                     | stale/zero screen size at first layout                        | ensure you're on a recent SDK (position is recomputed post-layout).                         |
| Delay never elapses / FAB stays hidden     | very large `delayMs`, or route flips inactive before it fires | verify the route stays in the flow; check `embedButtonDelayMs`/`groupDelays`.               |
| FAB flickers between screens in a flow     | continuity is `reset`                                         | set `EmbedButtonContinuity.keepVisible` (`continuous`) for that flow.                       |

***

## Best practices [#best-practices]

1. **Match route names exactly** to what your router reports (after slash
   normalization). Prefer one detection mechanism (observer *or* listener *or*
   manual) per app.
2. **Use flows as groups** — one flow per logical journey (checkout, onboarding).
3. **Prefer `oncePerGroupEntry` / `oncePerAppSession`** for delays inside
   multi-screen flows so users aren't re-delayed on every screen.
4. **Pair `continuous` continuity with `oncePerGroupEntry`** for a steady FAB
   across a flow.
5. **Keep delays short** (1–4s) — long delays read as "no button".
6. **Use `groupInsets`** to dodge bottom bars/FABs on specific flows rather than
   moving the global padding.

***

## Related documentation [#related-documentation]

<CardGroup cols="2">
  <Card title="Back to Flutter integration" href="/embed/integration/flutter">
    Installation, native LiveKit setup, `embedInitialize`, navigator
    integration, events, and troubleshooting.
  </Card>

  <Card title="React Native — EmbedProvider advanced" href="/embed/integration/embed-provider-advanced">
    The React Native counterpart: `includeScreens`, `embedButtonDelayMs`, and
    `embedButtonVisibilityConfig`.
  </Card>
</CardGroup>

***

*For additional help visit [https://docs.revrag.ai/embed/integration/flutter](https://docs.revrag.ai/embed/integration/flutter) or email [contact@revrag.ai](mailto:contact@revrag.ai).*
