embed_flutter 0.0.5
embed_flutter: ^0.0.5 copied to clipboard
A powerful Flutter SDK that provides a powerful voice-enabled AI-agent functionality with real-time widget tree monitoring for screen context fetching and realtime voice communication.
Embed Flutter SDK #
A powerful Flutter SDK that provides a powerful voice-enabled AI-agent functionality with real-time widget tree monitoring for screen context fetching and realtime voice communication.
Table of Contents #
Installation #
Add the following dependency to your pubspec.yaml:
dependencies:
embed_flutter: ^0.0.5
Then run:
flutter pub get
Android Configuration
Add the following permissions to your android/app/src/main/AndroidManifest.xml:
<!-- Required permissions for Embed SDK -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MICROPHONE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- Add the following permissions for embed -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MICROPHONE" />
iOS Configuration
Add the following permissions to your ios/Runner/Info.plist:
<key>NSMicrophoneUsageDescription</key>
<string>This app needs access to microphone for voice calls.</string>
Getting Started #
Initialization #
Before using any EmbedWidget, you must initialize the configuration once in your main.dart. You can initialize multiple API keys for different flows:
void main() {
// Initialize API keys for different flows
embedInitialize(
'api-key-for-flow-1',
flowName: 'flow 1', // Optional: Flow identifier
embedUrl: 'https://embed.revrag.ai', // Optional
);
embedInitialize(
'api-key-for-flow-2',
flowName: 'flow 2', // Optional: Flow identifier
embedUrl: 'https://embed.revrag.ai', // Optional
);
runApp(const MyApp());
}
Note: Each
embedInitialize()call saves the API key for that specific flow. When a user navigates to a route, the SDK automatically detects which flow the route belongs to and uses the corresponding API key.
Required: Navigator Observer
For route-based activation to work properly, you must add EmbedNavigatorObserver to your app's navigator observers. This enables the SDK to track route changes and activate/deactivate the EmbedWidget accordingly.
For GoRouter:
import 'package:go_router/go_router.dart';
import 'package:embed_flutter/embed_flutter.dart';
final GoRouter router = GoRouter(
observers: [EmbedNavigatorObserver()], // Required for route tracking
routes: [
// Your routes here
],
);
For MaterialApp:
import 'package:embed_flutter/embed_flutter.dart';
MaterialApp(
navigatorObservers: [EmbedNavigatorObserver()], // Required for route tracking
// Other MaterialApp properties
)
Required: USER_DATA Event
To activate the EmbedWidget on the screen, you must call the USER_DATA embedEvent with a user ID. This is typically done after user authentication or when you have a user identifier:
import 'package:embed_flutter/embed_flutter.dart';
// Call this after user authentication or when you have a user ID
embedEvent(
EventKeys.USER_DATA,
UserEventPayload(
app_user_id: 'user_12345', // Required: Unique user identifier
data: {
'name': 'John Doe', // Optional: Additional user data
'email': 'john@example.com',
'phone': '+1234567890',
},
),
);
Important: The EmbedWidget will not be fully functional until the
USER_DATAevent is sent. This event initializes the voice communication features.
Optional: SCREEN_STATE Event
You can optionally send SCREEN_STATE events to provide additional context about screen changes or user navigation:
// Send screen state when navigating to a new screen
embedEvent(
EventKeys.SCREEN_STATE,
ScreenEventPayload(
screen: 'product_selection_screen', // Required: Screen identifier
data: {
'category': 'banking', // Optional: Additional screen context
'user_type': 'premium',
'flow_step': 'selection',
},
),
);
Available Events
| Event | Purpose | Required | When to Use |
|---|---|---|---|
USER_DATA |
Initialize user context and activate EmbedWidget | Required | After user authentication or when user ID is available |
SCREEN_STATE |
Provide screen context and navigation info | Optional | When navigating between screens or when screen context changes |
Basic Usage #
Here's a complete example showing how to use the SDK with flow-based activation:
import 'package:flutter/material.dart';
import 'package:embed_flutter/embed_flutter.dart';
void main() {
// 1. Initialize the SDK with flow-based API keys
embedInitialize('api-key-for-flow-1', flowName: 'flow 1');
embedInitialize('api-key-for-flow-2', flowName: 'flow 2');
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return EmbedWidget(
showEmbedWidget: true,
// 4. Configure flow-based activation
enabledRoutes: {
'flow 1': ['home', 'product_selection_screen'],
'flow 2': ['checkout', 'payment'],
},
child: MaterialApp(
navigatorObservers: [EmbedNavigatorObserver()], // Required for route tracking
routes: [
'home': (context) => const MyHomePage(),
'product_selection_screen': (context) => const ProductSelectionScreen(),
'checkout': (context) => const CheckoutScreen(),
'payment': (context) => const PaymentScreen(),
]
home: MyHomePage(),
),
);
}
}
class MyHomePage extends StatefulWidget {
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
void initState() {
super.initState();
// 2. Send USER_DATA event to activate EmbedWidget
// This should typically be called after user authentication
embedEvent(
EventKeys.USER_DATA,
UserEventPayload(
app_user_id: 'user_12345', // Replace with actual user ID
data: {
'name': 'John Doe',
'email': 'john@example.com',
'plan': 'premium',
},
),
);
}
void _navigateToProductScreen() {
// 3. Send SCREEN_STATE event for additional context (Optional)
embedEvent(
EventKeys.SCREEN_STATE,
ScreenEventPayload(
screen: 'product_selection_screen',
data: {
'category': 'banking_products',
'user_segment': 'new_customer',
},
),
);
// Navigate to product screen
Navigator.pushNamed(context, 'product_selection');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('My App')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Welcome to My App!'),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _navigateToProductScreen,
child: const Text('Go to Product Selection'),
),
],
),
),
);
}
}
Required Parameters #
child(required): The main content of your app
Flow-Based Activation #
The SDK supports intelligent flow-based activation, allowing you to organize routes into flows and assign different API keys to each flow. When a user navigates to a route, the SDK automatically detects which flow the route belongs to and uses the corresponding API key.
Basic Flow Configuration
void main() {
// Initialize API keys for different flows
embedInitialize('api-key-for-flow-1', flowName: 'flow 1');
embedInitialize('api-key-for-flow-2', flowName: 'flow 2');
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return EmbedWidget(
// Configure flows with their associated routes
enabledRoutes: {
'flow 1': [
'personal_info',
'personal_details',
'product_selection_screen',
'additional_info',
'review_confirm_screen',
],
'flow 2': [
'aadhar_verification_screen',
],
},
child: MaterialApp.router(
routerConfig: appRouter,
),
);
}
}
How Flow-Based Activation Works
-
Flow Detection: When a user navigates to a route, the SDK automatically detects which flow the route belongs to based on your
enabledRoutesconfiguration. -
API Key Selection: The SDK retrieves the API key that was saved for the detected flow during initialization.
-
Flow Switching: When switching between flows (or navigating outside any flow), the EmbedWidget automatically:
- Ends any active calls
- Re-initializes with the new flow's API key when entering a new flow
-
Route Matching: Routes are matched using the
routeMatchModesetting (default:RouteMatchMode.exact).
Route Configuration Options
enabledRoutes:Map<String, List<String>>- Flow name to list of route names mapping- Key: Flow identifier (e.g.,
'flow 1','flow 2') - Value: List of route names that belong to that flow
- Key: Flow identifier (e.g.,
showOnAllRoutes: Show EmbedWidget on all routes (default:false)disabledRoutes: List of route names where EmbedWidget should be disabledrouteMatchMode: How to match routes:RouteMatchMode.exact- Exact match (default)RouteMatchMode.startsWith- Route starts with patternRouteMatchMode.contains- Route contains pattern
Optional Parameters #
apiKey(optional): Your Revrag AI API key for authentication (can be set globally viaembedInitialize()withflowName)embedUrl(optional): Custom base URL for API endpoints (can be set globally viaembedInitialize())showEmbedWidget(default:true): Controls whether the embedded widget is visibleenabledRoutes(optional):Map<String, List<String>>- Flow name to route names mapping for flow-based activationshowOnAllRoutes(default:false): Show EmbedWidget on all routesdisabledRoutes(optional): List of route names where EmbedWidget should be disabledrouteMatchMode(default:RouteMatchMode.exact): How to match routes
Examples #
Basic Integration #
import 'package:flutter/material.dart';
import 'package:embed_flutter/embed_flutter.dart';
void main() {
// Initialize the SDK
embedInitialize('your-api-key-here');
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return EmbedWidget(
showEmbedWidget: true,
child: MaterialApp(
navigatorObservers: [EmbedNavigatorObserver()], // Required for route tracking
home: MyHomePage(),
),
);
}
}
class MyHomePage extends StatefulWidget {
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
void initState() {
super.initState();
// Send USER_DATA event to activate EmbedWidget
// This should typically be called after user authentication
embedEvent(
EventKeys.USER_DATA,
UserEventPayload(
app_user_id: 'user_12345', // Replace with actual user ID
data: {
'name': 'John Doe',
'email': 'john@example.com',
},
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('My App')),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Welcome to My App!'),
SizedBox(height: 20),
Text('The embedded agent is monitoring this screen.'),
],
),
),
);
}
}
Form with Monitoring #
import 'package:flutter/material.dart';
import 'package:embed_flutter/embed_flutter.dart';
void main() {
// Initialize the SDK
embedInitialize('your-api-key-here');
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return EmbedWidget(
showEmbedWidget: true,
child: MaterialApp(
navigatorObservers: [EmbedNavigatorObserver()], // Required for route tracking
home: const MyFormPage(),
),
);
}
}
class MyFormPage extends StatefulWidget {
const MyFormPage({super.key});
@override
State<MyFormPage> createState() => _MyFormPageState();
}
class _MyFormPageState extends State<MyFormPage> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _emailController = TextEditingController();
@override
void initState() {
super.initState();
// Send USER_DATA event to activate EmbedWidget
// This should typically be called after user authentication
embedEvent(
EventKeys.USER_DATA,
UserEventPayload(
app_user_id: 'user_12345', // Replace with actual user ID
data: {
'name': 'John Doe',
'email': 'john@example.com',
},
),
);
// Send SCREEN_STATE event for additional context
embedEvent(
EventKeys.SCREEN_STATE,
ScreenEventPayload(
screen: 'form_page',
data: {
'form_type': 'user_registration',
'step': 'personal_info',
},
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('My Form')),
body: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextFormField(
controller: _nameController,
decoration: const InputDecoration(labelText: 'Name'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your name';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _emailController,
decoration: const InputDecoration(labelText: 'Email'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
return null;
},
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// Form is valid - EmbedWidget will track the submission
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Form submitted successfully!')),
);
}
},
child: const Text('Submit'),
),
],
),
),
),
);
}
@override
void dispose() {
_nameController.dispose();
_emailController.dispose();
super.dispose();
}
}
Flow-Based Configuration #
Configure the EmbedWidget with flow-based activation to organize routes into flows and use different API keys:
import 'package:flutter/material.dart';
import 'package:embed_flutter/embed_flutter.dart';
void main() {
// Initialize API keys for different flows
embedInitialize('api-key-for-onboarding-flow', flowName: 'onboarding');
embedInitialize('api-key-for-checkout-flow', flowName: 'checkout');
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return EmbedWidget(
// Configure flows with their associated routes
enabledRoutes: {
'onboarding': [
'welcome',
'personal-info',
'personal-details',
'additional-info'
],
'checkout': [
'product-selection',
'payment',
'review-confirm'
],
},
child: MaterialApp(
navigatorObservers: [EmbedNavigatorObserver()], // Required for route tracking
initialRoute: '/',
routes: {
'/': (context) => const SplashScreen(),
'welcome': (context) => const WelcomeScreen(),
'personal-info': (context) => const PersonalInfoScreen(),
'personal-details': (context) => const PersonalDetailsScreen(),
'product-selection': (context) => const ProductSelectionScreen(),
'additional-info': (context) => const AdditionalInfoScreen(),
},
),
);
}
}
Flow Behavior
- Within a Flow: When navigating between routes in the same flow, the EmbedWidget remains active and uses the same API key.
- Between Flows: When switching from one flow to another, the EmbedWidget:
- Automatically ends any active calls
- Cleans up resources
- Re-initializes with the new flow's API key
- Outside Flows: When navigating to routes not in any configured flow, the EmbedWidget is hidden and cleaned up.
Custom Router Integration #
An Example to show Full integration with Custom Routers we can take example of GoRouter for declarative navigation:
import 'package:go_router/go_router.dart';
import 'package:embed_flutter/embed_flutter.dart';
final appRouter = GoRouter(
initialLocation: '/splash',
routes: [
GoRoute(
path: '/splash',
name: 'splash_screen',
builder: (context, state) => const SplashScreen(),
),
GoRoute(
path: '/welcome',
name: 'welcome_screen',
builder: (context, state) => const WelcomeScreen(),
),
GoRoute(
path: '/personal-info',
name: 'personal_info',
builder: (context, state) => const PersonalInfoScreen(),
),
// ... more routes
],
// Add EmbedNavigatorObserver to the list of observers
observers: [
EmbedNavigatorObserver(),
// ... other observers
],
);
void main() {
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
widgetsBinding = EmbedWidgetsBinding();
// Initialize API keys for different flows
embedInitialize('api-key-for-flow-1', flowName: 'flow 1');
embedInitialize('api-key-for-flow-2', flowName: 'flow 2');
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return EmbedWidget(
enabledRoutes: {
'flow 1': ['welcome_screen', 'personal_info', 'product_selection_screen'],
'flow 2': ['checkout', 'payment'],
},
child: MaterialApp.router(
routerConfig: appRouter,
),
);
}
}
Configuration #
Custom Base URL #
You can set a custom base URL globally during initialization:
void main() {
embedInitialize(
'your-api-key',
embedUrl: 'https://your-custom-domain.com',
);
runApp(const MyApp());
}
Troubleshooting #
Common Issues #
-
Widget Not Visible
- Check
showEmbedWidgetparameter - Verify API key is valid
- Check network connectivity
- Check
-
Voice Call Issues
- Ensure microphone permission is granted
- Check device audio settings
-
Widget Tree Not Updating
- Check widget tree handler initialization
- Verify context is available
- Check for widget disposal issues
-
Flow-Based Activation Not Working
- Verify route names match exactly (case-sensitive)
- Check
EmbedNavigatorObserveris added to router observers - Ensure GoRouter is properly configured with named routes
- Verify
enabledRoutesmap contains correct flow names and route names - Ensure each flow has been initialized with
embedInitialize()andflowNameparameter - Check that API keys are saved correctly for each flow (check console logs)
- Verify the current route belongs to a configured flow
-
Button Click Detection Issues
- Ensure buttons have proper keys for identification
- Check if buttons are properly tracked in widget tree
- Verify hit testing permissions and context
- Test with different button types (ElevatedButton, TextButton, etc.)
-
EmbedWidget Not Appearing on Expected Screens
- Check
enabledRoutesconfiguration (flow-based map structure) - Check
disabledRoutesconfiguration - Verify route matching mode (
exactvsstartsWithvscontains) - Check if
showEmbedWidgetis set totrue - Verify the route belongs to a configured flow
- Check console logs for flow detection messages
- Check
-
Wrong API Key Being Used for a Flow
- Verify
embedInitialize()was called with the correctflowNamefor each flow - Check that API keys are saved correctly (check console logs for
LocalStorageHandler.saveAPIKey) - Ensure flow names in
enabledRoutesmatch theflowNameused inembedInitialize() - Check console logs for
EmbedConfigService.apiKeyto see which API key is being retrieved
- Verify
License #
This project is licensed under the Non-Commercial License License - see the LICENSE file for details.
Support #
- Documentation: https://docs.revrag.ai/embed/integration/flutter
- Email Support: contact@revrag.ai
- GitHub Issues: https://github.com/RevRag-ai/embed-react-native/issues
Changelog #
See CHANGELOG.md for a complete list of changes and version history.