方言を話すおしゃべり猫型ロボット『ミーア』をリリースしました(こちらをクリック)

【Flutter】Androidでweb viewでGoogleフォーム開いた時のJavaScript無効エラー対応法

android-web-view-javascript
この記事は約10分で読めます。

はじめに

様々な方言を話す、おしゃべり猫型ロボット「ミーア」を開発中。

https://mia-cat.com

先日、こちらで、「アプリに、不具合・エラー問い合わせのGoogleフォームを設置」に関する記事を記載した。

ところが、ご利用のユーザーから下記お問い合わせがきた。

「アプリから不具合・エラーレポートを開こうとしたところ、下記エラーが表示されました。Chromeブラウザのjavascript設定は有効にしているのですが」

今回は、上記対処法について記載する。

webview_flutterプラグインのデフォルト設定はJacaScript無効

今回、webビュー表示は、webview_flutterプラグインを使用している。

https://pub.dev/packages/webview_flutter

webview_flutterプラグインでのデフォルト設定は、JavaScriptMode.disabled。つまり、デフォルトではJavaScriptが無効化されている。

Dart
  /// Sets the JavaScript execution mode to be used by the WebView.
  Future<void> setJavaScriptMode(JavaScriptMode javaScriptMode) {
    return platform.setJavaScriptMode(javaScriptMode);
  }

Androidの場合、アプリケーション内のWebViewでJavaScriptを有効にするためには、コードレベルで明示的にJavaScriptMode.unrestrictedを設定する必要がある。

具体的には、WebViewControllerの初期化時にJavaScriptを有効にする必要がある。

Dart
_controller = WebViewController()
  ..setJavaScriptMode(JavaScriptMode.unrestricted)
  ..loadRequest(Uri.parse('https://docs.google.com/forms/d/e/XXXX/viewform'));

この設定により、WebViewがJavaScriptを実行できるようになり、JavaScriptを必要とするウェブページの正しい動作が保証される。

iOSとAndroidのJavascriptの設定の違い

iOSのWebView (WKWebView) では、デフォルトでJavaScriptが有効になっている。したがって、特に設定を変更しなくても、JavaScriptを使用するウェブサイト(例: Googleフォームなど)を問題なく表示することができる。

AndroidのWebViewはセキュリティ上の理由からJavaScriptを無効にしているため、アプリ内でWebViewを使用する場合、JavaScriptが必要であれば、コードレベルで明示的にJavaScriptMode.unrestrictedを設定して有効にする必要がある。

アプリケーション内のWebViewは、Android端末でのシステムのブラウザ設定とは独立している。つまり、端末のシステムブラウザ(Google Chromeなど)の設定でJavaScriptを有効にしても、アプリ内のWebViewでJavaScriptが有効になるわけではない。

Googleフォームのようなウェブページは、インタラクティブな要素や動的なコンテンツを表示するためにJavaScriptを多用している。このため、JavaScriptが無効な場合、正しく表示されないことがある。

修正したコード(Googleフォームwebview)

Dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class ErrorReportScreen extends StatefulWidget {
  const ErrorReportScreen({super.key});

  @override
  _ErrorReportScreenState createState() => _ErrorReportScreenState();
}

class _ErrorReportScreenState extends State<ErrorReportScreen> {
  late WebViewController _controller;

  @override
  void initState() {
    super.initState();
    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..loadRequest(Uri.parse(
          'https://docs.google.com/forms/d/e/XXXX/viewform'));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('ミーア不具合・エラー報告'),
        actions: <Widget>[
          IconButton(
            icon: const Icon(Icons.close),
            onPressed: () {
              Navigator.of(context).pop();
            },
          ),
        ],
      ),
      body: Column(
        children: [
          Container(
            padding: const EdgeInsets.all(16.0),
            color: Colors.yellow[100],
            child: const Text(
              'エラーや不具合の画像や動画をアップロードしていただくために、Googleアカウントでログインしてください。',
              style: TextStyle(fontSize: 16.0),
            ),
          ),
          Expanded(
            child: WebViewWidget(controller: _controller),
          ),
        ],
      ),
    );
  }
}

NavigationDelegateの設定

今回、アプリからgoogle formを直接webviewで開くだけなので、ユーザーが自分で他のURLにアクセスすることはできないようになっている。

ただし、予期せぬリダイレクトや外部リンクのクリックによるセキュリティリスクをさらに減らすために、NavigationDelegateを使用して、docs.google.comからのリクエストのみを許可するようにしておく。

Dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class ErrorReportScreen extends StatefulWidget {
  const ErrorReportScreen({super.key});

  @override
  _ErrorReportScreenState createState() => _ErrorReportScreenState();
}

class _ErrorReportScreenState extends State<ErrorReportScreen> {
  late WebViewController _controller;

  @override
  void initState() {
    super.initState();
    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..loadRequest(Uri.parse(
          'https://docs.google.com/forms/d/e/XXXX/viewform'))
      ..setNavigationDelegate(NavigationDelegate(
        onNavigationRequest: (NavigationRequest request) {
          // googleドメインのみ許可
          if (request.url.startsWith('https://docs.google.com/')) {
            return NavigationDecision.navigate;
          }
          return NavigationDecision.prevent;
        },
      ));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('ミーア不具合・エラー報告'),
        actions: <Widget>[
          IconButton(
            icon: const Icon(Icons.close),
            onPressed: () {
              Navigator.of(context).pop();
            },
          ),
        ],
      ),
      body: Column(
        children: [
          Container(
            padding: const EdgeInsets.all(16.0),
            color: Colors.yellow[100],
            child: const Text(
              'エラーや不具合の画像や動画をアップロードしていただくために、Googleアカウントでログインしてください。',
              style: TextStyle(fontSize: 16.0),
            ),
          ),
          Expanded(
            child: WebViewWidget(controller: _controller),
          ),
        ],
      ),
    );
  }
}

動作確認

上記設定変更で、無事Android実機でもGoogleフォームが表示されるようになった。

今まで自分のiPhone実機と、たまにAndroidエミュレーターでのみしか動作確認していなかったが、Android実機でも必ず確認する必要があると良い教訓になった。

FlutterアプリをAndroid実機で実行するセットアップに関する記事はこちら。

タイトルとURLをコピーしました