Feature SDKs
How to add a WebView to a Flutter app
Free Reference for webview_sdk: every variable, option and package it generates. Command: archipelago generate webview_sdk
Generate the WebView SDK, set platform permissions, embed the widget and its callbacks, intercept custom URI schemes, and call JavaScript from Dart.
What you'll learn
- Picking
webview_sdkoverwebview_sdk_advanced(they're independent, pick one) - Generating the self-contained WebView SDK
- Configuring platform permissions for Android and iOS
- Embedding the WebView widget and wiring up its callbacks
- Intercepting custom URI schemes for in-app navigation
- Making one-way calls from Dart into the page's JavaScript context
Prerequisites
- An existing Archipelago monorepo (see Monorepo Scaffolding)
Step 1: Pick a Package, Basic or Advanced
Archipelago ships two independent WebView bricks. Install the one that matches your needs, not both:
| Brick | Tier | Use it when... |
|---|---|---|
webview_sdk (this tutorial) | Free | You need URL loading, navigation controls, progress tracking, one-way JS calls, and URI scheme interception |
webview_sdk_advanced | Pro | You additionally need a bidirectional JS bridge, request interceptors, or the mini-app container |
webview_sdk_advanced does not require webview_sdk to be generated first, and does not extend or build on it. It is not an upgrade path. Installing both in the same app is not a supported layering model.
Step 2: Generate the WebView SDK
archipelago generate webview_sdkThis brick has no configuration variables. Generation is non-interactive. There is no prompt to answer and no config file to pass.
Step 3: Understand the Generated Structure
webview_sdk generates one self-contained package. The contract and the flutter_inappwebview implementation sit together, with no API/Impl split:
features/webview/
└── webview/
└── lib/src/
├── webview_sdk.dart # WebViewSDK contract
├── webview_sdk_impl.dart # flutter_inappwebview-backed impl
├── webview_controller.dart # WebViewController interface
├── webview_inappwebview_controller.dart # Concrete controller
├── di/
│ └── webview_global_module.dart # WebViewGlobalModule
└── models/
├── webview_config.dart # WebViewConfig value class
├── webview_intercepted_url.dart # URI intercept payload
├── webview_load_progress.dart
├── webview_load_state.dart
└── webview_navigation_event.dartThe generator also registers WebViewGlobalModule in lib/di/injector.dart and adds the webview path dependency to your app pubspec.yaml. You write no registration code. WebViewSDK is a lazy singleton, so nothing runs at boot.
Step 4: Configure Platform Permissions
Android
Apps generated by Archipelago already declare the Internet permission in android/app/src/main/AndroidManifest.xml. For an older app, add it inside the <manifest> tag:
<uses-permission android:name="android.permission.INTERNET" />iOS
If you need to load plain HTTP URLs, add App Transport Security to ios/Runner/Info.plist:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>HTTPS-only apps skip this block.
Step 5: Embed the WebView Widget
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:webview/webview.dart';
class TradePage extends StatefulWidget {
const TradePage({required this.webViewSDK, super.key});
final WebViewSDK webViewSDK;
@override
State<TradePage> createState() => _TradePageState();
}
class _TradePageState extends State<TradePage> {
late final WebViewInAppWebViewController _controller;
final _config = const WebViewConfig(
initialUrl: 'https://example.com/trade',
interceptedSchemes: ['app', 'tg'],
);
@override
void initState() {
super.initState();
// widget.webViewSDK is passed in by the route, not read from GetIt
_controller = widget.webViewSDK.createController(_config)
as WebViewInAppWebViewController;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: InAppWebView(
initialUrlRequest: URLRequest(url: WebUri(_config.initialUrl)),
onWebViewCreated: (c) => _controller.attachNativeController(c),
shouldOverrideUrlLoading: _controller.shouldOverrideUrlLoading,
onProgressChanged: _controller.onProgressChanged,
onLoadStart: _controller.onLoadStart,
onLoadStop: _controller.onLoadStop,
onReceivedError: _controller.onReceivedError,
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}WebViewSDK.isAvailable returns false on platforms flutter_inappwebview does not support, such as web and desktop. createController returns null in that case. Check it before casting if your app targets those platforms.
Step 6: Intercept Custom URI Schemes
interceptStream is a Stream<WebViewInterceptedUrl> that fires whenever the WebView navigates to a scheme listed in WebViewConfig.interceptedSchemes. No corresponding navigationStream event fires for an intercepted URL. The platform layer cancels the navigation itself.
@override
void initState() {
super.initState();
// ... controller setup ...
_controller.interceptStream.listen((event) {
// event.url — the full Uri that was intercepted
// event.scheme — e.g. 'app' or 'tg'
final productId = event.url.pathSegments.last;
Navigator.pushNamed(context, '/trade/$productId');
});
}The web page triggers an intercept by navigating to a custom URL:
<a href="app://trade/BTC-IDR">Open in app</a>Step 7: Call JavaScript from Dart
For one-way, fire-and-forget calls from Dart into the page's JavaScript context:
await _controller.runJavaScript('window.showBanner("Order filled!")');Need a response back from JavaScript, or bidirectional messaging? That requires the Pro-tier
webview_sdk_advancedbrick, which adds a fullwindow.archipelago.*JS bridge with Promise-based responses. See WebView SDK Advanced.
Common Customizations
| Customization | Where to Change |
|---|---|
| Change initial URL at runtime | Pass a different initialUrl in WebViewConfig |
| Add more intercepted schemes | Extend interceptedSchemes list in WebViewConfig |
| Restrict which schemes can load at all | Override allowedSchemes (defaults to ['http', 'https']) |
| Show a loading indicator | Listen to progressStream (WebViewLoadProgress, 0.0 → 1.0) |
| Handle navigation errors | Listen to progressStream for a WebViewLoadState.error state |
| Clear cookies/local storage | Call controller.clearCookies() |
Next Steps
- Add a JS bridge, request interceptors, and mini-app hosting with the independent Pro-tier WebView SDK Advanced brick
- Set up the Auth SDK to pass auth tokens into your WebView
- Configure monitoring to track WebView load failures