webview_flutterを使ってFlutterアプリ内でWebページを開く

公開日:2022/08/15 更新日:2022/08/15
webview_flutterを使ってFlutterアプリ内でWebページを開くのサムネイル

はじめに

FlutterでURLをタップした時にアプリ内でそのURLのWebページを開く方法についてまとめます。この記事では、Flutter公式のプラグインであるwebview_flutterを使用した実装例をメモします。

できるようになること

以下のようにボタンをタップすると指定したURLのWebページをFlutterアプリ内で開きます。

showing-webpage-in-flutter-app.gif

なお、もしFlutterアプリ内でリンクをクリックしたらアプリ外でブラウザを開いてそのWebページを表示したい場合については以下にまとめました。

virment.com

FlutterでURLをタップしたらブラウザが開いてそこでURL先を表示したい場合があります。この記事では、Flutter公式のプラグインを使用した実装例をメモします。

前提と環境

以降に記載するコードを実行した環境は以下になります。

  • iOS 15.5
  • Xcode 13.4.1
  • Flutter 3.0.5 (Dart 2.17.6)

必要なパッケージ

必要なパッケージは以下になります。

pubspec.yaml
dependencies:
  webview_flutter: ^3.0.4
pub.dev

A Flutter plugin that provides a WebView widget.

Webページを読み込むウィジェットのコード

冒頭に載せたデモのコードでは、Webページを読み込む部分をウィジェット化し、それを必要な箇所で使っています。 まず先にWebページを読み込むウィジェットのコードを以下に記載します。

webview_widget.dart
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:webview_flutter/webview_flutter.dart';

class WebViewWidget extends StatefulWidget {
  const WebViewWidget({
    Key? key,
    this.title,
    required this.url,
  }) : super(key: key);

  final String? title;
  final String url;

  
  _WebViewWidgetState createState() => _WebViewWidgetState();
}

class _WebViewWidgetState extends State<WebViewWidget> {
  final Completer<WebViewController> _controller =
      Completer<WebViewController>();

  bool _isLoading = false; // ページ読み込み状態
  double _downloadProgress = 0.0; // ページ読み込みの進捗値
  String _title = '';
  String _url = '';

  
  void initState() {
    super.initState();
    _title = widget.title ?? '';
    _url = widget.url;

    if (Platform.isAndroid) {
      WebView.platform = SurfaceAndroidWebView();
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(_title),
      ),
      body: Column(
        children: [
          _isLoading
              ? LinearProgressIndicator(value: _downloadProgress)
              : const SizedBox.shrink(),
          Expanded(
            child: WebView(
              // 表示する初期URLの指定
              initialUrl: _url,
              // javascriptを有効化. disabled で無効化
              javascriptMode: JavascriptMode.unrestricted,
              onWebViewCreated: (WebViewController webViewController) {
                _controller.complete(webViewController);
              },
              // ページ読み込み中の処理
              onProgress: (int progress) {
                print('Webサイトを読み込み中です... (進捗 : $progress%)');
                setState(() {
                  _downloadProgress = (progress / 100);
                });
              },
              // ページへの遷移処理開始時の処理
              navigationDelegate: (NavigationRequest request) {
                // 特定URLへのアクセスは拒否する
                if (request.url.startsWith('https://github.com/')) {
                  print('blocking navigation to $request}');
                  return NavigationDecision.prevent;
                }
                print('allowing navigation to $request');
                return NavigationDecision.navigate;
              },
              // ページの読み込み開始時の処理
              onPageStarted: (String url) {
                print('Page started loading: $url');
                setState(() {
                  _isLoading = true;
                });
              },
              // ページの読み込み完了時の処理
              onPageFinished: (String url) async {
                print('Page finished loading: $url');
                setState(() {
                  _isLoading = false;
                });
                // Webページのタイトルを取得
                final controller = await _controller.future;
                final title = await controller.getTitle();
                setState(() {
                  if (title != null) {
                    _title = title;
                  }
                });
              },
              // 水平方向のスワイプ時に戻る、進むを機能させるかのフラグ。iOSでのみ機能する。
              gestureNavigationEnabled: true,
              // 背景色
              backgroundColor: const Color(0x00000000),
              // ページ読み込み終了
            ),
          ),
        ],
      ),
    );
  }
}

今回使用しているFlutter公式のプラグインであるwebview_flutterでは、navigationDelegateにページ遷移する時に遷移先のURLに関連して何かしらの処理を記述できます。例えば、以下のコードでは、https://github.com/から始まるURLへのアクセスを拒否します。https://github.com/から始まるURLへのリンクをクリックしても、アクセス拒否の対象であるためページ遷移せず何も起きません。 実際には、特定のURLへのリンクをクリックしたら何かアラートメッセージを出したり、本当に移動するかの確認ダイアログを出したりすると思います。他にもいろいろな使い方ができると思います。

// ページへの遷移処理開始時の処理
  navigationDelegate: (NavigationRequest request) {
    print('遷移先 : ${request}');
    // 特定URLへのアクセスは拒否する
    if (request.url.startsWith('https://github.com/')) {
      print('$requestへのアクセスをブロック');
      return NavigationDecision.prevent;
    }
    print('$requestへのアクセスを許可');
    return NavigationDecision.navigate;
  },

上記のNavigationRequest requestは、url (string)isForMainFrame (bool)の値を持ちます。urlは遷移しようとしているURLの文字列です。isForMainFrameは遷移しようとしているURLがメインフレームであるか、それ以外かの真偽値になります。ここでいうメインフレームとは、WebViewが開いた最初のURL、すなわち上記のコードだとhttps://flutter.dev/をURLに含むWebページになります。https://flutter.dev内にあるリンクの内、https://flutter.devからはじまるURL以外のWebページへの遷移は全てメインフレームではなく、isForMainFramefalseになります。

なお、Webページの中で読み込まれるiFrameやlink要素についてもnavigationDelegateは実行されます。そしてこれらのNavigationRequest requestisForMainFramefalseになります。

例えば、以下のようなコードにすると、メインフレーム(上記のデモコードだとhttps://flutter.dev/を持つURL)以外へのアクセスは一切できなくなります。

// ページへの遷移処理開始時の処理
  navigationDelegate: (NavigationRequest request) {
    print('遷移先 : ${request}');
    // メインフレームでない場合は遷アクセス拒否
    if (!request.isForMainFrame) {
      return NavigationDecision.prevent;
    }
  },

これは実際にNavigationRequest requestisForMainFrameの値を見て動作確認すると理解しやすいと思います。

Webページを読み込むウィジェットを使う部分のコード

以下が実際に上記のWebページを読み込むウィジェットを使う部分になります。読み込みたいページのURLとタイトルを引数として渡して使用します。

main.dart
import 'package:flutter/material.dart';
import 'package:url_launcher/link.dart';

...(省略)...

TextButton(
  onPressed: () {
    Navigator.of(context).push(MaterialPageRoute(
      builder: (BuildContext context) => WebViewWidget(
        title: "Flutter",
        url: "https://flutter.dev",
      )
    ));
  },
  child: const Text(
    'Webサイト表示',
    style: TextStyle(fontSize: 12),
  ),
  style: ButtonStyle(
    padding: MaterialStateProperty.all(EdgeInsets.zero),
    tapTargetSize: MaterialTapTargetSize.shrinkWrap,
    ),
),

...(省略)...

まとめ

Flutterアプリ内でWebページを開くための実装についてまとめました。

関連記事

開発アプリ

nanolog.app

毎日の小さな出来事をなんでも記録して、ログとして残すためのライフログアプリです。