mcpField
M
maui-file-handling

maui-file-handling

github.com/davidortinau開発17646 回インストール16 週間前に更新
リポジトリ

maui-file-handling について

.NET MAUIでファイルを扱う開発者向けのスキルです。プラットフォームごとのピッカー形式や、パス・権限・ストリームの注意点を説明します。

主な機能

  1. 01OpenReadAsync による選択ファイルの読み取り
  2. 02プラットフォーム別のファイル形式設定
  3. 03編集前にバンドルファイルをコピー
  4. 04クロスプラットフォームのディレクトリパス利用
  5. 05ピッカーのキャンセルと Android 権限の確認

活用シーン

  1. 01クロスプラットフォームのファイル選択と読み取り
  2. 02Android、iOS、macOS、Windows向けフィルター設定
  3. 03バンドルアセットの保存と編集
  4. 04Androidのストリーム長や権限の問題の調査

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/davidortinau/maui-skills --skill maui-file-handling

インストール後、Claude Code で/maui-file-handlingを入力して読み込まれたか確認します。

同梱ファイルreferences/file-handling-api.md

詳細

.NET MAUI のファイル処理

重要:常に OpenReadAsync() を使用し、FullPath は使用しないでください

一部のプラットフォーム(特に Android)では、ファイルシステムのパスではなくコンテンツ URIが返されます。FullPath を直接読み取ると、例外が発生するか、データが空になります。

// ❌ Breaks on Android — FullPath may be a content:// URI
var result = await FilePicker.Default.PickAsync();
var bytes = File.ReadAllBytes(result.FullPath);

// ✅ Works on all platforms
var result = await FilePicker.Default.PickAsync();
if (result is not null)
{
    using var stream = await result.OpenReadAsync();
    // process stream
}

プラットフォームごとに異なるファイル形式に注意

カスタムの FilePickerFileType では、プラットフォームごとに異なる形式が使用されます。 形式を取り違えると、ファイルピッカーにファイルが表示されなかったり、クラッシュしたりします。

プラットフォーム形式例
AndroidMIME タイプ"application/json"
iOS / macOSUTType 識別子"public.json"
Windowsドットで始まる拡張子".json"
// ❌ Using file extensions for Android — picker shows nothing
{ DevicePlatform.Android, new[] { ".json", ".txt" } }

// ✅ Correct MIME types for Android
{ DevicePlatform.Android, new[] { "application/json", "text/plain" } }

よくある落とし穴

バンドルされたファイルは読み取り専用です

Resources/Raw のアセットは、実行時に変更できません。書き込もうとすると、例外が発生します(プラットフォームによっては何も通知されずに失敗します)。

// ❌ Trying to write to a bundled file
var path = "data.json"; // inside Resources/Raw
File.WriteAllText(path, newContent); // fails

// ✅ Copy to AppDataDirectory first, then modify
string targetPath = Path.Combine(FileSystem.Current.AppDataDirectory, "data.json");
if (!File.Exists(targetPath))
{
    using var source = await FileSystem.Current.OpenAppPackageFileAsync("data.json");
    using var dest = File.Create(targetPath);
    await source.CopyToAsync(dest);
}
// Now safe to read/write targetPath
バンドルされたサブディレクトリはフラット化されます

一部のプラットフォームでは、Resources/Raw/subdir/file.txt は単に file.txt になります。サブディレクトリ構成にかかわらず、一意のファイル名を使用してください。

再ビルド時に iOS のサンドボックスパスが変わります

iOS のサンドボックスパスにはアプリの GUID が含まれ、クリーンビルドのたびに変わります。絶対パスをハードコードすると、気付かないうちに動作しなくなります。

// ❌ Hard-coded path — breaks after clean rebuild
var path = "/var/mobile/.../Documents/data.json";

// ✅ Always use the FileSystem helper
var path = Path.Combine(FileSystem.Current.AppDataDirectory, "data.json");
Android:バンドルされたストリームには Length がありません

OpenAppPackageFileAsync は、.Length を呼び出すと NotSupportedException が発生するストリームを返すことがあります。サイズが必要な場合は、MemoryStream にコピーしてください。

// ❌ Throws on Android
using var stream = await FileSystem.Current.OpenAppPackageFileAsync("data.json");
var size = stream.Length; // NotSupportedException

// ✅ Copy first if you need the length
using var stream = await FileSystem.Current.OpenAppPackageFileAsync("data.json");
using var ms = new MemoryStream();
await stream.CopyToAsync(ms);
var size = ms.Length;
Windows:仮想化されたファイルシステム

パッケージ化されたアプリでは、書き込み先が %AppData% などの従来のパスに通知されずにリダイレクトされます。クロスプラットフォームで確実に使用できるパスとして、常に AppDataDirectory と CacheDirectory を使用してください。

キャンセル時、FilePicker は null を返します
// ❌ NullReferenceException when user cancels
var result = await FilePicker.Default.PickAsync();
using var stream = await result.OpenReadAsync();

// ✅ Always null-check
var result = await FilePicker.Default.PickAsync();
if (result is null) return;
using var stream = await result.OpenReadAsync();

Android の権限(API 33 以降の変更)

Android 13 では、READ_EXTERNAL_STORAGE が用途別のメディア権限に置き換えられました。API 33 以降で従来の権限を使用しても、何も許可されません。

Android のバージョン必要な権限
≤ 12 (API 32)READ_EXTERNAL_STORAGE
≥ 13 (API 33)READ_MEDIA_IMAGES、READ_MEDIA_VIDEO、READ_MEDIA_AUDIO

チェックリスト

  • OpenReadAsync() を使用する — FullPath を直接読み取らない
  • プロパティにアクセスする前に、FilePicker の結果が null でないことを確認する
  • カスタムの FilePickerFileType では、プラットフォームごとに正しい形式(MIME / UTType / 拡張子)を使用する
  • バンドルされたファイルを変更する前に AppDataDirectory にコピーする
  • Resources/Raw では一意のファイル名を使用する(サブディレクトリはフラット化されます)
  • Android マニフェストで、対象 API レベルに適した権限を宣言する
  • macOS:App Sandbox エンタイトルメントにファイルアクセスを含める

コメント