Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
Xamarin逆引きTips

Xamarin逆引きTips

Xamarin.Formsからプラットフォーム固有の機能を利用するには?(DependencyService利用)

2014年6月25日

UIを共通化するフレームワーク「Xamarin.Forms」で、「DependencyService」機能を使用してiOS/Androidの各プラットフォーム固有の機能を実装する方法を解説する。

奥山 裕紳(@amay077
  • このエントリーをはてなブックマークに追加

 Xamarin.Formsは、UIを共通にするフレームワークなので、プラットフォーム固有の機能は、それぞれ(iOS/Android)実装する必要がある。しかし、「DependencyService」と呼ばれる機能を使用することで、Xamarin.Formsから共通な呼び出しを行うことができる。今回は、このDependencyServiceの使い方を解説する。

1. Xamarin.Formsプロジェクトを作成する

 プラットフォーム固有の機能を使用する例として、iOSとAndroidで、GPSの緯度経度を表示するアプリを作る。

 Xamarin Studioのメニューバーの[ファイル]-[新規]-[ソリューション]から、[C#]-[Mobile Apps]-[Blank App (Xamarin.Forms Portable)]を選択し、ソリューション名を「GpsSample」として[OK]ボタンを押す。

図1 プロジェクト作成画面

 GpsSampleプロジェクト内のApp.csファイルを以下のように修正して、ボタンとラベルを配置する。

C#
……省略……
public class App
{
  public static Page GetMainPage()
  {   
    var buttonStartGps = new Button
    {
      Text = "Start GPS"
    };

    var labelLatLon = new Label
    {
    };

    return new ContentPage
    {
      Content = new StackLayout
      {
        Orientation = StackOrientation.Vertical,
        VerticalOptions = LayoutOptions.CenterAndExpand,
        HorizontalOptions = LayoutOptions.CenterAndExpand,
        Children =
        {
          buttonStartGps,
          labelLatLon
        }
      }
    };
  }
}
ボタンとラベルを配置するコード例(App.cs)

2. GPSを利用するための、共通なインターフェースを定義する

 GpsSampleプロジェクト内にIGeoLocator.csファイルを作成し、GPSを利用するための共通なIGeoLocatorインターフェースとその関連クラスを以下のように定義する。

C#
……省略……
// イベントのパラメーター
public class LocationEventArgs : EventArgs
{
  public double Latitude { get; set; }
  public double Longitude { get; set; }
}

// 位置を受信した際のイベントハンドラー
public delegate void LocationEventHandler(object sender, LocationEventArgs args);

// GPS を利用するための、共通なインターフェース
public interface IGeolocator
{
  void StartGps();
  event LocationEventHandler LocationReceived;
}
GPSを利用するための、共通なインターフェース定義のコード例(IGeoLocator.cs)

 Xamarin.Forms内ではこのIGeoLocatorインターフェースを介してプラットフォーム固有の機能を利用する。

3. iOSで、IGeolocatorインターフェースを実装する

 GpsSample.iOSプロジェクト内に、GeoLocator_iOS.csファイルを作成し、以下のように実装する。

C#
 
 
 
 
 
1
 
 
 
2
 
 
 
 
 
 
 
 
 
 
 
 
3
 
 
 
 
 
 
 
 
 
 
 
using System;
using MonoTouch.CoreLocation;
using Xamarin.Forms;
using GpsSample.iOS;

[assembly: Dependency (typeof (GeoLocator_iOS))]

namespace GpsSample.iOS
{
  public class GeoLocator_iOS : IGeolocator
  {
    public event LocationEventHandler LocationReceived;

    private readonly CLLocationManager _locationMan = new CLLocationManager();

    public void StartGps()
    {
      _locationMan.LocationsUpdated += (sender, e) =>
      {
        if (this.LocationReceived != null) {
          var l = e.Locations[e.Locations.Length - 1];

          this.LocationReceived(this, new LocationEventArgs
          {
            Latitude = l.Coordinate.Latitude,
            Longitude = l.Coordinate.Longitude
          });
        }
      };

      _locationMan.StartUpdatingLocation();
    }
  }
}
iOS向けのIGeolocatorインターフェース実装のコード例(GeoLocator_iOS.cs)

 まず、1が重要な要素だ。この定義により、このGeoLocator_iOSクラスがIGeoLocatorインターフェースのiOS用の実装クラスだと宣言している。

 2の箇所で、実際にGeoLocator_iOSクラスは、IGeoLocatorインターフェースを実装している。

 iOSではCLLocationManagerクラスを利用してGPSの位置を取得する。取得した位置をLocationReceivedイベントで通知するのが3だ。

【コラム】GeoLocator_iOSのクラス名

 このTipsではIGeoLocatorインターフェースの実装クラス名をGeoLocator_iOSとしているが、特に命名規約はない。IGeoLocatorを実装しており、Dependency(typeof ())属性で指定されていればどのようなクラス名でも動作する。

4. Xamarin.Formsで、IGeoLocatorオブジェクトを利用する

 GpsSampleプロジェクトに戻り、App.csファイルを以下のように追記する。

C#
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
public class App
{
  public static Page GetMainPage()
  {   
    var buttonStartGps = new Button
    {
      Text = "Start GPS"
    };

    var labelLatLon = new Label
    {
    };

    // 追加ここから ---
    buttonStartGps.Clicked += (sender, e) => 
    {
      var geoLocator = DependencyService.Get<IGeolocator>();

      geoLocator.LocationReceived += (_, args) => 
      {
        labelLatLon.Text = String.Format("{0:0.00}/{1:0.00}", 
          args.Latitude, args.Longitude);
      };

      geoLocator.StartGps();
    };
    // --- 追加ここまで

    return new ContentPage
    {
      Content = new StackLayout
      {
        Orientation = StackOrientation.Vertical,
        VerticalOptions = LayoutOptions.CenterAndExpand,
        HorizontalOptions = LayoutOptions.CenterAndExpand,
        Children =
        {
          buttonStartGps,
          labelLatLon
        }
      }
    };
  }
}
ボタンクリック時にIGeoLocatorオブジェクトのStartGpsメソッドを呼び出すコード例(App.cs)

 いよいよDependencyServiceの登場となる。ボタンが押された時に、DependencyServiceから、IGeoLocatorのインスタンスを取得する(1)。DependencyServiceはこのとき、実行中のプラットフォームに応じてIGeoLocatorの実装クラス(iOSならばGeoLocator_iOS)をインスタンス化し返却する。あとは、この得られたインスタンスを使い、受信した緯度経度値をラベルに表示させる。

5. iOSで実行してみる

 ここまでのプログラムをiOSで実行すると、次の画面のようになる。

図2 iOSでの実行画面
図2 iOSでの実行画面

6. Androidで、IGeolocatorインターフェースを実装する

 iOSでの実行が確認できたら、次はAndroid側も実装する。GpsSample.Androidプロジェクトに、GeoLocator_Android.csファイルを作成し、以下のように実装する。

C#
 
 
 
 
 
 
 
1
 
 
 
2
 
 
 
 
 
 
 
 
 
 
4
 
 
 
 
 
 
 
 
 
 
 
3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
using System;
using Xamarin.Forms;
using GpsSample.Android;
using Android.Content;
using Android.Locations;
using Android.OS;

[assembly: Dependency (typeof (GeoLocator_Android))]

namespace GpsSample.Android
{
  public class GeoLocator_Android : IGeolocator
  {
    public event LocationEventHandler LocationReceived;

    public void StartGps()
    {
      var context = Forms.Context;
      var locationMan = context.GetSystemService(Context.LocationService) 
        as LocationManager;

      locationMan.RequestLocationUpdates(LocationManager.GpsProvider, 0, 0, 
        new MyLocationListener(l =>
        {
          if (this.LocationReceived != null) {
            this.LocationReceived(this, new LocationEventArgs 
            {
              Latitude = l.Latitude,
              Longitude = l.Longitude
            });
          }
        }));
    }

    class MyLocationListener : Java.Lang.Object, ILocationListener
    {
      private readonly Action<Location> _onLocationChanged;

      public MyLocationListener(Action<Location> onLocationChanged)
      {
        _onLocationChanged = onLocationChanged;
      }

      public void OnLocationChanged(Location location)
      {
        _onLocationChanged(location);
      }

      public void OnProviderDisabled(string provider) { }
      public void OnProviderEnabled(string provider) { }
      public void OnStatusChanged(string provider, 
        Availability status, Bundle extras)  { }
    }

  }
}
Android向けのIGeolocatorインターフェース実装のコード例(GeoLocator_Android.cs)

 12はiOSと同じ要領だ。AndroidではStartGpsメソッドの実装は、LocationManagerクラスを利用して行う。

 位置の受信はILocationListenerインターフェースのオブジェクトで行うが、C#言語では、Java言語のように匿名クラスが使えないので、別のクラスMyLocationListenerを定義し(3)、そのコンストラクターの引数にActionデリゲートを設定できるようにして、そのデリゲート経由でOnLocationChangedメソッドへの位置受信結果をLocationReceivedイベントハンドラー側へ通知して、緯度経度値をラベルに表示させる。

 なお、このActionデリゲートのメソッドは、使用するときにラムダ式で記述できる(4)。この手法はXamarin.Androidではよく利用するので覚えておくとよいだろう。

7. Androidで、GPSを使うための権限を設定する

 Androidでは、GPSを使用するのに権限を設定する必要がある。次の画面を参考に、AndroidManifest.xmlを開いて、[AccessFineLocation]にチェックを入れる。

図3 権限の設定画面

8. Androidで実行する

 ここまでのプログラムをAndroidで実行すると、次の画面のようになる。

図4 Androidでの実行画面
図4 Androidでの実行画面

まとめ

 DependencyServiceを利用すると、プラットフォーム固有の処理をXamarin.Formsから、共通のインターフェースを介して利用できる。これはMvvmCrossなどのフレームワークでも提供される機能であるが、Xamarinの標準機能として搭載されているのはありがたい。

 DependencyServiceは、実行中のプラットフォームに対応する実装クラスが見つからないときにはnullを返す。例えばGeoLocator_Androidクラスを実装していないときにAndroidで実行すると、geoLocator変数(=DependencyServiceを使って取得したIGeolocatorオブジェクト)はnullとなり動作しないので考慮が必要だ。

 Xamarin Developersの「Accessing Native Features via the DependencyService(英語)」にも情報があるので、参考にしてほしい。

※以下では、本稿の前後を合わせて5回分(第6回~第10回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

Xamarin逆引きTips
6. Xamarin.iOSでStoryboardとXamarin.Formsを併用するには?

Storyboardで作成したiOSアプリの一部の画面に、Xamarin.Formsを利用する方法を解説する。

Xamarin逆引きTips
7. Xamarin.AndroidでActivityとXamarin.Formsを併用するには?

iOSの場合と同じように、Androidアプリの一部の画面に、Xamarin.Formsを利用する方法を解説する。また、iOSとの挙動の違いやフラグメントとの併用についても言及する。

Xamarin逆引きTips
8. 【現在、表示中】≫ Xamarin.Formsからプラットフォーム固有の機能を利用するには?(DependencyService利用)

UIを共通化するフレームワーク「Xamarin.Forms」で、「DependencyService」機能を使用してiOS/Androidの各プラットフォーム固有の機能を実装する方法を解説する。

Xamarin逆引きTips
9. Xamarin.Formsでダイアログボックス(とBusyインジケーター)を表示するには?

ダイアログ(=iOSのUIAlertView/AndroidのAlertDialog)や、処理実行中を示すBusyインジケーターを、Xamarin.Formsで表示する方法を解説する。これらは共通のAPIを使って実装できる。

Xamarin逆引きTips
10. Xamarin.Formsでカスタムダイアログを表示するには?(MessagingCenter利用)

Xamarin.Formsで共通のAPIが提供されているダイアログ(=iOSのUIAlertView/AndroidのAlertDialog)以外のプラットフォーム個別のダイアログを表示する方法を解説。その呼び出しにはMessagingCenterが利用できる。

サイトからのお知らせ

Twitterでつぶやこう!