綴Photo には「データをエクスポート」機能がある。綴った言葉を JSON で書き出して控えを残すためのもの(写真本体は含まない、テキストの控え)。モバイルでは問題なく動いていたが、デスクトップ(Windows)で試したら様子がおかしいことに気づいた。
「データをエクスポート」を押すと、出てくるのが Windows の共有ダイアログ——メールや他アプリ宛てに「共有」する画面だった。JSON ファイルをどこかに保存したいのに、保存する導線がどこにもない。
この記事で扱う観察ポイント
- 同じ「書き出す」でも、モバイルとデスクトップで自然な操作(イディオム)が違います
share_plusの共有はモバイルの作法。デスクトップは「名前を付けて保存」- ついでに: 自分のデータの書き出しでも、絶対パスは漏れます
何が起きていたか
エクスポートはこうなっていた。
final dir = await getApplicationDocumentsDirectory();
final file = File('${dir.path}/tsuzuphoto_export.json');
await file.writeAsString(jsonString);
await Share.shareXFiles(
[XFile(file.path, mimeType: 'application/json')],
subject: '綴Photo データエクスポート',
);一時ファイルを書いて Share.shareXFiles で「共有」します。モバイルではこれが正解です。
直し: デスクトップは「名前を付けて保存」
OS で分岐して、デスクトップは file_picker の saveFile(名前を付けて保存ダイアログ)を使う形にした。
if (PlatformUtils.isDesktopOS) {
// デスクトップ: 保存先を選ぶダイアログ
final savePath = await FilePicker.platform.saveFile(
dialogTitle: 'データを保存',
fileName: 'tsuzuphoto_export.json',
type: FileType.custom,
allowedExtensions: ['json'],
);
if (savePath == null) return; // キャンセル
await File(savePath).writeAsString(jsonString);
// 共有もしないし一時ファイルも作らない
return;
}
// モバイル: 従来どおり共有シート
// (一時ファイルを書いて Share.shareXFiles → 共有後に削除)実機で試すと、デスクトップでちゃんと保存ダイアログが出て、選んだ場所に JSON が落ちるようになった。
ついでに直したプライバシー: 絶対パスを残さない
保存された JSON の中身を確認していて、もう1つ気づいた。写真の参照として localPath(デスクトップだと絶対パス)がそのまま入っていた。
{ "localPath": "C:\\Users\\<ユーザー名>\\Pictures\\2024\\..." }これは自分のデータを自分で書き出すものなので危険度は低いです。localPath はファイル名だけにマスクして書き出す形に変えた。
'localPath': m.photo.localPath == null
? null
: _fileNameOnly(m.photo.localPath!), // 絶対パス → "photo_001.jpg"「自分のデータだから何が入っていてもいい」ではなく、「その用途に必要な情報だけ書き出す」に寄せた。書き出したファイルがどこへ行く(クラウド同期・うっかり共有)か分からない以上、最初から余計なものを入れないのが堅いです。
学びポイント
- 「クロスプラットフォームな1つの共有 API」で済ませると、デスクトップでは保存になりません。分岐は
PlatformUtils.isDesktopOS - 自分のデータの書き出しでも、用途に必要な情報だけ載せます
おわりに
呼べることと自然に使えることのズレは、実機(しかもデスクトップ)で触らないと引っかかりません。エミュレータやモバイルだけ見ていたら見逃していた。
この記事は2026年5月時点の自分の理解で書いています。