ABCABC Tech Catalog
夏の長期インターン参加学生募集中!詳細はこちら

webview_flutter v4でのWebView描画コード

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/';

@override
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を準備します。

@override
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 で左側に _canGoBackTrue つまり戻れる対象のページがあるときのみ表示するようにしています。

押されたときにはWebViewController に対して goBack を呼び出すことで戻ります。

また、進むボタンは同様に

actions: _canGoForward
    ? [
        IconButton(
          icon: const Icon(
            Icons.arrow_forward,
          ),
          color: Colors.black,
          onPressed: () async {
            _controller.goForward();
          },
        ),
      ]
    : []),

とすることで右側にアクションボタンとして _canGoForwardTrue つまり進める対象のページがあるときのみ表示するようにしています。

押されたときには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を使えばマルチプラットフォーム展開も容易ですので、ぜひ検討してみてはいかがでしょうか。

AUTHOR

伴 拓也

朝日放送グループホールディングス株式会社 デジタル・アーキテック局 データ戦略チーム

アプリケーションからインフラ、ネットワーク、データエンジニアリングまで幅広い守備範囲が売り。最近はデータ基盤の構築まわりに力を入れて取り組む。 主な実績として、M-1グランプリ敗者復活戦投票システムのマルチクラウド化等。

WORK@ABC

技術力を培うための
環境と文化

ABCに昔から根付く「自分たちで開発する」文化を支える環境や取り組みをご紹介します
ABCについてもっと知る