📂

【Flutter】Windows, Webでのファイル選択・保存方法 [後編]

2021/06/27に公開

前編(Windowsアプリ)に引き続き、Webでのファイル選択・保存を実装します。

ファイル読込

ファイル読込(アップロード)はWindowsアプリと同一ソースで可能でした。
実装内容は前編を参照ください。

ファイル保存

ここからが本題で、読込が上手くいったので保存もそのままいけるかと思ったらそう甘くはありませんでした。
ファイル保存を実行してもうんともすんとも言いません。
確認してみるとgetSavePathから空文字が返っていました。
(ファイル選択ダイアログでキャンセルを押下した場合はnullが返る)

どういうことか調べると、以下のURL先にあるように、プラットフォームとしてファイルを開くことはサポートされていないようです。
https://github.com/flutter/flutter/issues/78142

更に明示的なキャンセルであるnullと区別するために、プラットフォームとしてサポートしていない場合、空文字を返すようにしているようです。
https://github.com/flutter/plugins/blob/master/packages/file_selector/file_selector_web/lib/file_selector_web.dart#L48-L50

つまり、WebアプリではgetSavePathは使用できないので、getSavePathで空文字が返って来た場合、他の手段で実装する必要があります。

代替手段

FlutterのWebアプリでファイル保存(ダウンロード)を実現するには、dart:htmlパッケージのAnchorElementを使用してダウンロードリンクを作成し発火させる方法があります。
getSavePathで空文字が返ってきた場合のみ、AnchorElementを使用したダウンロードに切り替えます。

main.dart
import 'dart:html' as html;

---中略---

  child: ElevatedButton(
    onPressed: () async {
      String? path = await getSavePath(acceptedTypeGroups: [
	XTypeGroup(label: 'json', extensions: ['json'])
      ], suggestedName: "todoList.json");
      print(path);
      if (path == null) {
	return;
      }
      if (path == "") { // 空文字の場合、AnchorElementでDLリンクを作成 → 発火
	final anchor = html.AnchorElement(
	    href: "data:application/json;charset=utf-8," +
		jsonEncode(todoList));
	anchor.download = "todoList.json";
	anchor.click();
      } else {
	final data =
	    Uint8List.fromList(json.encode(todoList).codeUnits);
	final mimeType = "application/json";
	final file = XFile.fromData(data, mimeType: mimeType);
	await file.saveTo(path);
      }
      print(json.encode(todoList));
    },
    style: ElevatedButton.styleFrom(
      primary: Colors.red,
    ),
    child:
	Text("ファイル保存", style: TextStyle(color: Colors.white)),
  ),

これで無事、ファイルの保存が実現できました。

Windowsアプリでビルドエラー発生

これで一段落と思い、再度Windowsアプリのビルドを走らせてみるとエラーが発生しました。

原因はそのまま、dart:htmlがデスクトップをサポートしていないためです。(当たり前)
しかし、上記のようにWebアプリで保存を実現するためにはdart:htmlが必須です。
どうにか、条件設定などでdart:htmlをimportしたままWindowsアプリをビルドできないかと調べてみましたが、どうも無理なようです。

ここで、ワンソースでのWindows,Webアプリ開発断念かと思いましたが、そういった環境のためのパッケージが存在しました。
Universal_htmlです。

このパッケージははクロスプラットフォーム開発用にdart:htmlをデスクトップやモバイル環境でも使用できるようにしたもので、dart:htmlをそのままpackage:universal_html/html.dartに置き換えれば良いもののようです。

早速、インストールして、importを差し替えてみます。

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  file_selector: ^0.8.2
  file_selector_windows: ^0.0.2
+ universal_html: ^2.0.8
main.dart
- import 'dart:html' as html;
+ import "package:universal_html/html.dart" as html;

これで、無事Windows,Webともにビルドが通り、問題なく動作しました。

まとめ

手探りでWindowsアプリとWebアプリのファイル保存・読込を実装していきました。
一部分岐処理が必要になりましたが、ほぼワンソースで両アプリを開発できるFlutterのメリットを感じられました。

今回実装したソース(main.dartとpubspec.yaml)は以下に置いておきます。

Discussion