.NET MAUIと.NET 9でGPS位置情報サービスを有効化する完全ガイド(Xamarin.Formsからの移行ポイントも解説)

.NET MAUI / .NET 9 への移行で「Xamarin.Forms 時代の GPS ON ダイアログが動かない」「Android 14/15・targetSdk 34 以上で位置情報が有効にならない」と悩むケースが増えています。本記事では、Xamarin.Forms → .NET MAUI → .NET 9 という移行を前提に、Android 端末で確実に位置情報サービス(GPS)を有効化させる実装パターンを、サンプルコード付きで整理して解説します。

目次

.NET MAUI での位置情報有効化の考え方を整理する

まずは「位置情報まわりで何が起きているか」をレイヤー別に整理しておくと、移行時のトラブルシュートが一気に楽になります。

レイヤー役割主な設定・APIMAUI 側の責務
権限 (Permission)アプリに位置情報の利用を許可するかAndroid Manifest, Runtime Permission
(MAUI: Permissions.LocationWhenInUse など)
アプリ起動後に権限を要求・確認
システム設定 (GPS ON/OFF)端末全体で位置情報サービスが ON かAndroid 設定アプリの「位置情報」スイッチ
Google Play Services の LocationSettingsRequest
ユーザーを設定画面 or システムダイアログへ誘導
位置情報 API現在地の取得・監視MAUI: Geolocation.Default
Android ネイティブ: 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 の GeolocationMAUI 標準 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.Ktx
  • Xamarin.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 世代では以下のような「安全寄りの戦略」をとるのがおすすめです。

  1. MAUI 標準の Geolocation.Default を常に優先する
  2. それでも FeatureNotEnabledException が発生した場合のみ Google Play Services API でダイアログを試す
  3. 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.PriorityHighAccuracy1〜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 APILocationRequest.Create() をまだ使っていないかnew LocationRequest.Builder(...).Build() に置き換え
targetSdk / compileSdkAndroid 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 でハマっているプロジェクトの整理の一助になれば幸いです。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次