Docs
Flutter Integration Guide
Add Heyo live chat to your Flutter iOS and Android apps using WebView.
This guide shows you how to add Heyo to your Flutter application for iOS and Android.
Prerequisites
Add webview_flutter to your pubspec.yaml:
dependencies:
flutter:
sdk: flutter
webview_flutter: ^4.4.0
Then run:
flutter pub get
Installation
Create a Chat Widget
Create lib/widgets/heyo_chat.dart:
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class HeyoChat extends StatefulWidget {
final String projectId;
final Map<String, dynamic>? user;
const HeyoChat({
Key? key,
required this.projectId,
this.user,
}) : super(key: key);
@override
State<HeyoChat> createState() => _HeyoChatState();
}
class _HeyoChatState extends State<HeyoChat> {
late final WebViewController controller;
@override
void initState() {
super.initState();
final html = '''
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<style>
body { margin: 0; padding: 0; }
#heyo-container { width: 100vw; height: 100vh; }
</style>
</head>
<body>
<div id="heyo-container"></div>
<script src="https://heyo.so/embed/script" defer data-project-id="${widget.projectId}"></script>
<script>
window.addEventListener('load', function() {
if (window.HEYO) {
${widget.user != null ? 'window.HEYO.init({ projectId: "${widget.projectId}", user: ${_encodeUser()} });' : ''}
window.HEYO.show();
}
});
</script>
</body>
</html>
''';
controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..loadRequest(Uri.dataFromString(html, mimeType: 'text/html'));
}
String _encodeUser() {
if (widget.user == null) return '{}';
return widget.user.toString().replaceAll("'", '"');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Support Chat'),
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.of(context).pop(),
),
),
body: WebViewWidget(controller: controller),
);
}
}
Use in Your App
import 'package:flutter/material.dart';
import 'widgets/heyo_chat.dart';
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('My App'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => HeyoChat(
projectId: 'YOUR_PROJECT_ID',
),
),
);
},
child: const Text('Open Support Chat'),
),
),
);
}
}
Advanced Usage
Pass User Data
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => HeyoChat(
projectId: 'YOUR_PROJECT_ID',
user: {
'name': 'John Doe',
'email': '[email protected]',
},
),
),
);
Floating Action Button
Create a reusable FAB for chat:
class ChatFAB extends StatelessWidget {
final String projectId;
final Map<String, dynamic>? user;
const ChatFAB({
Key? key,
required this.projectId,
this.user,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => HeyoChat(
projectId: projectId,
user: user,
),
),
);
},
backgroundColor: const Color(0xFF6366F1),
child: const Icon(Icons.chat_bubble_outline),
);
}
}
Usage:
Scaffold(
body: // Your content
floatingActionButton: ChatFAB(
projectId: 'YOUR_PROJECT_ID',
user: {
'name': currentUser.name,
'email': currentUser.email,
},
),
);
Custom Modal Sheet
Show chat as a bottom sheet:
void openChatSheet(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => DraggableScrollableSheet(
initialChildSize: 0.9,
minChildSize: 0.5,
maxChildSize: 0.95,
expand: false,
builder: (context, scrollController) {
return HeyoChat(projectId: 'YOUR_PROJECT_ID');
},
),
);
}
Match Material Theme
Customize the widget to match your Material theme:
class ThemedHeyoChat extends StatelessWidget {
final String projectId;
const ThemedHeyoChat({Key? key, required this.projectId}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
backgroundColor: theme.scaffoldBackgroundColor,
appBar: AppBar(
title: Text('Support', style: theme.textTheme.titleLarge),
backgroundColor: theme.primaryColor,
),
body: HeyoChat(projectId: projectId),
);
}
}
Handle Loading State
class _HeyoChatState extends State<HeyoChat> {
late final WebViewController controller;
bool isLoading = true;
@override
void initState() {
super.initState();
controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onPageFinished: (url) {
setState(() {
isLoading = false;
});
},
),
)
..loadRequest(Uri.dataFromString(html, mimeType: 'text/html'));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Support Chat')),
body: Stack(
children: [
WebViewWidget(controller: controller),
if (isLoading)
const Center(
child: CircularProgressIndicator(),
),
],
),
);
}
}
Platform-Specific Configuration
iOS
In ios/Runner/Info.plist, add:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Android
In android/app/src/main/AndroidManifest.xml, ensure internet permission:
<uses-permission android:name="android.permission.INTERNET" />
Best Practices
- Use full-screen modal or bottom sheet for chat
- Add loading indicators while WebView loads
- Pass user context for personalized support
- Test on both iOS and Android devices
- Handle back button navigation properly
- Match your app's Material Design theme
Troubleshooting
WebView not loading
- Ensure
setJavaScriptMode(JavaScriptMode.unrestricted)is set - Check internet permission on Android
- Verify App Transport Security on iOS
Chat not appearing
- Verify Project ID is correct
- Check network connectivity
- Ensure WebView has proper dimensions
iOS build issues
- Update
webview_flutterto latest version - Check Info.plist configuration
- Clean build folder and rebuild
Android build issues
- Verify internet permission in manifest
- Update minSdkVersion if needed (minimum 19)
- Check for ProGuard rules if using
Flutter Web
For Flutter web, use the standard JavaScript integration instead:
import 'dart:html' as html;
void initHeyoForWeb() {
final script = html.ScriptElement()
..src = 'https://heyo.so/embed/script'
..defer = true
..setAttribute('data-project-id', 'YOUR_PROJECT_ID');
html.document.head?.append(script);
}