WebView Flutter
LiveCaller Chat Widget Integration for Flutter
This guide explains how to integrate the LiveCaller Chat Widget into a Flutter app using webview_flutter
.
1. Prerequisites
Flutter SDK (3.x recommended)
webview_flutter
package installed
Install dependency in pubspec.yaml
:
dependencies:
flutter:
sdk: flutter
webview_flutter: ^4.4.2
Run:
flutter pub get
2. Main Entry Point
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'LiveCaller Demo',
theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
home: const HomeScreen(),
);
}
}
3. Home Screen
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Home")),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const ChatScreen()),
);
},
child: const Text("Open Chat"),
),
),
);
}
}
4. Chat Screen with WebView
class ChatScreen extends StatefulWidget {
const ChatScreen({super.key});
@override
State<ChatScreen> createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
late final WebViewController _controller;
bool _isLoading = true;
String? _error;
@override
void initState() {
super.initState();
_initializeWebView();
}
void _initializeWebView() {
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..enableZoom(false)
..setBackgroundColor(const Color(0x00FFFFFF))
..setNavigationDelegate(
NavigationDelegate(
onPageStarted: (String url) {
setState(() {
_isLoading = true;
_error = null;
});
},
onPageFinished: (String url) {
setState(() {
_isLoading = false;
});
},
onWebResourceError: (WebResourceError error) {
setState(() {
_isLoading = false;
_error = 'Failed to load chat: \${error.description}';
});
},
onNavigationRequest: (NavigationRequest request) {
if (request.url.startsWith('https://cdn.livecaller.io') ||
request.url.startsWith('https://livecaller.io') ||
request.url.startsWith('data:')) {
return NavigationDecision.navigate;
}
return NavigationDecision.prevent;
},
),
)
..loadHtmlString(_chatHtml, baseUrl: 'https://cdn.livecaller.io');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("LiveCaller Chat"),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
),
body: SafeArea(
child: Stack(
children: [
if (_error != null)
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, size: 64, color: Colors.red[300]),
const SizedBox(height: 16),
Text(_error!, textAlign: TextAlign.center),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _initializeWebView,
child: const Text('Retry'),
),
],
),
)
else
WebViewWidget(controller: _controller),
if (_isLoading)
Container(
color: Colors.white,
child: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Loading chat...'),
],
),
),
),
],
),
),
);
}
}
5. LiveCaller HTML Widget
const String _chatHtml = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>LiveCaller Chat</title>
<style>
html, body { margin: 0; padding: 0; height: 100%; width: 100%; overflow: hidden; background: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif; }
#live-caller-widget { position: fixed; top: 0; left: 0; right: 0; bottom: 0; width: 100%; height: 100%; }
.loading { display: flex; align-items: center; justify-content: center; height: 100vh; flex-direction: column; color: #666; }
.spinner { border: 3px solid #f3f3f3; border-top: 3px solid #2196F3; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin-bottom: 16px; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
</style>
</head>
<body>
<div id="live-caller-widget">
<div class="loading">
<div class="spinner"></div>
<p>Initializing LiveCaller...</p>
</div>
</div>
<script>
console.log('Starting LiveCaller initialization...');
(function (w, t, c, p, s, l) {
p = new Promise(function (resolve, reject) {
w[c] = { client: () => p };
l = document.getElementById('live-caller-widget');
s = document.createElement(t);
s.async = true;
s.setAttribute('data-livecaller', 'script');
s.src = 'https://cdn.livecaller.io/js/app.js';
s.onload = function() { resolve(w[c]); };
s.onerror = function(error) { l.innerHTML = '<div class="loading"><p>Failed to load chat.</p></div>'; reject(error); };
(document.head || document.body).appendChild(s);
});
return p;
})(window, 'script', 'LiveCaller')
.then(function () {
LiveCaller.config.merge({
widget: { id: 'your widget id here' },
app: { locale: 'en' },
});
LiveCaller.liftOff();
setTimeout(function() { LiveCaller.$emit('ui.widget.open'); }, 500);
})
.catch(function(error) {
document.getElementById('live-caller-widget').innerHTML = '<div class="loading"><p>Chat initialization failed. Please try again.</p></div>';
});
</script>
</body>
</html>
''';
Last updated