Linuxで動作するFlutterのWebViewプラグインを利用したアプリ開発


# はじめに
FlutterでWebViewを利用するためのプラグインとしてwebview_flutterが存在する。しかし公式のサポートプラットフォームはiOSとAndroidのみとなっている。公式のWebViewプラグインと同じAPIをサポートするLinux版のWebViewを提供するプラグインが公開されたので、そのプラグインの動作を確認する。

筆者の環境の都合上M1 Macでの開発環境構築となっている。

https://pub.dev/packages/flutter_linux_webview

# Flutterの開発環境を用意する

Linux向けのFlutter開発環境を用意する。

## M1Mac向けにUbuntu22.04を用意する
Armチップを搭載しているMacBookでLinux環境を実行する仮想環境を用意するならUTMが利用できる。いわゆるM1 MacでLinux環境を用意する場合には下記のサイトが参考になるだろう。

https://qiita.com/rairaii/items/1e74d779c4e54d5daedc

## Flutterをインストールする
公式でサポートされている環境であればパッケージを利用してflutterをインストールするのが楽で良い。

:::note alert
Ubuntu22.04のsnap版のflutterは利用不可
flutter_linux_webviewを利用しようとするとライブラリの依存関係の問題でリンクに失敗してしまう。Ubuntu22.04を利用している場合にはflutterはマニュアルでインストールして利用する。
:::

最新情報は公式サイトを参照すること

https://docs.flutter.dev/get-started/install/linux

### Flutterをマニュアルインストール

