maui-permissions について
.NET MAUI の権限処理における注意点と推奨パターンをまとめたスキルです。iOS、Android、Windows の挙動の違いを踏まえた実装に役立ちます。
主な機能
- 01要求前の権限状態チェック
- 02拒否・制限・限定状態への対応
- 03iOS と Android の挙動差への配慮
- 04プラットフォーム別の権限宣言
活用シーン
- 01許可済み権限への再要求を回避
- 02拒否後に iOS の設定へ案内
- 03再要求前に Android で説明を表示
- 04Android 13+ でメディア権限を選択
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ davidortinau/ maui-skills --skill maui-permissionsインストール後、Claude Code で/maui-permissionsを入力して読み込まれたか確認します。
同梱ファイルreferences/permissions-api.md
詳細
.NET MAUI のアクセス許可 — 注意点とベストプラクティス
重大なアンチパターン
1. 最初に確認せずにリクエストする
// ❌ Shows prompt even if already granted
var status = await Permissions.RequestAsync<Permissions.Camera>();
// ✅ Check first — avoids unnecessary prompts
var status = await Permissions.CheckStatusAsync<Permissions.Camera>();
if (status != PermissionStatus.Granted)
status = await Permissions.RequestAsync<Permissions.Camera>();
2. コンストラクターでアクセス許可を呼び出す
アクセス許可 API は非同期であり、UI コンテキストを必要とします。コンストラクターでは await できません。
// ❌ Blocks the UI thread or throws
public MyViewModel()
{
var status = Permissions.RequestAsync<Permissions.Camera>().Result;
}
// ✅ Use OnAppearing, a command, or async initialization
protected override async void OnAppearing()
{
await CheckAndRequestPermissionAsync<Permissions.Camera>();
}
3. Denied と Restricted のステータスを無視する
// ❌ Only checks for Granted — misses edge cases
if (status == PermissionStatus.Granted) { /* proceed */ }
// ✅ Handle all relevant statuses
switch (status)
{
case PermissionStatus.Granted: /* proceed */ break;
case PermissionStatus.Limited: /* iOS partial access */ break;
case PermissionStatus.Denied:
case PermissionStatus.Restricted:
await ShowSettingsPromptAsync(); break;
}
プラットフォーム固有の注意点
⚠️ iOS:アクセス許可ダイアログは一度しか表示されない
iOS では、システムのアクセス許可ダイアログはアクセス許可ごとに一度しか表示されません。拒否された後は、UI を表示せずに RequestAsync がただちに Denied を返します。ユーザーを設定 → アプリ → アクセス許可へ誘導する必要があります。
⚠️ Android:ShouldShowRationale を呼び出すタイミング
ShouldShowRationale が true を返すのは、以前に拒否された後のみです(ただし、「今後表示しない」を選択した後は返しません)。再リクエストする前に説明用の UI を表示するために使用します。
if (Permissions.ShouldShowRationale<Permissions.Camera>())
{
await Shell.Current.DisplayAlert("Permission needed",
"Camera access is required to scan barcodes.", "OK");
}
status = await Permissions.RequestAsync<Permissions.Camera>();
⚠️ Android API 33 以降:StorageRead/StorageWrite は機能しない
Android 13 以降では、スコープ付きストレージにより意味がなくなったため、StorageRead と StorageWrite は常に Granted を返します。代わりに、メディアごとに細分化されたアクセス許可を使用してください。
// ❌ Always returns Granted on API 33+ — gives false confidence
await Permissions.RequestAsync<Permissions.StorageRead>();
// ✅ Use the specific media permission
await Permissions.RequestAsync<Permissions.Photos>(); // photo access
await Permissions.RequestAsync<Permissions.Media>(); // audio/video
⚠️ Windows:ほとんどのアクセス許可は常に Granted を返す
Windows には、ほとんどの機能についてランタイムのアクセス許可ダイアログがありません。代わりに、Package.appxmanifest で機能を宣言してください。
必ずリクエスト前に確認するパターン
推奨パターンでは、iOS の一度限りのダイアログと Android の説明表示に対応します。
public async Task<PermissionStatus> CheckAndRequestPermissionAsync<T>()
where T : Permissions.BasePermission, new()
{
var status = await Permissions.CheckStatusAsync<T>();
if (status == PermissionStatus.Granted)
return status;
if (status == PermissionStatus.Denied && DeviceInfo.Platform == DevicePlatform.iOS)
return status; // iOS won't show dialog again
if (Permissions.ShouldShowRationale<T>())
{
await Shell.Current.DisplayAlert("Permission needed",
"This feature requires the requested permission.", "OK");
}
return await Permissions.RequestAsync<T>();
}
チェックリスト
- すべての
RequestAsyncの前にCheckStatusAsyncを呼び出している - コンストラクターでアクセス許可を呼び出していない —
OnAppearingまたはコマンドを使用している - すべての
PermissionStatusの値(Denied、Restricted、Limited)を処理している - Android:再リクエストする前に
ShouldShowRationaleを表示している - iOS:拒否されたアクセス許可について、設定画面への移動手段を用意している
- Android API 33 以降:
StorageRead/StorageWriteの代わりにPhotos/Mediaを使用している - マニフェスト/plist の宣言が、ランタイムのアクセス許可リクエストと一致している
コメント
Markdown 対応