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_flutter to 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);
}

Next Steps