WebView Integration

WebView is a crucial component in modern mobile applications that allows you to display web content within your app. This guide will walk you through integrating and using WebView in your Flutter application using the flutter_inappwebview package.

Setting Up WebView

First, add the flutter_inappwebview package to your pubspec.yaml:

dependencies:
  flutter_inappwebview: ^6.0.0
  url_launcher: ^6.2.5
  google_fonts: ^6.1.0
  lucide_icons: ^0.4.0

Creating a WebView Screen

Here’s a complete example of a WebView screen that includes:

  • Loading indicators
  • Navigation controls
  • External browser opening option
  • Swipe-to-go-back gesture
  • Error handling
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:url_launcher/url_launcher.dart';

class WebViewScreen extends StatefulWidget {
  final String url;
  final String title;
  final String sourceName;

  const WebViewScreen({
    super.key,
    required this.url,
    required this.title,
    required this.sourceName,
  });

  
  State<WebViewScreen> createState() => _WebViewScreenState();
}

class _WebViewScreenState extends State<WebViewScreen> {
  late final InAppWebViewController _controller;
  bool _isLoading = true;
  double _startX = 0.0;

  Future<void> _openInExternalBrowser() async {
    try {
      Uri url = Uri.parse(widget.url);
      if (!url.hasScheme) {
        url = Uri.parse('https://${widget.url}');
      }
      if (await canLaunchUrl(url)) {
        await launchUrl(url, mode: LaunchMode.externalApplication);
      } else {
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('Could not open URL in browser')),
          );
        }
      }
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Error opening URL in browser')),
        );
      }
    }
  }

  
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    return Scaffold(
      appBar: AppBar(
        backgroundColor: theme.colorScheme.surface,
        elevation: 1,
        leading: IconButton(
          icon: Icon(LucideIcons.chevronLeft, color: theme.colorScheme.onSurface),
          onPressed: () => Navigator.pop(context),
        ),
        title: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              widget.sourceName,
              style: GoogleFonts.inter(
                fontSize: 14,
                color: theme.colorScheme.primary,
                fontWeight: FontWeight.w500,
              ),
            ),
            Text(
              widget.title,
              style: GoogleFonts.inter(
                fontSize: 16,
                color: theme.colorScheme.onSurface,
                fontWeight: FontWeight.w600,
              ),
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
            ),
          ],
        ),
        actions: [
          IconButton(
            icon: Icon(LucideIcons.externalLink,
                color: theme.colorScheme.onSurface),
            onPressed: _openInExternalBrowser,
          ),
        ],
      ),
      body: Stack(
        children: [
          Listener(
            onPointerDown: (event) {
              _startX = event.position.dx;
            },
            onPointerUp: (event) {
              final endX = event.position.dx;
              final distance = endX - _startX;

              if (distance > 100) {
                Navigator.pop(context);
              }
            },
            child: InAppWebView(
              initialUrlRequest: URLRequest(url: WebUri(widget.url)),
              initialSettings: InAppWebViewSettings(
                javaScriptEnabled: true,
              ),
              onLoadStart: (controller, url) {
                setState(() {
                  _isLoading = true;
                });
              },
              onLoadStop: (controller, url) {
                setState(() {
                  _isLoading = false;
                });
              },
              onWebViewCreated: (InAppWebViewController controller) {
                _controller = controller;
              },
            ),
          ),
          if (_isLoading)
            LinearProgressIndicator(
              color: theme.colorScheme.primary,
              backgroundColor: theme.colorScheme.surface,
            ),
        ],
      ),
    );
  }
}

Key Features

1. Loading Indicator

The WebView includes a loading indicator that appears while the web content is being loaded:

if (_isLoading)
  LinearProgressIndicator(
    color: theme.colorScheme.primary,
    backgroundColor: theme.colorScheme.surface,
  ),

2. External Browser Support

Users can open the current page in their default browser:

Future<void> _openInExternalBrowser() async {
  try {
    Uri url = Uri.parse(widget.url);
    if (!url.hasScheme) {
      url = Uri.parse('https://${widget.url}');
    }
    if (await canLaunchUrl(url)) {
      await launchUrl(url, mode: LaunchMode.externalApplication);
    }
    // ... error handling
  }
}

3. Swipe Navigation

The WebView supports swipe-to-go-back gesture:

Listener(
  onPointerDown: (event) => _startX = event.position.dx,
  onPointerUp: (event) {
    final endX = event.position.dx;
    final distance = endX - _startX;
    if (distance > 100) {
      Navigator.pop(context);
    }
  },
  // ... WebView widget
)

Usage

To use the WebView in your application:

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => WebViewScreen(
      url: 'https://example.com',
      title: 'Page Title',
      sourceName: 'Source Name',
    ),
  ),
);

Best Practices

  1. Error Handling: Always implement proper error handling for URL loading and external browser opening.
  2. Loading States: Show loading indicators to improve user experience.
  3. JavaScript: Enable JavaScript only if needed for your use case.
  4. Navigation: Provide clear navigation options (back button, external browser).
  5. Performance: Consider memory usage when implementing WebViews.

Platform-Specific Considerations

Android

Add these permissions to your android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET"/>

iOS

Update your ios/Runner/Info.plist:

<key>io.flutter.embedded_views_preview</key>
<true/>

For iOS 9+:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>