[Linux用SDKパッケージ](https://docs.flutter.dev/release/archive?tab=linux)のアーカイブがFlutterの公式サイトで配布されている。残念ながらLinux版はx86版のアーカイブしか公開されていないため、Arm64版のLinux向けのFlutterはGitHubからインストールする。
```terminal: $HOME/flutter にSDKをインストールしてPATHを通す
$ cd
$ git clone git@github.com:flutter/flutter.git
$ export PATH="$PATH:$HOME/flutter/bin"
$ which flutter dart
/home/raiko/flutter/bin/flutter
/home/raiko/flutter/bin/dart
$ flutter --version
Flutter 3.20.0-1.0.pre.18 \u2022 channel master \u2022 https://github.com/flutter/flutter
Framework \u2022 revision da375a7796 (6 hours ago) \u2022 2024-02-04 00:09:20 -0500
Engine \u2022 revision f34c658b96
Tools \u2022 Dart 3.4.0 (build 3.4.0-99.0.dev) \u2022 DevTools 2.31.0
```

flutter doctor コマンドでFlutterの環境の確認する。
Android(スマホ用)とChrome(Web用)の環境がないが、Linux環境はインストールされている。AndroidやWeb向け開発環境は今回は必要ないので無視して進める。
```terminal
$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[\u2713] Flutter (Channel master, 3.20.0-1.0.pre.18, on Ubuntu 22.04.3 LTS 5.15.0-92-generic, locale en_US.UTF-8)
[\u2717] Android toolchain - develop for Android devices
    \u2717 Unable to locate Android SDK.
      Install Android Studio from: https://developer.android.com/studio/index.html
      On first launch it will assist you in installing the Android SDK components.
      (or visit https://flutter.dev/docs/get-started/install/linux#android-setup for detailed instructions).
      If the Android SDK has been installed to a custom location, please use
      `flutter config --android-sdk` to update to that location.

[\u2717] Chrome - develop for the web (Cannot find Chrome executable at google-chrome)
    ! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.
[\u2713] Linux toolchain - develop for Linux desktop
[!] Android Studio (not installed)
[\u2713] VS Code (version 1.86.0)
[\u2713] Connected device (1 available)
[\u2713] Network resources

! Doctor found issues in 3 categories.
```

### Linuxのデスクトップアプリを開発する環境を整える
FlutterでLinux向けデスクトップアプリケーションを作成するために必要になるツールとライブラリ一式をインストールする。

```terminal
$ sudo apt-get update
$ sudo apt-get install clang cmake git ninja-build pkg-config libgtk-3-dev liblzma-dev libstdc++-12-dev
$ flutter config --enable-linux-desktop
```



## Flutterの開発環境を確認する

FlutterのSDKのインストールが終了した後は開発環境を整える。Flutterは数多くの機能をサポートしており、１つ１つ手作業で進めるのはなかなかに面倒である。公式でサポートされている Visual Source Code の Flutter拡張機能を利用する。

### エディタの設定
Visual Source Code(vscode)での開発が便利なのでそれを利用することがおすすめだ。
公式の設定方法に従ってインストールを進める。

https://docs.flutter.dev/get-started/editor

vscodeがインストールできたら、flutterの拡張をインストールを忘れないように実施する。

![スクリーンショット 2024-02-01 8.53.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/8f380a6b-87eb-7748-604a-4e0d2647d5c3.png)

### サンプルアプリを実行する

公式ガイドにしたがってサンプルアプリを作成して実行してみる。

https://docs.flutter.dev/get-started/test-drive




# WebViewアプリを作成する

サンプルアプリを作成して実行できるようになったら、そのサンプルアプリにWebViewを組み込んでみよう。

## サンプルアプリを用意
先ほど作成したサンプルアプリと全く同じサンプルアプリをwebviewというプロジェクト名で用意する。

![webview_sample.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/b8c2e42c-1580-141f-c9f1-54818e790109.gif)


## WebViewプラグインをインストールする
flutter_linux_webviewの公式サイトの説明にしたがってサンプルアプリを改変する。

https://pub.dev/packages/flutter_linux_webview

### プラグインのインストール
```terminal:
$ flutter pub add flutter_linux_webview:'^0.1.0'
$ flutter pub add webview_flutter:'^3.0.4'
```

### CMakeLists.txt の修正
`linux/CMakeLists.txt`の`add_executable(${BINARY_NAME})`より後ろの行に下記を追加
```cmake
include(flutter/ephemeral/.plugin_symlinks/flutter_linux_webview/linux/cmake/link_to_cef_library.cmake)
```

### main.dart を置き換え
`lib/main.dart`を公式ページのサンプルプログラムで置き換える

```dart:lib/main.dart
import 'dart:async';
// Required to use AppExitResponse for Fluter 3.10 or later
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:flutter_linux_webview/flutter_linux_webview.dart';

void main() {
  // ensureInitialized() is required if the plugin is initialized before runApp()
  WidgetsFlutterBinding.ensureInitialized();

  // Run `LinuxWebViewPlugin.initialize()` first before creating a WebView.
  LinuxWebViewPlugin.initialize(options: <String, String?>{
    'user-agent': 'UA String',
    'remote-debugging-port': '8888',
    'autoplay-policy': 'no-user-gesture-required',
  });

  // Configure [WebView] to use the [LinuxWebView].
  WebView.platform = LinuxWebView();

  runApp(const MaterialApp(home: _WebViewExample()));
}

class _WebViewExample extends StatefulWidget {
  const _WebViewExample({Key? key}) : super(key: key);

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

class _WebViewExampleState extends State<_WebViewExample>
    with WidgetsBindingObserver {
  final Completer<WebViewController> _controller =
      Completer<WebViewController>();

  /// Prior to Flutter 3.10, comment out the following code since
  /// [WidgetsBindingObserver.didRequestAppExit] does not exist.
  // ===== begin: For Flutter 3.10 or later =====
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  Future<AppExitResponse> didRequestAppExit() async {
    await LinuxWebViewPlugin.terminate();
    return AppExitResponse.exit;
  }
  // ===== end: For Flutter 3.10 or later =====

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('flutter_linux_webview example'),
      ),
      body: WebView(
        initialUrl: 'https://flutter.dev',
        initialCookies: const [
          WebViewCookie(name: 'mycookie', value: 'foo', domain: 'flutter.dev')
        ],
        onWebViewCreated: (WebViewController webViewController) {
          _controller.complete(webViewController);
        },
        javascriptMode: JavascriptMode.unrestricted,
      ),
      floatingActionButton: favoriteButton(),
    );
  }

  Widget favoriteButton() {
    return FutureBuilder<WebViewController>(
        future: _controller.future,
        builder: (BuildContext context,
            AsyncSnapshot<WebViewController> controller) {
          if (controller.hasData) {
            return FloatingActionButton(
              onPressed: () async {
                final String useragent = (await controller.data!
                    .runJavascriptReturningResult('navigator.userAgent'));
                final String title = (await controller.data!.getTitle())!;
                final String url = (await controller.data!.currentUrl())!;
                final String cookies = await (controller.data!
                    .runJavascriptReturningResult('document.cookie'));
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(
                    content: Text(
                        'userAgent: $useragent, title: $title, url: $url, cookie: $cookies'),
                  ),
                );
              },
              child: const Icon(Icons.favorite),
            );
          }
          return Container();
        });
  }
}
```

# WebViewアプリの実行

作成したWebViewアプリを実行する。

```terminal:webviewアプリの実行
$ flutter run
```

![flutter.webpage.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/62e75ebc-7594-0687-bf13-e51088657d7f.gif)

## YouTubeを表示する
`initialUrl`を`https://www.youtube.com`に変更すればyoutubeをブラウジングすることも可能だ。もちろん音声再生もサポートされている。
```dart:
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('flutter_linux_webview example'),
      ),
      body: WebView(
        initialUrl: 'https://www.youtube.com',
        initialCookies: const [
          WebViewCookie(name: 'mycookie', value: 'foo', domain: 'flutter.dev')
        ],
```

![youtube.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/0602b830-2f3e-afef-1c1c-6c7b11cfeff4.gif)