パルカワ2

最近はFlutterをやっています

Flutter for Web で WebView を使う

native_webviewは、WebViewの機能をWebで提供するのは難しいのでWebには今の所対応していない。だが、対応していないプラットフォームでも自前で実装すれば一応動かせるようにはなっている。(というか0.28.0から動かせるようにした)

// Webのときのみsetする
WebView.platform = AppWebView();

HtmlElementView を使って AppWebView() をつくる。

import 'dart:html';
import 'dart:ui' as ui;

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:native_webview/native_webview.dart';
import 'package:native_webview/platform_interface.dart';

class AppWebView extends PlatformWebView {
  @override
  Widget build({
    @required BuildContext context,
    @required CreationParams creationParams,
    @required String viewType,
    @required PlatformViewCreatedCallback onPlatformViewCreated,
    @required Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
    bool useHybridComposition = true,
  }) {
    if (creationParams.widget.initialData.data.isNotEmpty) {
      return _DataWebView(data: creationParams.widget.initialData.data);
    }
    return _UrlWebView(url: creationParams.widget.initialUrl);
  }
}

class _UrlWebView extends StatefulWidget {
  final String url;

  const _UrlWebView({Key key, this.url}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _UrlWebViewState();
}

class _UrlWebViewState extends State<_UrlWebView> {
  @override
  void initState() {
    ui.platformViewRegistry.registerViewFactory(
      "${WebView.viewType}-iframe-${widget.url}",
      (int viewId) {
        // ignore: unsafe_html
        return IFrameElement()..src = widget.url;
      },
    );
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return HtmlElementView(
      viewType: "${WebView.viewType}-iframe-${widget.url}",
    );
  }
}

class _DataWebView extends StatefulWidget {
  final String data;

  const _DataWebView({Key key, this.data}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _DataWebViewState();
}

class _DataWebViewState extends State<_DataWebView> {
  @override
  void initState() {
    ui.platformViewRegistry.registerViewFactory(
      "${WebView.viewType}-data-${widget.data.hashCode}",
      (int viewId) {
        // ignore: unsafe_html
        return DivElement()..innerHtml = widget.data;
      },
    );
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return HtmlElementView(
      viewType: "${WebView.viewType}-data-${widget.data.hashCode}",
    );
  }
}

このように差し込める形にしておくと対応していないプラットフォームでもライブラリ利用者が対応しようと思えば出来るので便利。

ちなみに dart:ui の platformViewRegistry は mobile でビルドするとエラーになる。import の部分で if (dart.library.html) などを使って出し分けるとビルド時のエラーにはならない。

// app_webview.dart
import 'mobile_app_webview.dart' if (dart.library.html) "web_app_webview.dart";
// mobile_app_webview.dart
import 'package:flutter/src/foundation/basic_types.dart';
import 'package:flutter/src/gestures/recognizer.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:native_webview/platform_interface.dart';
import 'package:native_webview/src/webview.dart';

class AppWebView extends PlatformWebView {
  @override
  Widget build({
    BuildContext context,
    CreationParams creationParams,
    String viewType,
    onPlatformViewCreated,
    Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
    bool useHybridComposition = true,
  }) {
    throw UnimplementedError("not support");
  }
}

しかし、これだとIDEやLintではエラーが出っぱなしなのでignoreする必要がある。Dart 2.12では unsafe_htmlやundefined_prefixed_nameは行単位のignoreができないので、全体でignoreするかファイルをlintの対象外から外す必要がある。

Dart 2.13以降ではどうにか出来るようになりそうなので、それを待ちたい。

analyzer: Introduce cannot-ignore analysis option. · dart-lang/sdk@97cd131 · GitHub

またそもそもpackageに分ける方法もあるがめんどいのでやっていない。 ちなみに universal_ui | Flutter Package というのがあるがメンテナンスされていなさそう+リポジトリが消えているので使えるかは不明。

個人的にはunivarsalではなく dart:ui の web特有のものを切り出したpackageを作るのが一番筋がいい気がしている。誰か頼む。