Skip to main content

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.
Terminology: A React “group” maps to a Flutter “flow” — the string key used in enabledRoutes, groupDelays, groupContinuity, and groupInsets. Throughout this doc, flow = group.

Overview

The FAB becomes visible on a screen only when all of these are true:
GateControlled by
Widget enabledshowEmbedWidget: true
Current route is in an enabled flow (or all routes)enabledRoutes / showOnAllRoutes
Route is not blacklisteddisabledRoutes
Agent is live for the flowbackend config (is_live)
Call channel initializedinternal (LiveKit)
Pre-show delay elapsedembedButtonDelayMs / 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).

Prerequisites

RequirementNotes
embed_flutter installedadded to pubspec.yaml
SDK initializedembedInitialize(apiKey, flowName: ...) (or use EmbedProvider)
Route detection wiredEmbedNavigatorObserver, EmbedRouteListener, or manual setCurrentScreen
Route names matchthe names you pass to enabledRoutes must match the names your router reports

Flutter integration (install, native setup, basic EmbedWidget)

Start here if you have not finished installation, Android/iOS native setup, embedInitialize, and a minimal EmbedWidget around your MaterialApp.

Basic setup

Step 1 — Minimal (show on all routes)

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

Step 2 — Allow-list specific routes (a flow)

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

Step 3 — Add a global pre-show delay

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)

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

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

Pick whichever fits your routing setup. All three feed the same EmbedRouteManager.
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:
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)

Wrap a screen’s body; it reports routeName to the manager on build:
EmbedRouteListener(
  routeName: 'home_screen',
  child: HomeScreenBody(),
);

3. EmbedRouteManager().setCurrentScreen(...) (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 /):
@override
void initState() {
  super.initState();
  EmbedRouteManager().setCurrentScreen('welcome_screen');
}

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). It is the fallback delay: any flow without its own groupDelays entry uses this value. Default 0 (appear immediately).
The nudge/inactivity countdown starts only after the FAB becomes visible (i.e. after this delay), not on route entry.

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({
  required int delayMs,
  EmbedButtonDelayPolicy policy = EmbedButtonDelayPolicy.perScreen,
});

EmbedButtonDelayPolicy

ValueBehavior
perScreen (default)Delay runs on every screen visit within the flow. With continuity: keepVisible, an already-visible FAB stays up (no re-delay).
oncePerGroupEntryDelay runs only when entering the flow; screens visited while staying in the flow skip it. Leaving and re-entering re-applies it.
oncePerAppSessionDelay 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

ValueAliasBehavior
keepVisiblecontinuousFAB stays visible when navigating between screens in the same flow — no hide/show cycle, no re-delay.
resetperScreenFAB 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

groupDelays[flow].delayMs  →  embedButtonDelayMs  →  0

Insets (positioning)

Default position is bottom-right, derived from rightPadding / bottomPadding (logical pixels). Override per flow with groupInsets:
class EmbedButtonInset {
  final double right;  // default 20.0
  final double bottom; // default 100.0
  const EmbedButtonInset({this.right = 20.0, this.bottom = 100.0});
}
groupInsets: const {
  'checkout': EmbedButtonInset(right: 16, bottom: 120),
},
A matching flow’s inset takes precedence over rightPadding / bottomPadding.
The backend UI config may also carry a widget_position (bottom-left / top-right / …) and padding that influence the corner the FAB anchors to.

Configuration reference

EmbedWidget props

PropTypeDefaultDescription
childWidgetYour app (typically MaterialApp).
showEmbedWidgetbooltrueMaster on/off for the FAB.
apiKeyString?Optional; usually set via embedInitialize.
embedUrlString?Backend URL override.
enabledRoutesMap<String, List<String>>?Flow → routes the FAB is allowed on.
showOnAllRoutesboolfalseShow on every route (overrides enabledRoutes).
disabledRoutesList<String>?Routes where the FAB is always hidden (blacklist).
routeMatchModeRouteMatchModeexactHow routes match patterns: exact / contains / startsWith.
embedButtonDelayMsint0Global pre-show delay; fallback for flows without groupDelays.
groupDelaysMap<String, EmbedButtonDelay>?Per-flow delay + EmbedButtonDelayPolicy.
groupContinuityMap<String, EmbedButtonContinuity>?Per-flow continuity (keepVisible/reset).
groupInsetsMap<String, EmbedButtonInset>?Per-flow position override.
rightPaddingdouble?20Default FAB right padding (dp).
bottomPaddingdouble?100Default FAB bottom padding (dp).
onPermissionStatusChangedvoid Function(bool)?Microphone permission callback.

