.NET MAUI / .NET 9 への移行で「Xamarin.Forms 時代の GPS ON ダイアログが動かない」「Android 14/15・targetSdk 34 以上で位置情報が有効にならない」と悩むケースが増えています。本記事では、Xamarin.Forms → .NET MAUI → .NET 9 という移行を前提に、Android 端末で確実に位置情報サービス(GPS)を有効化させる実装パターンを、サンプルコード付きで整理して解説します。
.NET MAUI での位置情報有効化の考え方を整理する
まずは「位置情報まわりで何が起きているか」をレイヤー別に整理しておくと、移行時のトラブルシュートが一気に楽になります。
| レイヤー | 役割 | 主な設定・API | MAUI 側の責務 |
|---|---|---|---|
| 権限 (Permission) | アプリに位置情報の利用を許可するか | Android Manifest, Runtime Permission (MAUI: Permissions.LocationWhenInUse など) | アプリ起動後に権限を要求・確認 |
| システム設定 (GPS ON/OFF) | 端末全体で位置情報サービスが ON か | Android 設定アプリの「位置情報」スイッチ Google Play Services の LocationSettingsRequest | ユーザーを設定画面 or システムダイアログへ誘導 |
| 位置情報 API | 現在地の取得・監視 | MAUI: Geolocation.DefaultAndroid ネイティブ: FusedLocationProvider など | ビジネスロジックから利用しやすい API にラップ |
重要なのは「アプリから GPS の ON/OFF を直接切り替えることはできない」という点です。できるのは 「ユーザーに切り替えてもらうためのダイアログや設定画面を開くこと」だけです。
Xamarin.Forms 時代との違い
Xamarin.Forms では、次のような構成が典型的でした。
- 共通プロジェクト:
DependencyService経由でILocationSettingsを呼ぶ - Android プロジェクト:
LocationSettingsRequest+ResolvableApiException.StartResolutionForResultでシステムダイアログを表示
.NET MAUI / .NET 9 では、同じことをする場合にも構成が少し変わります。
| 観点 | Xamarin.Forms | .NET MAUI (.NET 6–8) | .NET 9 / Android 14–15 |
|---|---|---|---|
| 依存性解決 | DependencyService | #if ANDROID / 部分クラス / DI | 同左(DependencyService は非推奨) |
| GPS 有効化ダイアログ | Google Play Services API(LocationSettingsRequest) | 同じ API を MAUI プロジェクトから直接呼び出し | パッケージを最新版に更新しつつ同じ API を利用 |
| 位置情報取得 | Xamarin.Essentials の Geolocation | MAUI 標準 Geolocation.Default | 同左(MAUI 10 / .NET 9 でも Geolocation.Default が IGeolocation 実装を提供) |
| Google Play 要件 | targetSdk 30–31 辺り | targetSdk 31–33 が多い | 新規アプリは targetSdk 35(Android 15)以上、既存アプリも 34 以上必須に移行中 |
つまり、やりたいこと自体は同じでも、「呼び出し方」と「使う NuGet パッケージ」が変わっているのがポイントです。
.NET MAUI 共通コードでの「基本」位置情報フロー
まずは Android / iOS 共通の「位置情報を取る」部分を、MAUI 標準 API に寄せておきます。これをきちんと整理しておくと、Android 固有の「GPS ON ダイアログ」を後付けしやすくなります。
権限の確認と要求
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Devices.Sensors;
public static class LocationHelper
{
public static async Task<Location?> GetCurrentLocationAsync(
GeolocationAccuracy accuracy = GeolocationAccuracy.High,
int timeoutSeconds = 10)
{
// 1. 権限チェック
var status = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>();
if (status != PermissionStatus.Granted)
{
status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
}
if (status != PermissionStatus.Granted)
{
// ユーザーが拒否した場合は null などでアプリ側に判断させる
return null;
}
// 2. 位置情報取得
var request = new GeolocationRequest(accuracy, TimeSpan.FromSeconds(timeoutSeconds));
try
{
var location = await Geolocation.Default.GetLocationAsync(request);
return location;
}
catch (FeatureNotEnabledException)
{
// OS 側で位置情報サービスが OFF の場合
throw;
}
catch (Exception)
{
// タイムアウトや一時的なエラー
return null;
}
}
}
Geolocation.Default.GetLocationAsync は、必要に応じてランタイム権限も自動的に要求してくれます(ただし Manifest の設定は別途必要)。Android / iOS 共通のコードは、まずこのヘルパーに寄せておくのがおすすめです。
.NET 6–8 の MAUI で GPS 有効化ダイアログを出す
ここからは Android 固有コードです。Xamarin.Forms 時代とほぼ同じロジックを、.NET MAUI プロジェクトの中に #if ANDROID で直接書いてしまうのがシンプルです。Microsoft Q&A の公式回答でもこの方針が紹介されています。
NuGet パッケージの追加
.NET 6–8 の MAUI で Google Play Services の Location API を利用するには、次の NuGet を追加します。
Xamarin.AndroidX.Fragment.KtxXamarin.GooglePlayServices.Location
Xamarin.GooglePlayServices.Location は、Google Play Services の com.google.android.gms:play-services-location のバインディングで、net8.0-android34.0 など MAUI のターゲットフレームワークにも対応しています。
プロジェクトファイル(.csproj)には、例えば以下のように記述します。
<ItemGroup>
<PackageReference Include="Xamarin.AndroidX.Fragment.Ktx" Version="1.8.8.1" />
<PackageReference Include="Xamarin.GooglePlayServices.Location" Version="121.3.0.7" />
</ItemGroup>
(実際のバージョンは環境に合わせて調整してください)
条件付きコンパイルと using
Android ネイティブ API にアクセスする部分は、以下のように #if ANDROID で囲みます。
#if ANDROID
using Android.Gms.Common.Apis;
using Android.Gms.Location;
using Microsoft.Maui.ApplicationModel;
#endif
この using を忘れると、ビルドは通ってもコードビハインドから参照できない、という微妙なエラーになりがちなので注意してください。
LocationRequest.Create() は Builder へ置き換える
Xamarin.Forms 時代の多くのサンプルでは、以下のように LocationRequest.Create() を使っていました。
// 旧 API(非推奨)
var locationRequest = Android.Gms.Location.LocationRequest.Create();
locationRequest.SetPriority(Android.Gms.Location.LocationRequest.PriorityHighAccuracy);
.NET 6–8 / 新しい Google Play Services では、LocationRequest.Builder を使う形に移行します。
// 新 API
var locationRequest = new LocationRequest
.Builder(Priority.PriorityHighAccuracy, 100) // 100ms 間隔
.Build();
GPS 有効化ダイアログを表示するメソッド(.NET 6–8 用)
上記を踏まえて、Android 専用のヘルパーを 1 つ作っておきます。
public static class AndroidLocationSettings
{
public static async Task<bool> EnsureLocationEnabledAsync()
{
#if ANDROID
var activity = Platform.CurrentActivity
?? throw new InvalidOperationException("CurrentActivity が取得できません。");
// 高精度 + 最短 100ms で更新を要求
var request = new LocationRequest
.Builder(Priority.PriorityHighAccuracy, 100)
.Build();
var settingsRequest = new LocationSettingsRequest
.Builder()
.AddLocationRequest(request)
.Build();
try
{
// ここで現在の設定をチェック
await LocationServices
.GetSettingsClient(activity)
.CheckLocationSettingsAsync(settingsRequest);
// 例外が出なければ、すでに GPS 有効
return true;
}
catch (ApiException ex) when (ex is ResolvableApiException resolvable)
{
// システム標準の「位置情報を ON にしますか?」ダイアログを表示
resolvable.StartResolutionForResult(activity, 0x1);
return false; // 実際に ON になったかどうかは OnActivityResult 側で判断
}
#else
await Task.CompletedTask;
return true;
#endif
}
}
このヘルパーは「GPS が OFF のときに、システムダイアログを出す」ことだけに責務を絞っています。実際の位置情報取得は、先ほどの LocationHelper.GetCurrentLocationAsync から行うように分離しておくとテストや保守が楽です。
共通コードからの呼び出し例
private async Task OnGetLocationButtonClickedAsync()
{
try
{
// 1. まず普通に位置情報取得を試みる
var location = await LocationHelper.GetCurrentLocationAsync();
if (location == null)
{
// 2. 取得に失敗した場合、FeatureNotEnabledException だったかどうかで分岐
// (例として try-catch を分けたい場合)
}
}
catch (FeatureNotEnabledException)
{
// 3. Android の場合だけ GPS 有効化ダイアログへ誘導
var enabled = await AndroidLocationSettings.EnsureLocationEnabledAsync();
if (!enabled)
{
// ユーザーがキャンセルしたなど
return;
}
// 4. 再度位置情報取得を試みる
var location = await LocationHelper.GetCurrentLocationAsync();
// TODO: 結果を UI に反映
}
}
このように「MAUI 標準 API → 失敗したら Android 固有ダイアログ → 再トライ」という 3 ステップで組むと、iOS / Windows では Android ネイティブコードがまったくコンパイルされず、きれいに分離できます。
.NET 9 / Android 14・15 / targetSdk 34+ での注意点
.NET 9 時代になると、問題は「コード」だけでなく「ビルド設定と NuGet の組み合わせ」にも広がります。
csproj のターゲットフレームワークと SDK レベル
.NET 9 / Android 14–15 向けの典型的な設定例は、次のようになります。
<PropertyGroup>
<TargetFrameworks>
net9.0-android;net9.0-ios;net9.0-windows10.0.19041.0
</TargetFrameworks>
<UseMaui>true</UseMaui>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
</PropertyGroup>
併せて、Android プロジェクトの「Target Android Version(targetSdkVersion)」が 34 以上(将来的には 35 以上)が必須になってくるので、Visual Studio のプロジェクト設定でも確認しておきます。
Xamarin.GooglePlayServices.Location のバージョンを最新に保つ
.NET 9 / Android 15 デバイスでも動かしたい場合は、Xamarin.GooglePlayServices.Location をできるだけ新しいバージョンにしておきます。2025 年時点の最新パッケージでは、少なくとも次のような点が確認できます。
- Java 側ライブラリ:
com.google.android.gms:play-services-location:21.3.0 - 対応フレームワーク:
net8.0-android34.0(net9.0-android などもコンピュート互換)
古いバージョンの Google Play Services パッケージのままだと、targetSdk 34 以上に上げたタイミングで以下のような現象が起きることがあります。
- ビルドは通るが、実機で
Java.Lang.NoSuchMethodErrorやClassNotFoundExceptionが発生 - GPS ON ダイアログが表示されず、
CheckLocationSettingsAsyncの Task が完了しない
この場合は、まず パッケージのバージョンを最新にする → それでもダメなら 一度クリーン & 再ビルド + キャッシュ削除 という順で切り分けていくとよいです。
.NET 9 での既知の落とし穴と回避戦略
前述の Microsoft Q&A では「.NET 9 に上げたら 1 年後にまた動かなくなった」というコメントもあり、.NET 9 + Android 15 の組み合わせでは、まだ情報が完全にはこなれていません。
そのため、.NET 9 世代では以下のような「安全寄りの戦略」をとるのがおすすめです。
- MAUI 標準の
Geolocation.Defaultを常に優先する - それでも
FeatureNotEnabledExceptionが発生した場合のみ Google Play Services API でダイアログを試す - Google Play Services API 側で問題がある場合は、最終的に設定アプリ (
Settings.ActionLocationSourceSettings) に飛ばす
public static class CrossPlatformLocationService
{
public static async Task<Location?> GetLocationWithDialogAsync()
{
try
{
// 1. まずは MAUI 標準 API で取得を試みる
var loc = await LocationHelper.GetCurrentLocationAsync();
if (loc != null)
{
return loc;
}
}
catch (FeatureNotEnabledException)
{
// 下に続く Android 分岐で処理
}
#if ANDROID
// 2. Android の場合、Google Play Services で GPS 設定をチェック
var enabled = await AndroidLocationSettings.EnsureLocationEnabledAsync();
if (!enabled)
{
// ユーザーがダイアログを閉じた、など
return null;
}
// 3. 再度 MAUI 標準 API で取得を試みる
try
{
return await LocationHelper.GetCurrentLocationAsync();
}
catch (FeatureNotEnabledException)
{
// 4. それでもダメなら設定画面に送る最終手段
Android.Content.Intent intent = new(Android.Provider.Settings.ActionLocationSourceSettings);
intent.AddFlags(Android.Content.ActivityFlags.NewTask);
Android.App.Application.Context.StartActivity(intent);
return null;
}
#else
return null;
#endif
}
}
このように「MAUI 標準 → Play Services ダイアログ → 設定アプリ」という 3 段階でフォールバックを用意しておくと、OS や端末による挙動差をある程度吸収できます。
バッテリーとユーザー体験を考慮した LocationRequest 設計
高頻度・高精度の位置情報更新は、当然ながらバッテリーを激しく消費します。Android 14 以降はバックグラウンド制限や Foreground Service のタイプ指定も厳しくなっているため、次の点を意識するとよいでしょう。
- 常時トラッキングが本当に必要か? → 多くの場合、「必要なタイミングだけ1回測位」で済む
Priority.PriorityHighAccuracyが必要なケースは限定的。地図表示などではAccuracy.Medium程度で十分なことが多い- 更新間隔(インターバル)は、業務要件を満たす最低限にする(数秒〜数十秒など)
例えば「ナビのようなリアルタイム追跡」なのか、「近くの店舗を一度だけ検索」なのかで、最適な設定は大きく変わります。
| ユースケース | 推奨精度 | 更新間隔の目安 | 備考 |
|---|---|---|---|
| 一度だけ現在地を取得 | Accuracy.Medium | 単発 | MAUI の GeolocationRequest のみで十分なことが多い |
| 徒歩ナビ / ランニングトラッカー | Priority.PriorityHighAccuracy | 1〜5 秒 | Foreground Service との組み合わせを検討 |
| エリア入退場検知(ジオフェンス) | 中〜高 | 数分〜10 分 | Google Play Services のジオフェンス API を活用 |
iOS との共通化戦略
Android 専用の「GPS 有効化ダイアログ」はどうしてもネイティブコードになりますが、それ以外はできるだけ MAUI 共通 API に寄せておくと、長期的な保守がかなり楽になります。
実務的には、次のような分け方がバランスがよいです。
- 共通コード
- 位置情報取得ロジック(
Geolocation.Default+Permissions) - 位置情報を使ったビジネスロジック(距離計算、ジオフェンス判定など)
- 位置情報取得ロジック(
- Android 専用コード(
#if ANDROID)- Google Play Services を使った GPS 有効化ダイアログ
- 最終手段としての設定画面遷移
- iOS 専用コード(必要なら)
CLLocationManager向けの微調整(フル精度要求など)
この構成にしておけば、.NET 8 → .NET 9 → .NET 10… と MAUI 側が進化しても、「Android ネイティブ部分だけを差し替える」だけで済むため、移行コストを抑えられます。
Xamarin.Forms → .NET MAUI → .NET 9 移行チェックリスト
最後に、実際に移行作業をするときのチェックリストをまとめておきます。
| 項目 | 確認内容 | OK なら |
|---|---|---|
| DependencyService の除去 | 位置情報関連で DependencyService.Get<T>() を使っていないか | #if ANDROID / 部分クラス / DI に置き換え |
| Manifest 権限 | ACCESS_FINE_LOCATION / ACCESS_COARSE_LOCATION が宣言されているか | MAUI の AndroidManifest.xml を確認 |
| ランタイム権限 | Permissions.LocationWhenInUse を使っているか | 共通ヘルパー(LocationHelper など)にまとめる |
| Google Play Services パッケージ | Xamarin.GooglePlayServices.Location のバージョンが古すぎないか | 最新の安定版へ更新(net8.0-android34.0 対応版など) |
| LocationRequest API | LocationRequest.Create() をまだ使っていないか | new LocationRequest.Builder(...).Build() に置き換え |
| targetSdk / compileSdk | Android 14 (API 34) 以上が指定されているか | Google Play の要件を満たす(将来的に 35 以上) |
| GPS ON ダイアログ | LocationSettingsRequest + ResolvableApiException で実装しているか | Android 固有ヘルパー(AndroidLocationSettings)に集約 |
| フォールバック | Play Services がうまく動かない端末向けの対策があるか | 設定画面 (Settings.ActionLocationSourceSettings) への遷移を用意 |
まとめ
本記事では、Xamarin.Forms 時代の DependencyService ベース実装から、.NET MAUI / .NET 9 時代の実装へと位置情報まわりをアップデートする手順を整理しました。
- 位置情報は「権限」「システム設定(GPS ON/OFF)」「位置情報 API」の 3 レイヤーで考える
- 権限と位置情報取得は、MAUI 標準の
Permissions/Geolocation.Defaultを中心に構成する - Android での「GPS を ON にしますか?」ダイアログは、
LocationSettingsRequest+ResolvableApiExceptionを MAUI プロジェクトから直接呼ぶ - .NET 9 / Android 14・15 では、targetSdk・NuGet バージョン・LocationRequest の API 変更をまとめて見直す
- うまく動かない端末向けに、最終手段として設定画面への遷移を用意しておく
この構成にしておけば、「Xamarin.Forms → .NET MAUI → .NET 9」とプラットフォームが進化しても、最小限の変更で Android 端末に標準の位置情報有効化ダイアログを表示し続けることができます。これから MAUI に移行するプロジェクトや、既に .NET 9 でハマっているプロジェクトの整理の一助になれば幸いです。

コメント