2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Flutter Web】File System Access APIをpackage:webに移行した件

Last updated at Posted at 2024-03-07

はじめに

dart2wasm対応のために自作Flutter Webアプリをdart:htmlからpackage:webに移行しようとしたところ、サードパーティのパッケージと名前のコンフリクトが多数発生しました。これはむしろサードパーティパッケージが不要になったという良いシグナルだろうと考え、これもpackage:web他に移行したという話です。

File System Access APIとは

File System Access APIはWebアプリ(ブラウザアプリ)からローカルのファイルシステムへのアクセスを提供します。自作アプリではディレクトリピッカで選択させたディレクトリの中のファイルを読み書きしています。これまでは、公式パッケージのサポートがなかったため、サードパーティのpackage:file_system_accsess_apiを利用していました。

公式新パッケージへの移行

Dart SDK 3.3.0でextension typeとそれを用いたpackage:webが利用できるようになったのでこちらに移行します。これはブラウザAPIへのオーバヘッドの無いバインディングをWeb IDLから自動生成したものだそうです。
Dart PMのkevmooさん曰くpackage:webはほとんどクレイジー

The new package:web is pretty crazy. We use Dart code + JS code (NPM modules), compiled to JS to generate Dart code (pkg:web /lib contents) that we then compile to JS (and WebAssembly).

JSにコンパイルしたDartコードとJSコード(NPMモジュール)でDartコードを生成し(これがpackage:webの本体)、後にこれをJS(およびWASM)にコンパイルして利用する...

なお、後述の通りwindow.showDirectoryPicker()が提供されていないのでごにょごにょするためにdart:js_interopも利用しています。

パッケージの変更

pubspac.yamlの変更

before
environment:
  sdk: '>=2.18.0 <3.0.0'
dependencies:
  file_system_access_api: ^2.0.0
after
environment:
  sdk: '>=3.3.0 <4.0.0'
dependencies:

なお、ごにょごにょするために、Dart SDKの下限も3.3.0にしております。

importの変更

before
import 'dart:html';
import 'package:file_system_access_api/file_system_access_api.dart';
after
import 'dart:js_interop';
import 'package:web/web.dart' as web;

プレフィクス追加

Fultterとpackage:webでクラス名Textがコンフリクトしました。当然(?)Flutterを優先してpackage:webas webとしました。その結果として未解決となった箇所にプレフィクスweb.を追加していきます。

before
FileSystemWritableFileStream? logStream;
after
web.FileSystemWritableFileStream? logStream;

JS型とDart型の相互変換

残る静的エラーの大半はDart型とJS型の差異によるものです。エラーメッセージとIDEの推奨に従いtoJSまたはtoDartを追加することで簡単に解消できます。

before
    await logStream!.writeAsText('$message\n');
after
    await logStream!.write('$message\n'.toJS).toDart;

なお、この例ではメソッドの変更も必要でした。

名前付きオプション引数をextension typeに変更

ブラウザAPIはオプション群としてJSのオブジェクトリテラルを受け取ることがあります。package:file_system_access_apiでは手作りでこれらをDartらしい名前付きオプションパラメタに展開していました。package:web移行に伴い、オブジェクトリテラルに戻す必要があります。

こちらもIDEの推奨に従い、それっぽい名前のextension typeを選ぶだけの簡単な作業です。

before
    await dirHandle.getFileHandle('lockfile', create: true);
after
    await dirHandle
        .getFileHandle('lockfile', web.FileSystemGetFileOptions(create: true))
        .toDart;

とはいえ、extension typeの名前が長いのが気になりますね。

ディレクトリピッカのコール

ディレクトリピッカの露出

Compatibilityによると、experimentalであるwindow.showDirectoryPicker()をSafariとFirefoxがサポートしていないために、package:webWindowに露出していません。

これを利用するためには、公式ドキュメントInterop type membersに従い、Windowextension typeをもう一つ(例:@JS('Window')Window2)作るという方法が有ります。externalなメソッドを宣言すると、あとはDart SDKが良いように計らってくれます。

extensionで回避

とはいえ、package:webが既にWindowを提供してるので、これにメソッドを追加することを考えました。同公式ドキュメントを読み進めると、下記の様にextensionexternalなメソッドを宣言してあげることでwindow.showDirectoryPicker()にアクセスできます。

extension on web.Window {
  external JSPromise<web.FileSystemDirectoryHandle> showDirectoryPicker(
      [JSAny? options]);
}

options

window.showDirectoryPicker()はJavascriptオブジェクトリテラルの形でオプションを受け取ります。ここではディレクトリへの書き込み権限が欲しかったので、なんとかオブジェクトリテラルを渡す必要が有ります。以下の様にすることで、{mode: 'readwrite'}を生成できるようになりました。

@JS()
extension type Options._(JSObject _) implements JSObject {
  external Options({String mode});
}

なお、Dart SDK 3.3.0ではそのバグ回避に下記のおまじないも必要です。

@JS()
library;

と言った直後に、Dart SDK 3.3.1がリリースされ、そのバグが解消されました。

ディレクトリピッカのコール

これで晴れて、showDirectoryPicker()が呼べるようになりました。

before
    dirHandle =
        await window.showDirectoryPicker(mode: PermissionMode.readwrite);
after
    dirHandle = await web.window
        .showDirectoryPicker(Options(mode: 'readwrite'))
        .toDart;

マイグレーション全容

マイグレーション全容を下記に示しておきます。
migrate from package:file_system_access_api to package:web · Cat-sushi/fmscreen_flutter@1a58e30

おわりに

実際にやってみると、たいして難しくはありません。
また、dart:js_interopのおかげでJS FFIが自分で作れるのが良いですね。今回の様に、Dart SDKのサポートも、サードパーティAPIの出現も、W3Cにおけるexperimentalの卒業すらも待つ必要がありませんので。

課題

JSオブジェクトリテラル生成が面倒

汎用的にMapからオブジェクトリテラルを作成できる手段があると良いと思いました。Web APIのoptions実現のためならMap<String, dynamic>からオブジェクトリテラルが作成できるだけでもだいぶ違うと思います。
いやいや、Dartにおける名前付きパラメタ群をJSにける最終パラメタ(オブジェクトリテラルを期待)にすれば良いんではないでしょうか?

package:webで警告が出ない

Web向けプラグイン以外でdart:htmlをインポートすると警告が出ていましたが、package:webにマイグレーションしたところ警告が出なくなりました。
まあ、自作アプリはWeb専用なので警告は出ないほうが良いのですが...

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?