EmbedProvider props

Convenience wrapper; also calls embedInitialize. Exposes a subset:
PropTypeDefault
childWidget
apiKeyString— (required)
embedUrlString?
flowNameString?
enabledRoutesMap<String, List<String>>?
showOnAllRoutesboolfalse
disabledRoutesList<String>?
routeMatchModeRouteMatchModeexact
embedButtonDelayMsint0
groupDelaysMap<String, EmbedButtonDelay>?
rightPadding / bottomPaddingdouble?20 / 100
onPermissionStatusChangedvoid Function(bool)?
onInitResultvoid Function(bool, String?)?
EmbedProvider does not currently expose groupInsets or groupContinuity. Use EmbedWidget directly if you need those.

Types

TypeMembers
RouteMatchModeexact, contains, startsWith
EmbedButtonDelaydelayMs: int, policy: EmbedButtonDelayPolicy
EmbedButtonDelayPolicyperScreen, oncePerGroupEntry, oncePerAppSession + fromString
EmbedButtonContinuitykeepVisible(=continuous), reset(=perScreen) + fromString
EmbedButtonInsetright: double = 20, bottom: double = 100

Usage examples

Example 1 — Multi-screen flow + one-shot confirmation flow

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

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)

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

Practical scenarios

ScenarioConfiguration
Let users read a form before the FAB appearsembedButtonDelayMs: 4000 (or a per-flow EmbedButtonDelay).
Keep the FAB steady across a multi-step flowgroupContinuity: {'flow': EmbedButtonContinuity.continuous} + oncePerGroupEntry delay.
Show the FAB only once per session, then instantlyEmbedButtonDelay(delayMs: 2000, policy: oncePerAppSession).
Avoid overlapping a bottom bar on one flowgroupInsets: {'flow': EmbedButtonInset(bottom: 140)}.
Hide the FAB on auth/splash screensomit them from enabledRoutes, or add to disabledRoutes.

Differences from the React provider

React NativeFlutterNotes
includeScreensenabledRoutesFlutter groups routes under a flow key.
embedButtonVisibilityConfig (single object)groupDelays + groupContinuity + groupInsets (parallel maps)Same capabilities, different shape.
group delayMs / delayPolicyEmbedButtonDelay
group continuity (continuous/perScreen)EmbedButtonContinuity (+ aliases)
embedButtonDelayMsembedButtonDelayMs
defaultDelayMscovered by global embedButtonDelayMs.
EmbedButtonInset { top, right, bottom, left }EmbedButtonInset { right, bottom }⚠️ Flutter supports right/bottom only.
defaultInsetuse rightPadding/bottomPadding.
navigationRefEmbedNavigatorObserver / EmbedRouteListener / setCurrentScreenroute detection.
appVersionnot exposed.

Troubleshooting

SymptomLikely causeFix
FAB never appears on a routeroute name doesn’t match enabledRoutesconfirm the name your router reports; use nameExtractor or setCurrentScreen.
FAB missing on the initial/home screenhome: route is reported as /, not your namemap / in nameExtractor, or call setCurrentScreen('...') in that screen’s initState.
FAB appears off-screenstale/zero screen size at first layoutensure you’re on a recent SDK (position is recomputed post-layout).
Delay never elapses / FAB stays hiddenvery large delayMs, or route flips inactive before it firesverify the route stays in the flow; check embedButtonDelayMs/groupDelays.
FAB flickers between screens in a flowcontinuity is resetset EmbedButtonContinuity.keepVisible (continuous) for that flow.

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.

Back to Flutter integration

Installation, native LiveKit setup, embedInitialize, navigator integration, events, and troubleshooting.

React Native — EmbedProvider advanced

The React Native counterpart: includeScreens, embedButtonDelayMs, and embedButtonVisibilityConfig.

For additional help visit https://docs.revrag.ai/embed/integration/flutter or email contact@revrag.ai.