webview_flutter v4でのWebView描画
FlutterでのWebView実装
以前の記事で、このTechブログを表示するだけのWebViewによる簡易アプリを作りました。
その途中で少し詰まった要因が、WebViewのために使用しているパッケージ「webview_flutter」についてバージョン4がリリースされており、バージョン3以前とコードの記述が大きく変わっていることでした。しかも 破壊的な変更 が行われています。聞きたくない言葉ですね。
本記事では、webview_flutter バージョン4でWebViewを描画するためにどのようなコードを書けば良いかについてまとめていきます。
WebView描画コード
webview_flutter v4では、例えば下記のような形でStateを継承したWebViewControllerを構成します。
late final WebViewController _controller; bool _isLoading = false; bool _canGoBack = false; bool _canGoForward = false; String _title = ''; static const homeUrl = 'https://tech.asahi.co.jp/'; void initState() { super.initState(); _controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setBackgroundColor(const Color(0x00000000)) ..setUserAgent("abc-dx-application") ..setNavigationDelegate( NavigationDelegate( onPageStarted: (String url) { setState(() { _isLoading = true; }); }, onPageFinished: (String url) async { final title = await _controller.getTitle(); final canGoBack = await _controller.canGoBack(); final canGoForward = await _controller.canGoForward(); setState(() { _isLoading = false; _canGoBack = canGoBack; _canGoForward = canGoForward; if (title != null) { _title = title.replaceFirst(' | ABC DX Tech Blog', ''); } }); }, onWebResourceError: (WebResourceError error) {}, onNavigationRequest: (NavigationRequest request) async { if (!request.url.startsWith(homeUrl)) { final Uri url = Uri.parse(request.url); final LaunchMode launchMode = (Platform.isAndroid ? LaunchMode.externalApplication : LaunchMode.platformDefault); if (!await launchUrl(url, mode: launchMode)) { throw Exception('Could not launch $url'); } return NavigationDecision.prevent; } else { return NavigationDecision.navigate; } }, ), ) ..loadRequest(Uri.parse(homeUrl)); }
このように、 WebViewController
をカスタマイズしていく必要があるのがv4の特徴です。
v3までは WebView
ウィジェットの設定値として JavaScriptMode
等の指定ができていましたが、 WebView
ウィジェットが廃止され、 controller
を引き数として取る WebViewWidget
を使うように変更されています。
加えてここではいくつか細かい設定を入れています ので、それを見ていければと思います。
UserAgentの設定
WebViewControllerに対して
..setUserAgent("abc-dx-application")
として、 abc-dx-application
というUserAgentでアクセスするよう設定しています。
こうすることで、WebViewでのアクセス時はWebアプリ画面側のヘッダ/フッタを外すなどの設定をWebアプリに仕込んでおくことができます。(よりアプリ内描画に近い形でページを表示することができる)
実際にNext.js でどのようにUserAgentに応じた画面変更を行うかはまた別記事で触れられればと思います。
url_launcherのLaunchModeの設定
また、外部リンクを開く際には url_launcherパッケージを使用しています。
request.url.startsWith(homeUrl)
で、ドメイン外のページと判定された場合、onNavigationRequest
内で下記のように LaunchModeを指定して開いています。
final LaunchMode launchMode = (Platform.isAndroid ? LaunchMode.externalApplication : LaunchMode.platformDefault);
Androidの場合は LaunchMode.externalApplication
で外部アプリで開くように指定し、それ以外は LaunchMode.latformDefault
としています。
それぞれのLaunchModeがどのような開き方を意味するかについては launchUrl functionのドキュメントに記述があります。
Androidだけ別にしているのは、単純に操作性の観点からそのようにしていますが、ドキュメントには externalApplication
は iOSにおいてはユーザのブラウザのCookie等のcontext活用の際に使えると記述がありますね。確かにシングルサインオン等の文脈ではそのほうが良さそうです。
戻る・進むボタン等AppBarの実装
上記のような形でStateを準備した上で、戻る・進むボタンを実装するために下記のようにWidgetを準備します。
Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text( _title, style: const TextStyle( fontSize: 16.0, ), ), centerTitle: true, leading: _canGoBack ? (IconButton( icon: const Icon( Icons.arrow_back, ), color: Colors.black, onPressed: () async { _controller.goBack(); }, )) : null, actions: _canGoForward ? [ IconButton( icon: const Icon( Icons.arrow_forward, ), color: Colors.black, onPressed: () async { _controller.goForward(); }, ), ] : []), body: Column( children: [ if (_isLoading) const LinearProgressIndicator(), Expanded(child: WebViewWidget(controller: _controller)) ], ), ); }
body内に注目すると、先述の通り、v3までは WebView
コンポーネントだったのが、v4からは WebViewWidget
コンポーネントとなっています。
ページタイトルを
title: Text( _title, style: const TextStyle( fontSize: 16.0, ), ), centerTitle: true,
のようにしてAppBar中央に表示した上で、
戻るボタンは
leading: _canGoBack ? (IconButton( icon: const Icon( Icons.arrow_back, ), color: Colors.black, onPressed: () async { _controller.goBack(); }, )) : null,
のようにして、 leading
で左側に _canGoBack
が True
つまり戻れる対象のページがあるときのみ表示するようにしています。
押されたときにはWebViewController
に対して goBack
を呼び出すことで戻ります。
また、進むボタンは同様に
actions: _canGoForward ? [ IconButton( icon: const Icon( Icons.arrow_forward, ), color: Colors.black, onPressed: () async { _controller.goForward(); }, ), ] : []),
とすることで右側にアクションボタンとして _canGoForward
が True
つまり進める対象のページがあるときのみ表示するようにしています。
押されたときにはWebViewController
に対して goForward
を呼び出すことで進むことが出来ます。
加えて、下記のようにロード中であることを示すために LinearProgressIndicator
を _isLoading
の値に応じて表示しています。
body: Column( children: [ if (_isLoading) const LinearProgressIndicator(), Expanded(child: WebViewWidget(controller: _controller)) ], ),
このようにするだけで、戻る・進むの機能とロード中表示・タイトル表示まで行えると思うと めちゃくちゃ簡単 ですよね。
まとめ
本記事では、FlutterでWebView描画するために活用できるパッケージwebview_flutterのv4でのコードについて見ていきました。
Webアプリの技術スタックも進化を続けている ので、以前の記事にも書きましたが、簡易的なアプリであれば WebViewを活用したローコスト開発のアプリで問題ないシチュエーションが増えている のではないかと思います。
FlutterあるいはReact Nativeを使えばマルチプラットフォーム展開も容易ですので、ぜひ検討してみてはいかがでしょうか。