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